# Bitquery Docs — full text: Use cases, trading APIs, examples, perpetuals, stablecoins and the API blog > 120 pages. All parts and the curated entry points: https://docs.bitquery.io/llms.txt ## Add-Liquidity Signals Telegram Bot URL: https://docs.bitquery.io/docs/usecases/add-liquidity-signal-bot/ Build Add-Liquidity Signals Telegram Bot: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Add-Liquidity Signals Telegram Bot This bot fetches real-time data on added liquidity for Solana DEX pools and sends alerts via Telegram. It highlights key details such as added liquidity, post-liquidity amounts, and provides direct trading links. Github Repository Link - [here](https://github.com/Akshat-cs/Add-Liquidity-Signal-Bot/tree/main) ## Tutorial Video ## Features - Monitors added liquidity for Solana DEX pools in real-time. - Sends detailed updates on: - Base and Quote currencies. - Added liquidity and post-liquidity values. - DEX protocol details. - Includes "Trade Now" links for immediate trading actions. - Handles Telegram message length limitations with intelligent splitting. - Periodically fetches updates (default: every 30 minutes). ## Prerequisites 1. **Python 3.8+** installed on your system. 2. **Telegram Bot Token** from [BotFather](https://telegram.me/BotFather). 3. **Bitquery API Token** for accessing Solana liquidity data. Get your API token [here](/docs/authorization/how-to-generate/). ## Installation 1. Clone this repository and navigate to the project directory: ```bash git clone https://github.com/Akshat-cs/Add-Liquidity-Signal-Bot ``` 2. Install the required dependencies: ```bash pip install python-telegram-bot aiohttp ``` 3. Replace Bot token and OAuth Token values in the `top-liquidity-additions.py` file with your own tokens. Get the BOT_TOKEN from Bot father and Bitquery OAuth token using these [steps](/docs/authorization/how-to-generate/): ``` BOT_TOKEN=your-telegram-bot-token OAUTH_TOKEN=your-bitquery-oauth-token ``` 4. Start the bot: ```bash python top-liquidity-additions.py ``` 5. Use the `/start` command in your Telegram chat with the bot to begin monitoring Solana pools with liquidity additions. ## How It Works 1. **Real-Time Monitoring**: - The bot queries the Bitquery API every 30 minutes for liquidity data from Solana DEX pools. - Fetches and formats data for pools with added liquidity in the last 5 minutes. 2. **Data Formatting**: - Extracts details about: - **Base and Quote Currencies**: Names, symbols, and mint addresses. - **Liquidity Information**: Added liquidity and post-liquidity values. - **Protocol Details**: Protocol family and name. - Includes direct "Trade Now" links for each pool. 3. **Message Splitting**: - Automatically splits long messages exceeding Telegram's character limit (4096 characters). - Handles Telegram's flood control by waiting and retrying if rate limits are exceeded. ## Code Walkthrough ### 1. **Configuration** - **`BOT_TOKEN`**: Your Telegram bot token from BotFather. - **`OAUTH_TOKEN`**: Your Bitquery API token for accessing Solana DEX data. - **Logging**: Configures logging to track bot operations and errors. ### 2. **Helper Functions** - **`split_text(text, max_length)`**: - Splits long messages into smaller parts to adhere to Telegram's 4096-character limit. - **`send_long_message(update, context, message_generator)`**: - Sends long messages as multiple parts. - Handles Telegram’s flood control by retrying after delays. ### 3. **GraphQL Query** - Queries the Bitquery API for Solana DEX pools with recently added liquidity: - Filters for pools with added liquidity (`ChangeAmount > 0`). - Retrieves market details, added liquidity, and post-liquidity values. - Limits results to the top 10 pools based on the `Block_Time`. ### 4. **Core Functions** - **`send_query_and_process(update, context)`**: - Sends the GraphQL query to the API and processes the response. - Formats the data into a user-friendly message using `format_message()`. - Sends the formatted messages to the Telegram chat. - **`format_message(data)`**: - Processes the API response data to extract: - Base and quote currency details. - Added liquidity and post-liquidity values. - Protocol information and trading links. - Constructs HTML-formatted Telegram messages. - Splits messages if they exceed the character limit. ### 5. **Task Management** - **Global Flag (`is_task_running`)**: - Prevents multiple instances of the periodic task from running simultaneously. - **`start_regular_requests(update, context)`**: - Continuously fetches and sends updates every 30 minutes. - Handles errors gracefully and ensures the global flag resets on task completion. ### 6. **Command Handlers** - **`/start` Command**: - Initializes the bot and starts the periodic task for fetching liquidity updates. ### 7. **Main Execution** - Initializes the Telegram bot using the `ApplicationBuilder` from `python-telegram-bot`. - Adds the `/start` command handler to the bot. - Runs the bot with polling to listen for incoming commands. --- ## Arbitrum Sniper Bot URL: https://docs.bitquery.io/docs/usecases/arbitrum-sniper-bot/ Build Arbitrum Sniper Bot: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. See examples in the Bitquery IDE. # Tutorial : Building a Arbitrum Sniper Bot Using Bitquery Arbitrum Events API and Uniswap SDK This tutorial will guide you through building a Arbitrum sniper bot using Bitquery Events API and the Uniswap SDK for executing swaps. > Note: This material is for educational and informational purposes only and is not intended as investment advice. The content reflects the author's personal research and understanding. While specific investments and strategies are mentioned, no endorsement or association with these entities is implied. Readers should conduct their own research and consult with qualified professionals before making any investment decisions. Bitquery is not liable for any losses or damages resulting from the application of this information. ## Tutorial Video ## ## Tutorial Github Code Repository - [Repository Link](https://github.com/Akshat-cs/Arbitrum-sniper-bot) ### Prerequisites 1. **Node.js** and **npm** installed on your system. 2. **Bitquery Free Developer Account** with OAuth token (follow instructions [here](/docs/authorization/how-to-generate/)). 3. **Any Arbitrum Chain supported Wallet** with some Arbitrum ETH for transaction fees and also some WETH as I have used WETH in the video tutorial to make swap. I have used the Bitquery Arbitrum Events API to get the latest created pool which has Token A as WETH with Token Addres `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`. - If you want to conduct the swap using different Token then you can change the address in this `Arguments: {startsWith: {Value: {Address: {is: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"}}}}` in tokens.ts file to your Token Address that you want to conduct swaps with. ### Step 1: Setting Up the Environment 1. **Initialize a new Node.js project:** ```bash mkdir Arbitrum-sniper-bot cd Arbitrum-sniper-bot npm init -y ``` 2. **Install the necessary dependencies:** ```bash npm install @types/node @uniswap/sdk-core @uniswap/smart-order-router @uniswap/v3-sdk axios dotenv ethers ts-node tslib typescript ``` ### Step 2: Creating the Bot 1. **Create a `.env` file :** This file will contain all the environment variables. Put in your Wallet private key that you are using to conduct swap. And also put in the Bitquery OAuth Token, follow the instructions on how to get it [here](/docs/authorization/how-to-generate/). ```javascript # Arbitrum MAINNET RPC=https://arb1.arbitrum.io/rpc WALLET_PRIVATE_KEY= CHAIN_ID=42161 SWAP_ROUTER_ADDRESS=0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 SLIPPAGE_TOLERANCE=5 DEADLINE_IN_MINUTES=30 BITQUERY_TOKEN= ``` 2. **Create a `config.ts` file:** This is a basic configuration file which helps us to expose our environment variables to our application and we are also using Ethers to set provider and signer. ```javascript import { Percent } from "@uniswap/sdk-core"; import { ethers, providers, Wallet } from "ethers"; import { config as loadEnvironmentVariables } from "dotenv"; loadEnvironmentVariables(); export const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; export const SWAP_ROUTER_ADDRESS = process.env.SWAP_ROUTER_ADDRESS || ""; export const CHAIN_ID = parseInt(process.env.CHAIN_ID || "1"); export const DEADLINE = Math.floor( (Date.now() / 1000) _ (parseInt(process.env.DEADLINE_IN_MINUTES || "30") _ 60) ); export const SLIPPAGE_TOLERANCE = new Percent( process.env.SLIPPAGE_TOLERANCE || 5, 100 ); const RPC = process.env.RPC; export const provider = ethers.providers.getDefaultProvider(RPC); export const signer = new Wallet(WALLET_PRIVATE_KEY, provider); ``` 3. **Create a `tokens.ts` file:** In this step we are importing necessary modules then loading environment variables and also setting ERC20 ABI as we are going to need it to make the token contract instance from token address. ```javascript loadEnvironmentVariables(); const ERC20_ABI = [ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function allowance(address, address) external view returns (uint256)", "function approve(address, uint) external returns (bool)", "function balanceOf(address) external view returns(uint256)", ]; ``` We are going to need Token Contract to call different functions like `balanceOf`, `decimals` and `symbol`. So we are building Token Contract using `buildERC20TokenWithContract` function by providing the token address and the provider. ```javascript type TokenWithContract = { contract: Contract, walletHas: (signer: Signer, requiredAmount: BigNumberish) => Promise, token: Token, }; const buildERC20TokenWithContract = async ( address: string, provider: Provider ): Promise => { try { const contract = new Contract(address, ERC20_ABI, provider); const [name, symbol, decimals] = await Promise.all([ contract.name(), contract.symbol(), contract.decimals(), ]); return { contract: contract, walletHas: async (signer, requiredAmount) => { const signerBalance = await contract .connect(signer) .balanceOf(await signer.getAddress()); return signerBalance.gte(BigNumber.from(requiredAmount)); }, token: new Token(CHAIN_ID, address, decimals, symbol, name), }; } catch (error) { console.error( `Failed to fetch token details for address ${address}:`, error ); return null; } }; ``` Setting provider and type of Tokens here as we are using Typescript. Then we built a function `getTokens` which makes an API call and gets the address of Tokens 0 and 1 in the latest created liquidity pool. This function finally returns the Token Contract for the fetched Token 0 and Token 1 addresses using `buildERC20TokenWithContract`. Keep in mind we have set the Token 0 address as WETH token address. In the `index.ts` file we are going to swap this WETH with the other token i.e token 1 in the pool. ```javascript // Example usage for ARBITRUM const provider = new providers.JsonRpcProvider(process.env.RPC); type Tokens = { Token0: TokenWithContract | null, Token1: TokenWithContract | null, }; export const getTokens = async (): Promise => { try { let data = JSON.stringify({ query: 'query {\n EVM(network: arbitrum) {\n Events(\n limit: {count:1}\n orderBy: {descending: Block_Time}\n where: {Log: {Signature: {Name: {is: "PoolCreated"}}, SmartContract: {is: "0x1F98431c8aD98523631AE4a59f267346ea31F984"}}, Arguments: {startsWith: {Value: {Address: {is: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"}}}}}\n ) {\n Transaction {\n Hash\n }\n Block {\n Time\n }\n Log {\n Signature {\n Name\n }\n }\n Arguments {\n Name\n Type\n Value {\n ... on EVM_ABI_Integer_Value_Arg {\n integer\n }\n ... on EVM_ABI_String_Value_Arg {\n string\n }\n ... on EVM_ABI_Address_Value_Arg {\n address\n }\n ... on EVM_ABI_BigInt_Value_Arg {\n bigInteger\n }\n ... on EVM_ABI_Bytes_Value_Arg {\n hex\n }\n ... on EVM_ABI_Boolean_Value_Arg {\n bool\n }\n }\n }\n }\n }\n}\n', variables: "{}", }); const axiosConfig: AxiosRequestConfig = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.BITQUERY_TOKEN}`, // put your oauth token here }, data: data, }; const response = await axios.request(axiosConfig); const token0Address = response.data.data.EVM.Events[0].Arguments[0].Value.address; const token1Address = response.data.data.EVM.Events[0].Arguments[1].Value.address; console.log(token0Address); console.log(token1Address); const Token0 = await buildERC20TokenWithContract(token0Address, provider); const Token1 = await buildERC20TokenWithContract(token1Address, provider); return { Token0, Token1 }; } catch (error) { console.error("Error fetching tokens:", error); return { Token0: null, Token1: null }; } }; ``` For the sake of the demo we have used a query, to **track new tokens in real-time** use the below subscriptions ([link](https://ide.bitquery.io/Subscription-Latest-created-pool-on-uniswap-V3-on-Arbirum-chain-with-Token0-as-WETH_1)). ```javascript subscription { EVM(network: arbitrum) { Events( orderBy: {descending: Block_Time} where: {Log: {Signature: {Name: {is: "PoolCreated"}}, SmartContract: {is: "0x1F98431c8aD98523631AE4a59f267346ea31F984"}}, Arguments: {startsWith: {Value: {Address: {is: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"}}}}} ) { Transaction { Hash } Block { Time } Log { Signature { Name } } Arguments { Name Type Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } } } } ``` 4. **Create a `index.ts` file:** a. **Doing the necessary imports** ```javascript import { BigNumber, ethers } from "ethers"; import { AlphaRouter, SwapType, SwapRoute, } from "@uniswap/smart-order-router"; import { CurrencyAmount, TradeType } from "@uniswap/sdk-core"; import type { TransactionRequest } from "@ethersproject/abstract-provider"; import { getTokens } from "./tokens"; import { provider, signer, CHAIN_ID, SWAP_ROUTER_ADDRESS, SLIPPAGE_TOLERANCE, DEADLINE, } from "./config"; ``` b. **Building the main function** All of the code covered under this section b is going to be in the main function. 1. Firstly it calls the `getTokens` function and fetches the Token0 and Token1 token contracts. And then set `tokenFrom` and `tokenTo` tokens and `tokenFromContract` to call functions on tokenFrom token. ```javascript // Wait for the getTokens function to resolve const { Token0, Token1 } = await getTokens(); // Ensure tokens are not null if (!Token0 || !Token1) { throw new Error("Tokens are not initialized."); } const tokenFrom = Token0.token; const tokenFromContract = Token0.contract; const tokenTo = Token1.token; ``` 2. Then we check if we have passed the argument in the terminal while running the bot. This means that if we have not passed the amount of WETH we want to swap with then throw error. Then we are checking if we have enough amount of the TokenFrom token or not. It must be grater than the passed argument in the terminal. ```javascript if (typeof process.argv[2] === "undefined") { throw new Error(`Pass in the amount of ${tokenFrom.symbol} to swap.`); } const walletAddress = await signer.getAddress(); const amountIn = ethers.utils.parseUnits( process.argv[2], tokenFrom.decimals ); const balance = await tokenFromContract.balanceOf(walletAddress); if (!(await Token0.walletHas(signer, amountIn))) { throw new Error( `Not enough ${tokenFrom.symbol}. Needs ${amountIn}, but balance is ${balance}.` ); } ``` 3. We are using `AlphaRouter` here from Uniswap to swap tokens on Uniswap efficiently. Then we use this router object to create a route which takes the specific details of our swap. If no route is found then it throws error. ```javascript const router = new AlphaRouter({ chainId: CHAIN_ID, provider }); const route = await router.route( CurrencyAmount.fromRawAmount(tokenFrom, amountIn.toString()), tokenTo, TradeType.EXACT_INPUT, { recipient: walletAddress, slippageTolerance: SLIPPAGE_TOLERANCE, deadline: DEADLINE, type: SwapType.SWAP_ROUTER_02, } ); if (!route) { throw new Error("No route found for the swap."); } console.log( `Swapping ${amountIn} ${tokenFrom.symbol} for ${route.quote.toFixed( tokenTo.decimals )} ${tokenTo.symbol}.` ); ``` 4. Then we check the allowance. We are just defining here `buildSwapTransaction` and then also using `swapTransaction` to populate the `buildSwapTransaction`. Then we have also defined `attemptSwapTransaction` which sends the transaction to the network. ```javascript const allowance: BigNumber = await tokenFromContract.allowance( walletAddress, SWAP_ROUTER_ADDRESS ); const buildSwapTransaction = ( walletAddress: string, routerAddress: string, route: SwapRoute ): TransactionRequest => { return { data: route.methodParameters?.calldata, to: routerAddress, value: BigNumber.from(route.methodParameters?.value), from: walletAddress, gasLimit: BigNumber.from("2000000"), // Set your desired gas limit here // Optionally, you can specify gasPrice here if needed // gasPrice: YOUR_GAS_PRICE_IN_WEI }; }; const swapTransaction = buildSwapTransaction( walletAddress, SWAP_ROUTER_ADDRESS, route ); const attemptSwapTransaction = async ( signer: ethers.Wallet, transaction: TransactionRequest ) => { const signerBalance = await signer.getBalance(); if (!signerBalance.gte(transaction.gasLimit || "0")) { throw new Error(`Not enough ETH to cover gas: ${transaction.gasLimit}`); } // Send the transaction with the specified gas-related parameters signer.sendTransaction(transaction).then((tx) => { tx.wait().then((receipt) => { console.log("Completed swap transaction:", receipt.transactionHash); }); }); }; ``` 5. Here we finally call the before defined functions. Firstly we check if there is enough WETH allowance. And if there is not then we send an approve transaction to the network with the `AmountIn` amount of WETH. Then we call the `attemptSwapTransaction` which des the actual swap. ```javascript if (allowance.lt(amountIn)) { console.log(`Requesting ${tokenFrom.symbol} approval…`); const approvalTx = await tokenFromContract .connect(signer) .approve( SWAP_ROUTER_ADDRESS, ethers.utils.parseUnits(amountIn.mul(1000).toString(), 18) ); approvalTx.wait(3).then(() => { attemptSwapTransaction(signer, swapTransaction); }); } else { console.log( `Sufficient ${tokenFrom.symbol} allowance, no need for approval.` ); attemptSwapTransaction(signer, swapTransaction); } ``` c. **Calling the main function with some error handling** ```javascript main().catch((error) => { console.error(error); process.exit(1); }); ``` ### Step 3: Running the Bot 1. **Check the .env:** - Make sure that you have replace `PRIVATE_KEY` with your actual Arbitrum account public key. - Make sure that you have replace `BITQUERY_TOKEN` with your actual Bitquery OAuth token. 2. **Run the bot:** 0.001 in the below script is the amount of WETH that I want to use for the swap. ```bash ts-node index.ts 0.001 ``` ### Conclusion You've successfully set up a Arbitrum sniper bot using Bitquery for Arbitrum Events API and Uniswap SDK for executing swaps. You need to change the query in tokens.ts file into subscription if you want to use it to listen for on-chain events and then buy the token B from each new pool that gets created on Uniswap. But you will need to make some necessary changes before that. This tutorial just shows you how you can get the recently created pool on uniswap and which token B it has as token A we have already set as WETH in the query and swap a token in that pool. Ensure your bot is monitored and managed appropriately, as we are running on the mainnet with real funds. --- ## Argmax Selectors: maximum and minimum URL: https://docs.bitquery.io/docs/trading/query-operators/argmax-selectors/ Field(maximum: Key) returns a field's value on the row where Key is maximal — the argmax primitive behind first-value, last-value and peak queries on the Bitquery Trading cube. # Argmax Selectors: `maximum:` and `minimum:` `Field(maximum: Key)` returns **the value of `Field` on the row where `Key` is maximal**. It compiles to ClickHouse `argMax(Field, Key)`; `minimum:` is `argMin`. This is the primitive behind almost every "first", "last" and "at the peak" question: ```graphql Price { Ohlc { Open(minimum: Interval_Time_Start) # first value in the window Close(maximum: Interval_Time_Start) # last value in the window } } ``` :::caution It returns the field, not the key `Number(maximum: Block_GasUsed)` returns the block **number** of the highest-gas block — not the gas figure. Reading it as "the maximum of the argument" is the single most common mistake. ::: ## Where it works Every **non-aggregate leaf path** in a Trading cube accepts both `maximum:` and `minimum:`. Only the 23 aggregate functions (`sum`, `count`, `uniq`, `median`, `calculate`, `correlation`, `gini`, …) do not. That gives a large surface on every cube — on the order of 30 to 60 paths each, across `Trading.Trades`, `Trading.Pairs`, `Trading.Tokens` and `Trading.Currencies`. **You can argmax *into* a string, but never *by* one.** The key must be a comparable column, so `side`-style string columns are rejected at GraphQL validation rather than failing at runtime. `Block_Date` and `Block_Timestamp` are string-typed but comparable, so they *are* valid keys. ```graphql # valid — the value is a string, the key is numeric TransactionHeader { Hash(maximum: Amounts_Base) } ``` `calculate` cannot substitute here: its column is typed `Float64`, so `calculate(expression: "argMax(TransactionHeader_Hash, Amounts_Base)")` fails. Use the field-level selector. ## Row coherence — the rule that makes this useful **All selectors sharing one key resolve to the same underlying row.** So this returns a genuine, internally consistent snapshot of the largest trade: ```graphql Trader { Address(maximum: AmountsInUsd_Quote) } Pair { Pool { Address(maximum: AmountsInUsd_Quote) } } TransactionHeader { Hash(maximum: AmountsInUsd_Quote) } ``` :::danger Mixing keys silently fabricates a row Selectors keyed on **different** columns come from **different rows**. Combining them produces a composite that never existed on chain — no error, no warning: ```graphql # these two are from different rows Open(minimum: Block_Time) Trader { Address(maximum: AmountsInUsd_Quote) } ``` That is fine when you intend it (an OHLC bar is built exactly this way). It is a bug when you meant "one row". ::: ## Ties are resolved non-deterministically When several rows tie on the key, the winner varies between identical runs — even over a frozen window. The choice is made **once per response**, so fields sharing that key stay coherent with each other; it is *which* tied row you get that moves. `Block_Time` is second-resolution and ties readily on busy pools. Break ties with a second, finer key — `Block_Timestamp` is nanosecond-resolution — or accept that any tied row is representative. ## Making it per-group Argmax becomes per-group when you **select a dimension**, which introduces the GROUP BY: ```graphql Token { Id } # <- this makes it per-token peak: Price { Ohlc { High(maximum: Price_Ohlc_High) } } ``` `limitBy` is orthogonal — it caps how many result rows survive per key and composes fine with argmax, but it is **not** what makes the argmax per-group. Select `limitBy` with no dimension and you get one global argmax. ## Combining with other arguments - **`if:`** turns the selector into `argMaxIf`, and takes the full cube filter type — any `where`-shape the cube accepts. - **`selectWhere:`** applies HAVING semantics to the argmax result: groups whose result fails the predicate are dropped **before** ordering and limiting, so lower-ranked groups get pulled into the limit window. The predicate is `Metric_filter_Float` for numeric paths and `Metric_filter_String` for string paths. ## Two footguns :::warning `maximum:` and `minimum:` on one field — only the first applies Supplying both on a single field occurrence is **silently accepted**, and only the first-listed argument takes effect. The other is dropped with no error. To get both ends of a range, use two aliased selections: ```graphql first: Price { Ohlc { Open(minimum: Block_Time) } } last: Price { Ohlc { Close(maximum: Block_Time) } } ``` ::: :::warning An empty set returns type-zeros, not an empty result If an `if:` filter matches nothing, the cube still emits a row and the selector returns a type-zero — `0`, or `""`. That is **indistinguishable from a genuine zero**. Pair the selector with `count(if: )` to tell the two apart. ::: ## Related - [Calculations and Expressions](/docs/graphql/calculations/) - [Conditions with `if`](/docs/graphql/metrics/if/) - [Screeners with `selectWhere`](/docs/trading/query-operators/selectwhere-screeners/) - [OHLC candles](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/) --- ## AsterDEX API Documentation URL: https://docs.bitquery.io/docs/examples/futures-dexs/asterdex-api/ AsterDEX perpetual futures on BNB Chain: order lifecycle, core contract events, market trades, recent liquidations, and full details of a trade. # AsterDEX API Documentation - Complete Guide to BNB Smart Chain Perpetual Futures Trading :::info Perp DEX data on other chains This page covers AsterDEX on BNB Smart Chain via decoded contract events. For dedicated perpetual-futures cubes — orders, fills, positions, PnL, liquidations, funding and open interest — see the [**Perp DEX API**](/docs/perpetuals/) section. ::: ## Quick Start Guide ### Prerequisites for AsterDEX API Integration Before integrating AsterDEX APIs, ensure you have: 1. **Bitquery Account**: Sign up at [ide.bitquery.io](https://ide.bitquery.io/) for API access 2. **GraphQL Knowledge**: Basic understanding of GraphQL query syntax 3. **BNB Smart Chain Familiarity**: Knowledge of BSC addresses and transactions 4. **Development Environment**: Any programming language with HTTP request capabilities ### 5-Minute Setup ```javascript // Example: Fetch latest AsterDEX trades const query = ` { EVM(dataset: realtime, network: bsc) { Events( where: {LogHeader: {Address: {is: "0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0"}}} orderBy: {descending: Block_Time} limit: {count: 10} ) { Block { Time } Transaction { Hash } Log { Signature { Name } } } } } `; fetch("https://streaming.bitquery.io/graphql", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer YOUR_API_KEY", }, body: JSON.stringify({ query }), }); ``` ## Smart Contract Information | Contract Type | Address | Network | Purpose | | -------------------------- | ------------------------------------------------------------------------------------- | --------------------- | -------------------------------------- | | **AsterDEX Main Contract** | `0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0` | BNB Smart Chain (BSC) | Core perpetual futures trading logic | | **Network ID** | `56` | BNB Smart Chain | Main BSC network identifier | | **Block Explorer** | [bscscan.com](https://bscscan.com/address/0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0) | BSC Explorer | Contract verification and transactions | --- ## AsterDEX Order Lifecycle The AsterDEX Order Lifecycle is defined by the sequence of smart contract events emitted during the creation, execution, modification, and closing of trades on the AsterDEX protocol deployed on BNB Smart Chain (BSC). Using the ABI, we can trace the major steps of a trade as follows: ### 1. Order Creation A trader begins by opening a position using one of the following contract calls: - `openMarketTrade` or `openMarketTradeBNB` — for immediate market orders - `openLimitOrder` or `openLimitOrderBNB` — for pending limit orders These functions emit events like: - `OpenMarketTrade(user, tradeHash, trade)` — signals a new market trade - `OpenLimitOrder(user, orderHash, data)` — logs the creation of a limit order These events include key parameters: - **Pair base** (trading pair symbol) - **isLong flag** (direction) - **tokenIn, amountIn** (input asset and margin) - **qty** (position size) - **price, stopLoss, takeProfit** - **broker** (affiliate ID) ### 2. Trade Pending / Validation After the trade is created, AsterDEX emits `MarketPendingTrade` or `PredictAndBetPending` events to indicate the trade or bet is awaiting price validation or oracle callback. For limit orders, this pending state continues until market conditions are met. ### 3. Trade Execution When the trade conditions are satisfied (for market or limit execution), the contract emits: - `ExecuteLimitOrderSuccessful(user, orderHash)` — for executed limit orders - `ExecuteLimitOrderRejected(user, orderHash, refund)` — if conditions are invalid - `MarketTradeCallback` — finalizes market trade once the external price feed confirms execution Once executed, the trader's position is stored, and Bitquery indexes these on-chain records as open trades. ### 4. Margin Update (Optional) Traders can adjust their position margin using the `addMargin` or `updateMargin` functions, which emit: - `UpdateMargin(user, tradeHash, beforeMargin, margin)` — updates collateral These events help Bitquery track capital movement within an active position. ### 5. Stop-Loss / Take-Profit / Liquidation During the trade's lifecycle, risk management parameters can trigger automatically. The following events define these actions: - `UpdateTradeSl` / `UpdateTradeTp` / `UpdateTradeTpAndSl` — manual stop-loss or take-profit updates - `ExecuteTpSlOrLiq` — executed close due to stop-loss, take-profit, or liquidation condition - `ExecuteCloseRejected` — if the closing execution fails or is invalid These events are captured by Bitquery and can be used to determine close reason and realized PnL. ### 6. Trade Closing A trade is closed manually or automatically, resulting in: - `CloseTradeSuccessful(user, tradeHash, closeInfo)` — main closing event, includes closePrice, fundingFee, closeFee, pnl, holdingFee - `CloseTradeReceived` — indicates funds have been distributed to the trader - `CloseTradeAddLiquidity` / `CloseTradeRemoveLiquidity` — if closing affects liquidity pools Bitquery records these closing events, linking them with the original tradeHash and calculating total realized profit or loss. ### 7. Settlement and Fee Distribution When a trade closes, associated fees are emitted as separate events: - `OpenFee` / `CloseFee` — show DAO, broker, and pool fee splits - `FundingFeeAddLiquidity` — transfers funding fees into liquidity pools - `WithdrawRevenue` — shows DAO or broker withdrawals of earned fees These events help reconstruct the entire economic flow of a trade — from initiation to settlement. ### 8. Post-Trade State Updates Finally, market-level updates are emitted periodically: - `UpdatePairAccFundingFeePerShare` — funding rate updates - `UpdatePairPositionInfo` — updates aggregate long/short positions for a pair These allow Bitquery to display aggregate market metrics, such as open interest, funding rates, and average entry prices. ## Summary of Core Events | Stage | Key Events | Description | | ------------------ | ----------------------------------------------------------- | ------------------------------- | | **Order Open** | `OpenMarketTrade`, `OpenLimitOrder` | Creation of new orders | | **Pending** | `MarketPendingTrade`, `PredictAndBetPending` | Waiting for oracle / validation | | **Execution** | `ExecuteLimitOrderSuccessful`, `MarketTradeCallback` | Order filled | | **Margin Update** | `UpdateMargin` | Margin increased or decreased | | **Risk Triggers** | `ExecuteTpSlOrLiq`, `UpdateTradeSl`, `UpdateTradeTp` | Stop-loss/TP triggered | | **Close** | `CloseTradeSuccessful`, `CloseTradeReceived` | Trade settled | | **Fees** | `OpenFee`, `CloseFee`, `FundingFeeAddLiquidity` | Fee settlements | | **Market Updates** | `UpdatePairPositionInfo`, `UpdatePairAccFundingFeePerShare` | Aggregate pair data refresh | Bitquery indexes all of these events in real-time, linking them to transactions, blocks, and traders—enabling complete lifecycle tracking of each AsterDEX trade via API or stream. --- ## AsterDEX API Examples ### 1. All Events of AsterDEX Monitor all events emitted by the AsterDEX contract to track all platform activities. **Query Link**: [All events of AsterDEX](https://ide.bitquery.io/All-events-of-AsterDEX) ```graphql { EVM(dataset: realtime, network: bsc) { Events( limit: { count: 20 } where: { LogHeader: { Address: { is: "0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0" } } } ) { count Log { Signature { Name Signature } } } } } ``` ### 2. OpenMarketTrade Events `OpenMarketTrade` is when a user opens a market trade. The following API provides all newly opened market trades on AsterDEX. **Query Link**: [AsterDEX - OpenMarketTrade](https://ide.bitquery.io/AsterDEX---OpenMarketTrade) Similarly, we can get all newly created Limit Orders by following the `OpenLimitOrder` event. #### Difference between OpenLimitOrder and OpenMarketTrade | Feature | OpenLimitOrder | OpenMarketTrade | | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | **Purpose** | Creates a limit order — an instruction to open a trade only when price conditions are met. | Opens a market trade immediately at the current market price. | | **Function Call** | `openLimitOrder()` or `openLimitOrderBNB()` | `openMarketTrade()` or `openMarketTradeBNB()` | | **Emitted Event** | `OpenLimitOrder(address user, bytes32 orderHash, IBook.OpenDataInput data)` | `OpenMarketTrade(address user, bytes32 tradeHash, IBook.OpenDataInput trade)` | | **Execution Timing** | Delayed – waits for trigger (price crossing limit, oracle validation). | Immediate – executes as soon as transaction is confirmed. | | **Order State After Creation** | Becomes pending — monitored by AsterDEX Keeper or Chainlink callback. | Becomes active trade instantly. | | **Follow-up Events** | • `ExecuteLimitOrderSuccessful` (when executed)
• `ExecuteLimitOrderRejected` (if canceled/invalid)
• `CancelLimitOrder` (if user cancels) | • `MarketPendingTrade` (waiting oracle)
• `MarketTradeCallback` (execution confirmation)
• `PendingTradeRefund` (if failed) | | **Event Parameters (data struct)** | pairBase, isLong, tokenIn, amountIn, qty, price, stopLoss, takeProfit, broker | Same struct, but price acts as execution price instead of limit trigger. | ### 3. All Recent Liquidations on AsterDEX When there is a liquidation event on AsterDEX, it emits `ExecuteCloseSuccessful` event with `executionType` 2. **Query Link**: [AsterDEX - All latest Liquidations](https://ide.bitquery.io/AsterDEX---All-latest-Liquidations) #### ExecutionTypes Explained There are 4 executionTypes: - **0 – TakeProfit (TP)** → closed because TP hit (handled by `executeTpSlOrLiq`) - **1 – StopLoss (SL)** → closed because SL hit (your sample shows `executionType = 1` with negative PnL, consistent with SL) - **2 – Liquidation (LIQ)** → closed by liquidation (also through `executeTpSlOrLiq`) - **3 – Manual/Market close** → user (or UI) explicitly requested a close via `closeTrade(...)`; still emits `ExecuteCloseSuccessful` with this code. (This path does not go through `executeTpSlOrLiq` but the event includes the same enum.) ### 4. Getting All the Details of a Trade In AsterDEX every trade has an Order Hash, using which you can track the whole lifecycle of that trade. For example, for this OrderHash `5d5a2a37ef2afff8ce95101930507af4a255e271f5201658ea2660bc3baf6605` You can check all related events using the following query. This query takes time as we are querying arguments which are not indexed inside our database. You can use any latest trade to make it fast. **Query Link**: [All details of trade - 5d5a2a37ef2afff8ce95101930507af4a255e271f5201658ea2660bc3baf6605](https://ide.bitquery.io/All-details-of-trade---5d5a2a37ef2afff8ce95101930507af4a255e271f5201658ea2660bc3baf6605_7) #### Example Trade Lifecycle Here are all the emitted events for this trade and related transactions: 1. **OpenLimitOrder** - `0x67b7fe335e05599ae6b316b348c23055e152282638bd4e9e6219283a46789613` 2. **UpdateOrderSl** - `0x1b22bb4787efb3c40aca9093aedd70a59a9767ad7c3f3b19a29048f6514ca5dc` 3. **OpenMarketTrade** - `0x0b318a469ebc6f648d0c3a7e95705aa72c807f9248b21b451963970936ee48a0` 4. **ExecuteLimitOrderSuccessful** - `0x0b318a469ebc6f648d0c3a7e95705aa72c807f9248b21b451963970936ee48a0` 5. **UpdateTradeSl** - `0x6cb3d98e95f78e3708665c35481c04a96080b0ce4a3afd2b53cf4a49c164209d` 6. **CloseTradeReceived** - `0x2e24c951abbfb2cdea92b4f7157f52727b21761bc26368614a40a3d86137ba2e` 7. **ExecuteCloseSuccessful** - `0x2e24c951abbfb2cdea92b4f7157f52727b21761bc26368614a40a3d86137ba2e` Similarly, you can query any event using our events API. You can check this API to see the complete list of events: [All events of AsterDEX](https://ide.bitquery.io/All-events-of-AsterDEX) ### 5. Following a Specific AsterDEX Trader Using Bitquery's APIs you can follow specific traders on AsterDEX to check all their latest activities. **Query Link**: [Traders data - 0x2b7363708984aa25a90450cfca7bedaf6804115c](https://ide.bitquery.io/Traders-data---0x2b7363708984aa25a90450cfca7bedaf6804115c) This is a very interesting address (`0x2b7363708984aa25a90450cfca7bedaf6804115c`), it looks like it's market making on AsterDEX. You can look for `Transaction -> From` or in some cases the address might be in arguments, for example: [Traders specific event](https://ide.bitquery.io/Traders-specific-event) You can actually merge these two queries. Here is an example: [Combined Traders data - 0x01554d63537d3c62715826a268d4eab645d64b92](https://ide.bitquery.io/Copy-of-Traders-data---0x01554d63537d3c62715826a268d4eab645d64b92) --- ## Complete List of AsterDEX Events Here is the complete list of all events emitted by the AsterDEX smart contract with their signatures: | Event Name | Signature | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PendingPredictionRefund** | `PendingPredictionRefund(address,uint256,uint8)` | | **MarketPendingTrade** | `MarketPendingTrade(address,bytes32,(address,bool,address,uint96,uint80,uint64,uint64,uint64,uint24))` | | **OpenFee** | `OpenFee(address,uint256,uint256,uint24,uint256,uint256)` | | **CloseTradeAddLiquidity** | `CloseTradeAddLiquidity(address,uint256)` | | **ExecuteLimitOrderSuccessful** | `ExecuteLimitOrderSuccessful(address,bytes32)` | | **CloseTradeReceived** | `CloseTradeReceived(address,bytes32,address,uint256)` | | **UnStake** | `UnStake(address,uint256)` | | **BurnFee** | `BurnFee(address,address,uint256,uint256,uint256,uint256)` | | **FundingFeeAddLiquidity** | `FundingFeeAddLiquidity(address,uint256)` | | **OpenLimitOrder** | `OpenLimitOrder(address,bytes32,(address,bool,address,uint96,uint80,uint64,uint64,uint64,uint24))` | | **CloseTradeRemoveLiquidity** | `CloseTradeRemoveLiquidity(address,uint256)` | | **SettlePredictionSuccessful** | `SettlePredictionSuccessful(uint256,bool,uint256,address,uint256,uint256)` | | **MintAlp** | `MintAlp(address,address,uint256,uint256)` | | **ExecuteCloseRejected** | `ExecuteCloseRejected(address,bytes32,uint8,uint64,uint64)` | | **UpdatePairAccFundingFeePerShare** | `UpdatePairAccFundingFeePerShare(address,uint256,int256,uint256)` | | **UpdateOrderSl** | `UpdateOrderSl(address,bytes32,uint256,uint256)` | | **CloseFee** | `CloseFee(address,uint256,uint256,uint24,uint256,uint256)` | | **CloseTradeSuccessful** | `CloseTradeSuccessful(address,bytes32,(uint64,int96,uint96,int96,uint96))` | | **UpdateMargin** | `UpdateMargin(address,bytes32,uint256,uint256)` | | **MintFee** | `MintFee(address,address,uint256,uint256,uint256,uint256)` | | **PredictAndBet** | `PredictAndBet(address,uint256,(address,uint96,address,uint96,address,uint96,uint32,uint64,uint40,uint24,bool,uint8))` | | **BurnRemoveLiquidity** | `BurnRemoveLiquidity(address,address,uint256)` | | **UpdateSlippageConfig** | `UpdateSlippageConfig(uint16,uint8,uint256,uint256,uint16,uint16,uint256,uint256)` | | **PendingTradeRefund** | `PendingTradeRefund(address,bytes32,uint8)` | | **PredictionCloseFee** | `PredictionCloseFee(address,uint256,uint256,uint24,uint256,uint256)` | | **PredictAndBetPending** | `PredictAndBetPending(address,uint256,(address,uint96,address,uint96,address,uint64,uint24,bool,uint128,uint8))` | | **UpdatePairPositionInfo** | `UpdatePairPositionInfo(address,uint256,uint256,uint256,int256,uint64,uint64)` | | **OpenMarketTrade** | `OpenMarketTrade(address,bytes32,(address,uint32,uint64,address,address,uint96,uint64,uint64,uint24,bool,uint96,int256,uint96,uint40,uint80,uint40,uint256))` | | **ExecuteCloseSuccessful** | `ExecuteCloseSuccessful(address,bytes32,uint8,(uint64,int96,uint96,int96,uint96))` | | **CancelLimitOrder** | `CancelLimitOrder(address,bytes32)` | | **UpdateTradeTp** | `UpdateTradeTp(address,bytes32,uint256,uint256)` | | **UpdateTradeSl** | `UpdateTradeSl(address,bytes32,uint256,uint256)` | | **MintAddLiquidity** | `MintAddLiquidity(address,address,uint256)` | | **BurnAlp** | `BurnAlp(address,address,address,uint256,uint256)` | --- ## Developer Integration Guide ### Getting Started with AsterDEX APIs 1. **Access Bitquery GraphQL IDE**: All AsterDEX APIs are accessible through [Bitquery's GraphQL IDE](https://ide.bitquery.io/) 2. **Use the Contract Address**: The main AsterDEX contract address is `0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0` on BNB Smart Chain 3. **Choose Your Network**: Always specify `network: bsc` for BNB Smart Chain queries 4. **Filter by Events**: Use the event signatures from the complete list above to track specific trading activities ### Common Use Cases **Trading Dashboard**: - Track `OpenMarketTrade` and `OpenLimitOrder` for new positions - Monitor `CloseTradeSuccessful` for position closures - Display `UpdateMargin` events for margin changes **Risk Management**: - Monitor `ExecuteCloseSuccessful` with `executionType: 2` for liquidations - Track `UpdateTradeSl` and `UpdateTradeTp` for stop-loss and take-profit updates - Alert on funding rate changes via `UpdatePairAccFundingFeePerShare` **Analytics Platform**: - Aggregate trading volumes from all position opening/closing events - Analyze trader behavior using specific trader addresses - Calculate platform fees from `OpenFee` and `CloseFee` events **Arbitrage Monitoring**: - Watch for price discrepancies in `MarketPendingTrade` events - Monitor funding rates for cross-platform opportunities - Track liquidation events for potential arbitrage ## Best Practices for AsterDEX API Integration ### Performance Optimization **Efficient Query Design:** - Use specific event names instead of querying all events - Implement proper pagination for large datasets - Cache frequently accessed data to reduce API calls - Use GraphQL field selection to minimize response size **Real-time Data Handling:** ```javascript // Optimized query for high-frequency trading applications const optimizedQuery = `{ EVM(dataset: realtime, network: bsc) { Events( where: { Log: {Signature: {Name: {in: ["OpenMarketTrade", "ExecuteCloseSuccessful"]}}} LogHeader: {Address: {is: "0x1b6F2d3844C6ae7D56ceb3C3643b9060ba28FEb0"}} } orderBy: {descending: Block_Time} limit: {count: 50} ) { Block { Time Number } Arguments { Name Value } } } }`; ``` ### Security Considerations **API Key Management:** - Store API keys securely using environment variables - Implement API key rotation for production applications - Use read-only permissions when possible - Monitor API usage and set up alerts for unusual activity **Data Validation:** - Always validate contract addresses and event signatures - Implement checksum verification for address fields - Cross-reference critical data with multiple sources - Set up monitoring for data anomalies ### Error Handling and Reliability **Robust Integration Pattern:** ```javascript async function fetchAsterDEXData(retries = 3) { try { const response = await fetch(BITQUERY_ENDPOINT, { method: "POST", headers: { Authorization: `Bearer ${API_KEY}` }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { if (retries > 0) { console.log(`Retrying... ${retries} attempts remaining`); await new Promise((resolve) => setTimeout(resolve, 1000)); return fetchAsterDEXData(retries - 1); } throw error; } } ``` ### Scaling Your Application **Production Deployment:** - Implement connection pooling for high-throughput applications - Use caching layers (Redis/Memcached) for frequently accessed data - Set up monitoring and alerting for API performance - Plan for rate limit management and backoff strategies **Data Processing Pipeline:** - Process events in chronological order for accurate state tracking - Implement event deduplication for reliability - Use event sourcing patterns for complex trading logic - Set up data backup and recovery procedures ### Additional Resources - [Bitquery Documentation](https://docs.bitquery.io/) - [GraphQL Query Guide](/docs/graphql/query/) - [BNB Smart Chain Explorer](https://bscscan.com/) - [Production Deployment Guide](/docs/start/getting-updates/) - [API Rate Limits](/docs/graphql/limits/) ## What You Can Build with AsterDEX API Integration With Bitquery's AsterDEX integration, developers can build: - **Query all trades, swaps, and liquidity pool activity** on AsterDEX - **Monitor trader, token pairs, and pool performance** in real-time - **Stream real‑time on‑chain events** (swaps, stop loss & take profit updates, liquidations) as they occur ## Related Resources ### Bitquery Platform - [Bitquery GraphQL IDE](https://ide.bitquery.io/) - [Bitquery Documentation](https://docs.bitquery.io/) - [BNB Smart Chain APIs](/docs/blockchain/BSC/) ### Support For technical support and questions: - **Bitquery Support**: [Telegram Community](https://t.me/bloxy_info) --- _This comprehensive AsterDEX API documentation provides complete coverage of perpetual futures trading data on BNB Smart Chain. From order lifecycle tracking to liquidation monitoring, developers have access to all the tools needed to build sophisticated DeFi trading applications. All API examples include real contract addresses and working GraphQL queries for immediate implementation._ --- ## Automated Trading Ethereum Volume Surge Bot URL: https://docs.bitquery.io/docs/usecases/automated-trading-ethereum-volume-surge-bot/ Build Automated Trading Ethereum Volume Surge Bot: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Automated Trading on Ethereum: How To Build a Volume Surge Detection Bot In this tutorial, we will build a Python bot that monitors token trading volume on Ethereum and automatically executes buy orders when volume surges by 10% or more. For real-time price data with volume metrics, use our [Crypto Price API](/docs/trading/crypto-price-api/). We’ll leverage the Bitquery APIs with Python to monitor activities and automatically execute trades in real-time. We’ll explore how to access and use Bitquery APIs to fetch real-time trading volume data, calculate volume changes, and perform automated transactions on the Ethereum Sepolia testnet. > Note: This material is for educational and informational purposes only and is not intended as investment advice. The content reflects the author's personal research and understanding. While specific investments and strategies are mentioned, no endorsement or association with these entities is implied. Readers should conduct their own research and consult with qualified professionals before making any investment decisions. Bitquery is not liable for any losses or damages resulting from the application of this information. ## Why Bitquery? [Bitquery](https://bitquery.io) is a blockchain data provider that offers suites of tools and APIs to access blockchain data easily. It enables developers, analysts, and researchers to query and retrieve historical and real-time data from over 40 blockchains and protocols with GraphQL. ## Building a Simple Volume Surge Detection MEV Bot in Python In this section, we’ll build an MEV bot that detects a surge in trade volume for a specific token and automatically executes a buy order when all conditions are met. This tutorial will guide you through creating a Python script that detects a surge in trading volume for a specific token and executes a buy order on the Ethereum Sepolia testnet. We'll use the Bitquery API to fetch trading volume data and Web3.py to interact with the Ethereum blockchain. This bot uses the [Bitquery API](/docs/intro/) to fetch real-time trading data and Web3.py to interact with the Ethereum blockchain, demonstrating a practical application of Python in blockchain trading automation using the Bitquery API. - Here is the [link](https://github.com/bitquery/volume-surge-trading-bot/tree/main) to the GitHub Repository for the volume surge detection MEV bot - Here is also a [step-by-step video tutorial](https://www.youtube.com/watch?v=2sK-dtYF2-k) for the project. Here is the step-by-step tutorial on how to build this MEV bot. ### Setting Up Your Programming Environment To build this MEV Bot, set up your programming environment by installing Python, downloading the necessary libraries (requests, web3 os), and setting up Infura and Bitquery APIs. Follow the steps below to set up your environment and the APIs you need for the project. 1. By running the query below, you can download the libraries needed for the project without any issues. ```bash pip install requests web3 os ``` The requests library is used for making Https requests in python while the web3 library is used for interacting with the Ethereum blockchain network. And the os library lets you use the operating system dependent functionality in python. It allows you to interface with the underlying system in several ways like creating, removing, and manipulating directories and libraries. 2. Once you’ve set up your python environment and download the necessary library for the project as we’ve done in the previous step, you need to set up the API keys needed to retrieve the data from Bitquery. - Follow [this link](/docs/authorization/how-to-generate/) to learn how to generate the tokens either programmatically or manually to use for this project. - Visit the alchemy website, and follow the necessary instructions to generate the URL needed to connect with your Sepolia Testnet through the Infura environment. ### Step1: Importing Required Libraries Once you’ve set up your programming environment, you have to import all the necessary libraries needed to build the MEV bot. These includes the libraries for fetching web pages or API data (requests), connecting to Ethereum nodes (web3), creating directories and files (OS), and manipulating time (datetime and time module) ```python from datetime import datetime, timedelta, timezone from web3 import Web3 ``` ### Step 2: Script Configurations Here, you need to configure your scripts by setting up all the necessary variables and API keys. As seen in the query below, you set up the: - BITQUERY_AUTH_TOKEN: generated from Bitquery. Learn how to generate an API token here - TOKEN_ADDRESS: is the address of the token you want the MEV bot to track - TESTNET_URL: URL gotten from alchemy needed to connect to the Ethereum Sepolia testnet - PRIVATE_KEY: is the private key of the wallet address where the transaction will be executed - ADDRESS: is your the wallet address you’ll be using - VOLUME_SURGE_THRESHOLD: is the volume percentage that triggers the MEV bot to execute a trade - TIME_WINDOW_MINUTES: is the time-frequency to check the data for executing a trade ```python # Configuration BITQUERY_AUTH_TOKEN = "ory_at_" TOKEN_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' # Address of the token to track TESTNET_URL = 'https://eth-sepolia.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY' # Sepolia testnet URL PRIVATE_KEY = 'YOUR_PRIVATE_KEY' # Never commit a real private key ADDRESS = 'YOUR_WALLET_ADDRESS' # Address from which transactions will originate VOLUME_SURGE_THRESHOLD = 0.1 # Threshold for volume surge detection (0.1% increase) TIME_WINDOW_MINUTES = 60 # Time window in minutes for historical data fetching ``` ### Step 3: Setting Up the Predefined Time Range The script sets up and prints the start (PREDEFINED_SINCE_DATE) and end (PREDEFINED_TILL_DATE) timestamps for a historical data query. These timestamps are in ISO 8601 format and represent the range from the current time minus a predefined number of minutes (specified by TIME_WINDOW_MINUTES) to the current time. This time range can then be used to fetch historical data from a database or an API, ensuring that the data falls within the specified period. ```python # Predefined time range for historical data query PREDEFINED_SINCE_DATE = (datetime.now(timezone.utc) - timedelta(minutes=TIME_WINDOW_MINUTES)).strftime("%Y-%m-%dT%H:%M:%SZ") PREDEFINED_TILL_DATE = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") print(f"PREDEFINED_SINCE_DATE: {PREDEFINED_SINCE_DATE}") print(f"PREDEFINED_TILL_DATE: {PREDEFINED_TILL_DATE}") ``` ### Step 4: Fetch the Historical Volume Data At this step, you create the fetch_volume_data function to fetch historical trading volume data from Bitquery. The fetch_volume_data function below takes the token_address parameter and uses the [DEXTradeByTokens API](/docs/schema/evm/dextrades/) to retrieve the count of trade, sell, and buy amounts from the blockchain using the variables you set up in Step2 (Script configuration) above. ```python # Function to fetch historical volume data from Bitquery def fetch_volume_data(token_address): print("Fetching volume data...") query = """ { EVM(dataset: realtime, network: eth) { DEXTradeByTokens( where: {Trade: {Currency: {SmartContract: {is: "%s"}}, Side: {Currency: {SmartContract: {is: "0xdac17f958d2ee523a2206206994597c13d831ec7"}}}}, Block: {Time: {since: "%s", till: "%s"}}} ) { buy: sum(of: Trade_AmountInUSD) sell: sum(of: Trade_Side_AmountInUSD) count } } } """ % (token_address, PREDEFINED_SINCE_DATE, PREDEFINED_TILL_DATE) ``` The function also send the POST request, which includes an authorization header to the Bitquery API. ```python print("Querying Bitquery API...") try: response = requests.post( 'https://streaming.bitquery.io/graphql', json={'query': query}, headers={'Authorization': f'Bearer {BITQUERY_AUTH_TOKEN}'} ) if response.status_code == 200: print("Data successfully fetched.") data = response.json() trades = data['data']['EVM']['DEXTradeByTokens'] if not trades: print("No trades found.") else: print("Fetched trades:") for trade in trades: print(f" Buy Volume: {trade['buy']} USD, Sell Volume: {trade['sell']} USD") return trades else: raise Exception(f"Failed to fetch data: {response.text}") except Exception as e: print(f"Error in fetch_volume_data: {str(e)}") return None ``` ### Step 5: Calculate the Total Volume Traded Create a get_volume() function to calculate the total volume traded for the specified token. This function calculates the total trading volume for the specified token by summing up the buy and sell volumes retrieved from the Bitquery API. If there are no trades or an error occurs, it returns 0. ```python # Function to calculate total volume traded for the token def get_volume(): try: print("Calculating token volume...") trades = fetch_volume_data(TOKEN_ADDRESS) if trades is None: return 0 # No trades, so volume is 0 total_volume = sum(float(trade['buy']) + float(trade['sell']) for trade in trades) print(f"Total volume: {total_volume}") return total_volume except Exception as e: print(f"Error in get_volume: {str(e)}") return 0 # Return 0 volume on error ``` ### Step 6: Check for Volume Surge In this step, you’ll create the check_volume_surge function to check if there is a surge in trading volume. The function determines if there is a significant surge in the trading volume of the specified token by comparing the initial volume to the current volume. If the increase in volume exceeds a predefined threshold, it indicates a volume surge. In the case of this demo, if the increase in volume is equal to or greater than the VOLUME_SURGE_THRESHOLD (which in this case is 0.1), it indicates the volume surge or otherwise returns FALSE. ```python # Function to check if there is a volume surge def check_volume_surge(initial_volume, current_volume): print("Checking volume surge condition...") if initial_volume > 0: increase_percentage = ((current_volume - initial_volume) / initial_volume) * 100 print(f"For Time: {PREDEFINED_SINCE_DATE}" + " the " + f"increase percentage is: {increase_percentage}%") return increase_percentage >= VOLUME_SURGE_THRESHOLD return False ``` ### Step 7: Execute a Buy Order This execute_buy_order function is designed to execute a buy order for the token address specified as the parameter on the Ethereum Sepolia test network. It creates and executes a transaction using the provided Ethereum address, Private Key, and Token address. 1. Initialize Web3 and Fetch the Transaction Nonce The function initializes web3 by creating an instance of Web3 connected to the Ethereum Sepolia test network using the testnet URL you provided above. It also retrieves the transaction nonce for the wallet address the transaction will be executed. ```python # Function to execute a buy order on the testnet def execute_buy_order(token_address): try: print("Executing buy order...") web3 = Web3(Web3.HTTPProvider(TESTNET_URL)) nonce = web3.eth.get_transaction_count(ADDRESS) ``` 2. Define the Transaction Parameter This block of code defines the parameter needed to execute the transactions. For the case of this demo, the transaction value, 0.1 ETH was converted to wei (the smallest unit of ETH). The gas price of 50gwei was converted to wei. A transaction dictionary, which contains parameters to execute the transactions : - the nonce of the originating address (nonce), - the smart contract address of the token you want to buy(to), - The amount of ETH to send(value), the gas limit for the transaction(gas), - and the gas price for the transaction (gasPrice). ``` value = Web3.to_wei(0.1, 'ether') gas_price = Web3.to_wei('50', 'gwei') # Convert 50 Gwei to Wei transaction = { 'nonce': nonce, 'to': token_address, 'value': value, 'gas': 2000000, 'gasPrice': gas_price } ``` 3. Sign and Send the Transaction This section of the execute_buy_order function signs the transaction with the private key of the originating address. In the case of this demo, the private key you provided in the script configuration step above. The transaction signed above with the private key will be sent to the Ethereum Sepolia Testnet network. The transaction hash for the executed transaction will be printed and returned as a hexadecimal string for confirmation. ```python signed_tx = web3.eth.account.sign_transaction(transaction, PRIVATE_KEY) tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction) print(f"Transaction sent. Hash: {web3.toHex(tx_hash)}") return web3.toHex(tx_hash) except Exception as e: print(f"Error in execute_buy_order: {str(e)}") return None ``` ### Step 8: Setting Up the Main Function to Run the Bot The main function code snippet below is the entry point for running the Volume surge detection MEV bot. This bot continuously monitors the trading volume for the specified token and executes a buy order if a significant volume surge is detected. In the case of this demo, a buy order will be executed if the surge is greater than 10% of the previous trade volume. According to this demo: - The main function initializes the trading bot by fetching the initial trading volume of the specified token. - It then enters an infinite loop where it continuously fetches the current trading volume, checks for significant volume surges, and executes buy orders if a surge is detected. - The loop runs every minute, ensuring the bot operates in real time. Any exceptions during the execution are caught and logged to avoid crashes. ```python # Main function to run the bot def main(): try: initial_volume = get_volume() print("Initial Volume:", initial_volume) while True: current_volume = get_volume() print("Current Volume:", current_volume) if check_volume_surge(initial_volume, current_volume): print(f"Volume surge detected. Executing buy order.") tx_hash = execute_buy_order(TOKEN_ADDRESS) if tx_hash: print(f"Transaction hash: {tx_hash}") time.sleep(60 * 1) # Check every 1 minute except Exception as e: print(f"Error occurred: {str(e)}") if __name__ == "__main__": main() ``` By following this tutorial, you have created an MEV bot in Python that detects trading volume surges for a specific token and executes buy orders automatically. --- ## Bar continuity (OHLC stitching) URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/bar-continuity/ Build Bar continuity (OHLC stitching): a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Bar continuity (OHLC stitching) TradingView draws each candle from the **open, high, low, close** you supply. If the next bar’s **open** does not equal the previous bar’s **close**, the bodies can look disconnected or “floating,” even when the underlying stream is correct. That mismatch is common with **aggregated** OHLC from APIs: interval boundaries, late trades, or how the backend rounds bars are not always the same as a strict “open = prior close” rule. This tutorial addresses continuity in two places: | Path | What we do | |------|------------| | **Historical** | After sorting bars from the REST/GraphQL response, run `connectBarContinuity` on the array so each bar’s open (and high/low envelope) lines up with the previous close. See [Getting Historical Data](/docs/usecases/tradingview-subscription-realtime/historical_OHLC/#bar-continuity-historical). | | **Real-time** | Track the last emitted bar’s time and close; when the stream advances to a new candle timestamp, set the new bar’s open to that close and expand high/low. See [Fetching Real-time OHLC](/docs/usecases/tradingview-subscription-realtime/realtime_OHLC/#subscribing-to-the-stream). | ## What changes, what does not - **Adjusted:** `open`, and possibly `high` / `low`, so the candle **touches** the prior close visually. - **Unchanged:** `close`, `volume`, and time—so the **last price** and **volumes** stay as returned by Bitquery. This is a **presentation** normalization for the chart, not a change to exchange-reported economics. ## Placeholder / missing bars Padding the series with synthetic bars (for example zero OHLC) only fills **time slots** so TradingView has enough points. It does **not** fix continuity between **real** API bars. Always run continuity on the real bars first, then prepend placeholders if your app still needs them. ## Reference implementation The sample repo includes: - [`barContinuity.js`](https://github.com/bitquery/tradingview-subscription-realtime/blob/main/src/barContinuity.js) — `connectBarContinuity(bars)` - [`histOHLC.js`](https://github.com/bitquery/tradingview-subscription-realtime/blob/main/src/histOHLC.js) — calls it after sorting - [`webSocketOHLC.js`](https://github.com/bitquery/tradingview-subscription-realtime/blob/main/src/webSocketOHLC.js) — live stitching across candle boundaries --- ## Base Sniper Bot URL: https://docs.bitquery.io/docs/usecases/base-sniper-bot/ Build Base Sniper Bot: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. Keep queries fast with indexed filters. # Tutorial : Building a Base Sniper Bot Using Bitquery Base Events API and Uniswap SDK This tutorial will guide you through building a Base sniper bot using Bitquery Events API and the Uniswap SDK for executing swaps. Sniping depends on seeing transactions before they confirm — the [Mempool API](https://bitquery.io/products/mempool-api) page covers pending transactions and MEV data feeds. > Note: This material is for educational and informational purposes only and is not intended as investment advice. The content reflects the author's personal research and understanding. While specific investments and strategies are mentioned, no endorsement or association with these entities is implied. Readers should conduct their own research and consult with qualified professionals before making any investment decisions. Bitquery is not liable for any losses or damages resulting from the application of this information. ## Tutorial Video ## Tutorial Github Code Repository - [Repository Link](https://github.com/Akshat-cs/Base-sniper-bot) ### Prerequisites 1. **Node.js** and **npm** installed on your system. 2. **Bitquery Free Developer Account** with OAuth token (follow instructions [here](/docs/authorization/how-to-generate/)). 3. **Any Base Chain supported Wallet** with some Base ETH for transaction fees and also some WETH as I have used WETH in the video tutorial to make swap. I have used the Bitquery Base Events API to get the latest created pool which has Token A as WETH with Token Addres `0x4200000000000000000000000000000000000006`. - If you want to conduct the swap using different Token then you can change the address in this `Arguments: {startsWith: {Value: {Address: {is: "0x4200000000000000000000000000000000000006"}}}}` in tokens.ts file to your Token Address that you want to conduct swaps with. ### Step 1: Setting Up the Environment 1. **Initialize a new Node.js project:** ```bash mkdir base-sniper-bot cd base-sniper-bot npm init -y ``` 2. **Install the necessary dependencies:** ```bash npm install @types/node @uniswap/sdk-core @uniswap/smart-order-router @uniswap/v3-sdk axios dotenv ethers ts-node tslib typescript ``` ### Step 2: Creating the Bot 1. **Create a `.env` file :** This file will contain all the environment variables. Put in your Wallet private key that you are using to conduct swap. And also put in the Bitquery OAuth Token, follow the instructions on how to get it [here](/docs/authorization/how-to-generate/). ```javascript # BASE MAINNET RPC=https://mainnet.base.org WALLET_PRIVATE_KEY= CHAIN_ID=8453 SWAP_ROUTER_ADDRESS=0x2626664c2603336E57B271c5C0b26F421741e481 SLIPPAGE_TOLERANCE=5 DEADLINE_IN_MINUTES=30 BITQUERY_TOKEN= ``` 2. **Create a `config.ts` file:** This is a basic configuration file which helps us to expose our environment variables to our application and we are also using Ethers to set provider and signer. ```javascript import { Percent } from "@uniswap/sdk-core"; import { ethers, providers, Wallet } from "ethers"; import { config as loadEnvironmentVariables } from "dotenv"; loadEnvironmentVariables(); export const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; export const SWAP_ROUTER_ADDRESS = process.env.SWAP_ROUTER_ADDRESS || ""; export const CHAIN_ID = parseInt(process.env.CHAIN_ID || "1"); export const DEADLINE = Math.floor( (Date.now() / 1000) _ (parseInt(process.env.DEADLINE_IN_MINUTES || "30") _ 60) ); export const SLIPPAGE_TOLERANCE = new Percent( process.env.SLIPPAGE_TOLERANCE || 5, 100 ); const RPC = process.env.RPC; export const provider = ethers.providers.getDefaultProvider(RPC); export const signer = new Wallet(WALLET_PRIVATE_KEY, provider); ``` 3. **Create a `tokens.ts` file:** In this step we are importing necessary modules then loading environment variables and also setting ERC20 ABI as we are going to need it to make the token contract instance from token address. ```javascript loadEnvironmentVariables(); const ERC20_ABI = [ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function allowance(address, address) external view returns (uint256)", "function approve(address, uint) external returns (bool)", "function balanceOf(address) external view returns(uint256)", ]; ``` We are going to need Token Contract to call different functions like `balanceOf`, `decimals` and `symbol`. So we are building Token Contract using `buildERC20TokenWithContract` function by providing the token address and the provider. ```javascript type TokenWithContract = { contract: Contract, walletHas: (signer: Signer, requiredAmount: BigNumberish) => Promise, token: Token, }; const buildERC20TokenWithContract = async ( address: string, provider: Provider ): Promise => { try { const contract = new Contract(address, ERC20_ABI, provider); const [name, symbol, decimals] = await Promise.all([ contract.name(), contract.symbol(), contract.decimals(), ]); return { contract: contract, walletHas: async (signer, requiredAmount) => { const signerBalance = await contract .connect(signer) .balanceOf(await signer.getAddress()); return signerBalance.gte(BigNumber.from(requiredAmount)); }, token: new Token(CHAIN_ID, address, decimals, symbol, name), }; } catch (error) { console.error( `Failed to fetch token details for address ${address}:`, error ); return null; } }; ``` Setting provider and type of Tokens here as we are using Typescript. Then we built a function `getTokens` which makes an API call and gets the address of Tokens 0 and 1 in the latest created liquidity pool. This function finally returns the Token Contract for the fetched Token 0 and Token 1 addresses using `buildERC20TokenWithContract`. Keep in mind we have set the Token 0 address as WETH token address. In the `index.ts` file we are going to swap this WETH with the other token i.e token 1 in the pool. ```javascript // Example usage for BASE const provider = new providers.JsonRpcProvider(process.env.RPC); type Tokens = { Token0: TokenWithContract | null, Token1: TokenWithContract | null, }; export const getTokens = async (): Promise => { try { let data = JSON.stringify({ query: 'query {\n EVM(network: base) {\n Events(\n limit: {count: 1}\n orderBy: {descending: Block_Time}\n where: {Log: {Signature: {Name: {is: "PoolCreated"}}, SmartContract: {is: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD"}}, Arguments: {startsWith: {Value: {Address: {is: "0x4200000000000000000000000000000000000006"}}}}}\n ) {\n Transaction {\n Hash\n }\n Block {\n Time\n }\n Log {\n Signature {\n Name\n }\n }\n Arguments {\n Name\n Type\n Value {\n ... on EVM_ABI_Integer_Value_Arg {\n integer\n }\n ... on EVM_ABI_String_Value_Arg {\n string\n }\n ... on EVM_ABI_Address_Value_Arg {\n address\n }\n ... on EVM_ABI_BigInt_Value_Arg {\n bigInteger\n }\n ... on EVM_ABI_Bytes_Value_Arg {\n hex\n }\n ... on EVM_ABI_Boolean_Value_Arg {\n bool\n }\n }\n }\n }\n }\n}\n', variables: "{}", }); const axiosConfig: AxiosRequestConfig = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", Authorization: `Bearer ${process.env.BITQUERY_TOKEN}`, // put your oauth token here }, data: data, }; const response = await axios.request(axiosConfig); const token0Address = response.data.data.EVM.Events[0].Arguments[0].Value.address; const token1Address = response.data.data.EVM.Events[0].Arguments[1].Value.address; console.log(token0Address); console.log(token1Address); const Token0 = await buildERC20TokenWithContract(token0Address, provider); const Token1 = await buildERC20TokenWithContract(token1Address, provider); return { Token0, Token1 }; } catch (error) { console.error("Error fetching tokens:", error); return { Token0: null, Token1: null }; } }; ``` For the sake of the demo we have used a query, to **track new tokens in real-time** use the below subscriptions ([link](https://ide.bitquery.io/Subscription-Latest-created-pool-on-uniswap-V3-on-Base-chain-with-Token0-as-WETH)). ```javascript subscription { EVM(network: base) { Events( orderBy: {descending: Block_Time} where: {Log: {Signature: {Name: {is: "PoolCreated"}}, SmartContract: {is: "0x33128a8fC17869897dcE68Ed026d694621f6FDfD"}}, Arguments: {startsWith: {Value: {Address: {is: "0x4200000000000000000000000000000000000006"}}}}} ) { Transaction { Hash } Block { Time } Log { Signature { Name } } Arguments { Name Type Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } } } } ``` 4. **Create a `index.ts` file:** a. **Doing the necessary imports** ```javascript import { BigNumber, ethers } from "ethers"; import { AlphaRouter, SwapType, SwapRoute, } from "@uniswap/smart-order-router"; import { CurrencyAmount, TradeType } from "@uniswap/sdk-core"; import type { TransactionRequest } from "@ethersproject/abstract-provider"; import { getTokens } from "./tokens"; import { provider, signer, CHAIN_ID, SWAP_ROUTER_ADDRESS, SLIPPAGE_TOLERANCE, DEADLINE, } from "./config"; ``` b. **Building the main function** All of the code covered under this section b is going to be in the main function. 1. Firstly it calls the `getTokens` function and fetches the Token0 and Token1 token contracts. And then set `tokenFrom` and `tokenTo` tokens and `tokenFromContract` to call functions on tokenFrom token. ```javascript // Wait for the getTokens function to resolve const { Token0, Token1 } = await getTokens(); // Ensure tokens are not null if (!Token0 || !Token1) { throw new Error("Tokens are not initialized."); } const tokenFrom = Token0.token; const tokenFromContract = Token0.contract; const tokenTo = Token1.token; ``` 2. Then we check if we have passed the argument in the terminal while running the bot. This means that if we have not passed the amount of WETH we want to swap with then throw error. Then we are checking if we have enough amount of the TokenFrom token or not. It must be grater than the passed argument in the terminal. ```javascript if (typeof process.argv[2] === "undefined") { throw new Error(`Pass in the amount of ${tokenFrom.symbol} to swap.`); } const walletAddress = await signer.getAddress(); const amountIn = ethers.utils.parseUnits( process.argv[2], tokenFrom.decimals ); const balance = await tokenFromContract.balanceOf(walletAddress); if (!(await Token0.walletHas(signer, amountIn))) { throw new Error( `Not enough ${tokenFrom.symbol}. Needs ${amountIn}, but balance is ${balance}.` ); } ``` 3. We are using `AlphaRouter` here from Uniswap to swap tokens on Uniswap efficiently. Then we use this router object to create a route which takes the specific details of our swap. If no route is found then it throws error. ```javascript const router = new AlphaRouter({ chainId: CHAIN_ID, provider }); const route = await router.route( CurrencyAmount.fromRawAmount(tokenFrom, amountIn.toString()), tokenTo, TradeType.EXACT_INPUT, { recipient: walletAddress, slippageTolerance: SLIPPAGE_TOLERANCE, deadline: DEADLINE, type: SwapType.SWAP_ROUTER_02, } ); if (!route) { throw new Error("No route found for the swap."); } console.log( `Swapping ${amountIn} ${tokenFrom.symbol} for ${route.quote.toFixed( tokenTo.decimals )} ${tokenTo.symbol}.` ); ``` 4. Then we check the allowance. We are just defining here `buildSwapTransaction` and then also using `swapTransaction` to populate the `buildSwapTransaction`. Then we have also defined `attemptSwapTransaction` which sends the transaction to the network. ```javascript const allowance: BigNumber = await tokenFromContract.allowance( walletAddress, SWAP_ROUTER_ADDRESS ); const buildSwapTransaction = ( walletAddress: string, routerAddress: string, route: SwapRoute ): TransactionRequest => { return { data: route.methodParameters?.calldata, to: routerAddress, value: BigNumber.from(route.methodParameters?.value), from: walletAddress, gasLimit: BigNumber.from("2000000"), // Set your desired gas limit here // Optionally, you can specify gasPrice here if needed // gasPrice: YOUR_GAS_PRICE_IN_WEI }; }; const swapTransaction = buildSwapTransaction( walletAddress, SWAP_ROUTER_ADDRESS, route ); const attemptSwapTransaction = async ( signer: ethers.Wallet, transaction: TransactionRequest ) => { const signerBalance = await signer.getBalance(); if (!signerBalance.gte(transaction.gasLimit || "0")) { throw new Error(`Not enough ETH to cover gas: ${transaction.gasLimit}`); } // Send the transaction with the specified gas-related parameters signer.sendTransaction(transaction).then((tx) => { tx.wait().then((receipt) => { console.log("Completed swap transaction:", receipt.transactionHash); }); }); }; ``` 5. Here we finally call the before defined functions. Firstly we check if there is enough WETH allowance. And if there is not then we send an approve transaction to the network with the `AmountIn` amount of WETH. Then we call the `attemptSwapTransaction` which des the actual swap. ```javascript if (allowance.lt(amountIn)) { console.log(`Requesting ${tokenFrom.symbol} approval…`); const approvalTx = await tokenFromContract .connect(signer) .approve( SWAP_ROUTER_ADDRESS, ethers.utils.parseUnits(amountIn.mul(1000).toString(), 18) ); approvalTx.wait(3).then(() => { attemptSwapTransaction(signer, swapTransaction); }); } else { console.log( `Sufficient ${tokenFrom.symbol} allowance, no need for approval.` ); attemptSwapTransaction(signer, swapTransaction); } ``` c. **Calling the main function with some error handling** ```javascript main().catch((error) => { console.error(error); process.exit(1); }); ``` ### Step 3: Running the Bot 1. **Check the .env:** - Make sure that you have replace `PRIVATE_KEY` with your actual BASE account public key. - Make sure that you have replace `BITQUERY_TOKEN` with your actual Bitquery OAuth token. 2. **Run the bot:** 0.001 in the below script is the amount of WETH that I want to use for the swap. ```bash ts-node index.ts 0.001 ``` ### Conclusion You've successfully set up a Base sniper bot using Bitquery for Base Events API and Uniswap SDK for executing swaps. You need to change the query in tokens.ts file into subscription if you want to use it to listen for on-chain events and then buy the token B from each new pool that gets created on Uniswap. But you will need to make some necessary changes before that. This tutorial just shows you how you can get the recently created pool on uniswap and which token B it has as token A we have already set as WETH in the query and swap a token in that pool. Ensure your bot is monitored and managed appropriately, as we are running on the mainnet with real funds. --- ## Binance Exchange Wallet Monitoring URL: https://docs.bitquery.io/docs/usecases/binance-exchange-wallet-monitoring/ Build Binance Exchange Wallet Monitoring: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Monitoring Withdrawals and Deposits for 1000s of Binance Exchange Wallets In this guide we will utilize the [Protobuf Kafka streams](/docs/streams/protobuf/kafka-protobuf-python/) provided by Bitquery to monitor withdrawals and deposits for a large number of Binance Exchange Wallets on BSC network. You can read more about Kafka solution by Bitquery [here](/docs/streams/kafka-streaming-concepts/). Checkout the complete [codebase](https://github.com/bitquery/binance-exchange-wallets-monitoring) for any issues in the tutorial. ## Prerequisites 1. Create a project folder with the given command. ```shell mkdir bsc-exchange-wallet-monitoring ``` 2. Install the dependencies using the command line. ```shell pip install bitquery-pb2-kafka-package ``` 3. Create the file structure with the following commands. ```shell touch main.py mkdir helpers cd helpers touch convert_bytes.py ``` ## Helper Functions In this section we will discuss the code written inside the helpers folder. ### Convert Bytes This functions takes the base58 encoded bytes as parameter and returns readable string values. ```python def convert_bytes(value, encoding='hex'): if encoding == 'base58': return base58.b58encode(value).decode() else: return value.hex() ``` ## Imports ```python from confluent_kafka import Consumer, KafkaError, KafkaException from google.protobuf.message import DecodeError from evm import token_block_message_pb2 from helpers.convert_bytes import convert_bytes from helpers.print_protobuf_message import print_protobuf_message ``` ## Creating Kafka Configuration The credentials such as `username` and `password` could be received by contacting the Bitquery team via - sales@bitquery.io ```python group_id_suffix = uuid.uuid4().hex conf = { 'bootstrap.servers': 'rpk0.bitquery.io:9092,rpk1.bitquery.io:9092,rpk2.bitquery.io:9092', 'group.id': f'{username}-group-{group_id_suffix}', 'session.timeout.ms': 30000, 'security.protocol': 'SASL_PLAINTEXT', 'ssl.endpoint.identification.algorithm': 'none', 'sasl.mechanisms': 'SCRAM-SHA-512', 'sasl.username': username, 'sasl.password': password, 'auto.offset.reset': 'latest', } ``` ## Creating Consumer and Subcribing to Topic The topic to subscribe should be selected based on the usecase. In this case we are looking to track the deposits and withrawls of tokens for a list of Binance Exchange Wallets, thus `bsc.tokens.proto` would be the best choice. ```python consumer = Consumer(conf) topic = 'bsc.tokens.proto' consumer.subscribe([topic]) ``` ## Creating Set of Binance Exchange Wallets For the purpose of tutorial we have directly created a set containing a bunch of Binance exchange wallet addresses. When building an enterprise grade solution where the number of wallets are in thousands or millions, the set could be saved in a separate file. The wallets are stored in a set instead of list due to fast lookup in sets. ```python wallets = { '0xf977814e90da44bfa03b6295a0616a897441acec', '0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8', '0x5a52e96bacdabb82fd05763e25335261b270efcb', '0x3c783c21a0383057d128bae431894a5c19f9cf06', '0xdccf3b77da55107280bd850ea519df3705d1a75a', '0x8894e0a0c962cb723c1976a4421c95949be2d4e3', '0x515b72ed8a97f42c568d6a143232775018f133c8', '0xbd612a3f30dca67bf60a39fd0d35e39b7ab80774', '0x01c952174c24e1210d26961d456a77a39e1f0bb0', '0x29bdfbf7d27462a2d115748ace2bd71a2646946c', '0x73f5ebe90f27b46ea12e5795d16c4b408b19cc6f', '0x161ba15a5f335c9f06bb5bbb0a9ce14076fbb645', '0x1fbe2acee135d991592f167ac371f3dd893a508b', '0xeb2d2f1b8c558a40207669291fda468e50c8a0bb', '0xa180fe01b906a1be37be6c534a3300785b20d947' } ``` ## Proccess Message Function This function receives the message from the stream as a parameter. The message contains a list of token transfers and the function checks if either of the `sender` or `receiver` are present in the set defined earlier. If either of the condition is satisfied then it prints the message displayed in the [Final Results](#final-result) section. ```python def process_message(message): try: buffer = message.value() parsed_message = token_block_message_pb2.TokenBlockMessage() parsed_message.ParseFromString(buffer) transfers = parsed_message.Transfers for transfer in transfers: sender = '0x' + convert_bytes(transfer.Sender) receiver = '0x' + convert_bytes(transfer.Receiver) amount = int.from_bytes(transfer.Amount, byteorder='big')/10e18 currency = transfer.Currency symbol = currency.Symbol if sender in wallets: print(receiver, "has withdrawn", amount, symbol, "from the", sender, "Exchange Wallet") elif receiver in wallets: print(sender, "has deposited", amount, symbol, "to the", receiver, "Exchange Wallet") except DecodeError as err: print(f"Protobuf decoding error: {err}") except Exception as err: print(f"Error processing message: {err}") ``` ## Poll and Monitor Activities in Real Time This is the main loop for consuming Kafka messages and monitoring the exchange wallets activities in real time. ```python try: while True: msg = consumer.poll(timeout=1.0) if msg is None: continue if msg.error(): if msg.error().code() == KafkaError._PARTITION_EOF: continue else: raise KafkaException(msg.error()) process_message(msg) except KeyboardInterrupt: print("\nStopping consumer...") finally: consumer.close() ``` ## Running the Script Run this command line to run the `main.py` script. ```shell python main.py ``` ## Final Result ![Final Results of Project](/img/usecases/kafka-examples/result.png) --- ## Breaking Down Price Streams in Detail URL: https://docs.bitquery.io/docs/trading/crypto-price-api/in-depth/ Breaking Down Price Streams in Detail via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. # Breaking Down Price Streams in Detail This section is not necessary reading to start using the APIs; it explains how we have set up and designed the APIs and what algorithmic rules we follow to show data. You can go to the [examples section](/docs/trading/crypto-price-api/examples) to start using it. For how prices and volumes are computed (trade filtering, volume-weighted token/currency pricing, and field semantics), see **[Price Index Algorithm (March 11 2026 Update)](/docs/trading/crypto-price-api/price-index-algorithm)**. ## Understanding Base and Quote in Pairs API This section explains the design philosophy and algorithmic choices behind the Pairs data cube, specifically how we determine the base and quote tokens in a trading pair. In every trading pair, one token is designated as the base, and the other as the quote. The price you see is how much of the quote token is needed to buy 1 unit of the base token. For example: If SOL/USDC = 180.0, then 1 SOL costs 180 USDC. Here, SOL is the base and USDC is the quote. But how do we determine which token is the base and which is the quote? price feed: pairs -> base and quote differentiated ### The Logic Behind Quote Token Selection We use the following rules to determine the **quote** token: 1. **Stablecoin Priority** If one of the tokens is a **stablecoin**, it is chosen as the **quote**. > Example: In a USDC/ETH pair, ETH is base, USDC is quote — you get the ETH price in USD. Stablecoins include: - USDT - USDC - DAI - USDS - USD1 - TUSD - USDD - USD₮0 2. **Native Assets vs Another Token** If neither token is a stablecoin but one is a **native token** of the blockchain (e.g., ETH on Ethereum, SOL on Solana, BTC on Bitcoin), we choose the native token as the **quote**. > Example: For a wBTC/ETH pair on Ethereum, ETH is quote because it's the native asset of that chain. 3. **Dynamic Volume-Based Decision** If neither of the above rules apply (i.e., two non-stable, non-native tokens), we determine the quote **dynamically** based on liquidity: - We calculate the **trading volume** of both tokens across all stablecoin pairs. - The token with the **higher total volume in USD** is selected as the **quote**. > This ensures that the quote is the more liquid asset, which leads to more stable and usable price data. --- ## Build Solana OHLC from DEX Trades URL: https://docs.bitquery.io/docs/usecases/solana-ohlc-calculator/ Calculate Solana OHLC candles from Bitquery DEX trades with JavaScript examples for intervals, pricing, and chart integrations. # Build OHLC Values using DEX Trades Data This script calculates **OHLC (Open, High, Low, Close)** data for a particular Token Pair Solana-based trades by leveraging percentile filtering thus removing anomaly or bot trades. It fetches trading data from the Bitquery API and processes it to compute the OHLC values for a specific trading pair. For pre-aggregated OHLC data, see our [Crypto Price API](/docs/trading/crypto-price-api/). The sole motive of this tutorial is to guide you how an OHLC is calculated and Bot trades or anomaly trades are filtered out. We have shown one of the by using `quantile`, you can devise your own strategy to use different filters or logics to omit these anomaly trades. ## Tutorial Video ## Features - Fetches the 5th and 95th percentiles of trade prices for a specific Solana trading pair. - Filters trades within the percentile price range. - Computes and displays **OHLC** data: - **Open**: The first trade's price. - **High**: The highest trade price. - **Low**: The lowest trade price. - **Close**: The last trade's price. ## Prerequisites Make sure you have the following before starting: 1. **Node.js** installed on your system. 2. A **Bitquery API token** to fetch data from the APIs You can get your API token from Bitquery using these steps [here](/docs/authorization/how-to-generate/). 3. Information about the Solana trading pair you want to analyze: - **Main Currency Address**: The mint address of the main currency. - **Side Currency Address**: The mint address of the side currency. - **Market Pair Address**: The address of the trading pair. ## Installation 1. Clone this repository and navigate to the project directory: ```bash git clone https://github.com/Akshat-cs/OHLC-Calculator-Solana.git ``` 2. Install the required dependencies: ```bash npm install ``` 3. Create a `.env` file in the root directory and add your Bitquery API Token, check the steps to get it [here](/docs/authorization/how-to-generate/): ``` AUTH_TOKEN = ``` ## Configuration Before running the script, update the following constants in the code (`index.js`) to match your Solana trading pair details: ```javascript const MAIN_CURRENCY_ADDRESS = "9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump"; const SIDE_CURRENCY_ADDRESS = "So11111111111111111111111111111111111111112"; const PAIR_ADDRESS = "Bzc9NZfMqkXR6fz1DBph7BDf9BroyEf6pnzESP7v5iiw"; ``` ## Usage To calculate OHLC values, run the script: ```bash node index.js ``` ## Script Workflow 1. Fetches the 5th and 95th percentiles of trade prices for the past hour using the Bitquery API. 2. Retrieves trades that fall within the calculated percentile price range. 3. Computes the **OHLC** data based on the filtered trades: - **Open**: The price of the earliest trade. - **High**: The highest trade price. - **Low**: The lowest trade price. - **Close**: The price of the latest trade. 4. Displays the OHLC values in the console. ## Code Overview ### 1. Fetching Percentiles The `fetchPercentiles` function retrieves the 5th and 95th percentiles for the specified trading pair using the Bitquery API. ```javascript async function fetchPercentiles() { const timestamp1Hago = getTimestamp(60); const percentileData = JSON.stringify({ query: `query ($mainCurrencyAddress: String , $sideCurrencyAddress: String, $pairAddress: String, $timestamp1Hago: DateTime ) { Solana { DEXTradeByTokens( where: {Trade: {Currency: {MintAddress: {is: $mainCurrencyAddress}}, Side: {Currency: {MintAddress: {is: $sideCurrencyAddress}}}, Market: {MarketAddress: {is: $pairAddress}}}, Transaction: {Result: {Success: true}}, Block: {Time: {since: $timestamp1Hago}}} ) { percentile5th: quantile(of: Trade_PriceInUSD, level: 0.05) percentile95th: quantile(of: Trade_PriceInUSD, level: 0.95) } } }`, variables: JSON.stringify({ mainCurrencyAddress: MAIN_CURRENCY_ADDRESS, sideCurrencyAddress: SIDE_CURRENCY_ADDRESS, pairAddress: PAIR_ADDRESS, timestamp1Hago, }), }); const config = { method: "post", maxBodyLength: Infinity, url: API_URL, headers: { "Content-Type": "application/json", Authorization: `Bearer ${AUTH_TOKEN}`, }, data: percentileData, }; try { const response = await axios.request(config); return response.data.data.Solana.DEXTradeByTokens[0]; } catch (error) { console.error("Error fetching percentiles:", error); throw error; } } ``` ### 2. Fetching Trades and Computing OHLC The `fetchTradesAndComputeOHLC` function retrieves trades within the specified percentile price range and calculates the **OHLC** values: - **Open**: The price of the earliest trade. - **High**: The highest trade price. - **Low**: The lowest trade price. - **Close**: The price of the latest trade. ```javascript async function fetchTradesAndComputeOHLC(lowerBoundPrice, upperBoundPrice) { const timestamp1Hago = getTimestamp(60); const tradeData = JSON.stringify({ query: `query ($mainCurrencyAddress: String , $sideCurrencyAddress: String, $pairAddress: String, $lowerBoundPrice: Float, $upperBoundPrice: Float, $timestamp1Hago: DateTime ) { Solana { DEXTradeByTokens( orderBy: {descending: Block_Time} where: {Trade: {Currency: {MintAddress: {is: $mainCurrencyAddress}}, Side: {Currency: {MintAddress: {is: $sideCurrencyAddress}}}, Market: {MarketAddress: {is: $pairAddress}}, PriceInUSD: {ge: $lowerBoundPrice, le: $upperBoundPrice}}, Transaction: {Result: {Success: true}}, Block: {Time: {since: $timestamp1Hago}}} ) { Block { Time Slot } Trade { Price PriceInUSD } } } }`, variables: JSON.stringify({ mainCurrencyAddress: MAIN_CURRENCY_ADDRESS, sideCurrencyAddress: SIDE_CURRENCY_ADDRESS, pairAddress: PAIR_ADDRESS, lowerBoundPrice, upperBoundPrice, timestamp1Hago, }), }); const config = { method: "post", maxBodyLength: Infinity, url: API_URL, headers: { "Content-Type": "application/json", Authorization: `Bearer ${AUTH_TOKEN}`, }, data: tradeData, }; try { const response = await axios.request(config); const trades = response.data.data.Solana.DEXTradeByTokens; if (trades.length === 0) { console.log("No trades found."); return; } // Compute OHLC const ohlc = { open: trades[trades.length - 1].Trade.Price, high: Math.max(...trades.map((t) => t.Trade.Price)), low: Math.min(...trades.map((t) => t.Trade.Price)), close: trades[0].Trade.Price, }; console.log("OHLC:", ohlc); } catch (error) { console.error("Error fetching trades:", error); } } ``` This function ensures the OHLC values are accurately calculated based on filtered trade data within the provided range. ### 3. Main Execution The `main` function orchestrates the entire process by: 1. Fetching the 5th and 95th percentiles for the specified trading pair. 2. Using these percentiles to filter trades within the range. 3. Computing and displaying the **OHLC** data. ```javascript // Main Function (async function main() { try { const percentiles = await fetchPercentiles(); await fetchTradesAndComputeOHLC( percentiles.percentile5th, percentiles.percentile95th ); } catch (error) { console.error("Error in main function:", error); } })(); ``` This function acts as the entry point for the script, coordinating the percentile fetching and OHLC calculation in a seamless workflow. --- ## Build Trading Indicators for Crypto Data URL: https://docs.bitquery.io/docs/usecases/trading-indicators/ Build Trading Indicators for Crypto Data: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Build Trading Indicators for Crypto Data Technical indicators are a vital tool for cryptocurrency traders, as they can help to identify trends, predict price movements, and make informed trading decisions. For real-time price data with pre-calculated indicators, use our [Crypto Price API](/docs/trading/crypto-price-api/). With our new [Crypto Price Stream](/docs/trading/crypto-price-api/) we can get real-time crypto market data with **Simple Moving Average (SMA)**, **Exponential Moving Average (EMA)**, and **Weighted Simple Moving Average (WSMA)** at 1-second interval precalculated. In this tutorial we will see how to stream them and calculate advanced trading indicators. **This code is available readily as a Python package [here](https://pypi.org/project/bitquery-trading-indicators-stream/)** The list of indicators we will be calculating - Simple Moving Average (SMA) - Exponential Moving Average (EMA) - Weighted Simple Moving Average - Relative Strength Index (RSI) - Volume Weighted Average Price (VWAP) ### Sample Output **Step 1: Setup WebSocket Connection** ```python from gql import gql from gql.transport.websockets import WebsocketsTransport transport = WebsocketsTransport( url=f"wss://streaming.bitquery.io/graphql?token={config.oauth_token}", headers={"Sec-WebSocket-Protocol": "graphql-ws"} ) await transport.connect() print("Connected") ``` **Remember:** Replace `YOUR_TOKEN` with an OAuth token from your Bitquery account. You can generate it [here](https://account.bitquery.io/user/api_v2/access_tokens) **Step 2: Subscribe to the Price Feed** We write a query to get OHLC data for the Uniswap pair ETH-DAI, filtering for trades in the past 10 minutes and returning the volume, trade details, and block timestamps. You can [extend the query to get data for longer duration of different periods like year/month/day](https://ide.bitquery.io/USDT-OHLC-Price-Data-V2_3). We’ll request **Solana token** prices in **1-minute intervals**, including SMA/EMA/WSMA. > Streaming every token on a chain is a good fit for the `Tokens` cube. To compute indicators for **one specific token**, swap in [`Pairs` with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price): the same `Price.Average` fields (`SimpleMoving`, `ExponentialMoving`, `WeightedSimpleMoving`) are available there, computed from the token's top market rather than blended across all of its pools. ```python query = gql(""" subscription { Trading { Tokens( where: { Token: { Network: { is: "Solana" } }, Interval: { Time: { Duration: { eq: 1 } } } } ) { Token { Address Symbol } Interval { Time { End } } Volume { Base Quote Usd } Price { Ohlc { Close } Average { SimpleMoving ExponentialMoving WeightedSimpleMoving } } } } } """) async for data in transport.subscribe(query): for t in data["Trading"]["Tokens"]: close = t["Price"]["Ohlc"]["Close"] sma = t["Price"]["Average"]["SimpleMoving"] ema = t["Price"]["Average"]["ExponentialMoving"] wsma = t["Price"]["Average"]["WeightedSimpleMoving"] # Choose a volume basis suitable for your use case volume = (t.get("Volume", {}).get("Base") or t.get("Volume", {}).get("Quote") or t.get("Volume", {}).get("Usd") or 1.0) print(t["Token"]["Address"], sma, ema, wsma, close, volume) ``` **Step 3: Calculate RSI in Real Time** We’ll keep RSI state in memory for each token and update it with **Wilder’s smoothing**. ```python rsi_period = 14 state = {} # address → RSI state def update_rsi(address, close): s = state.setdefault(address, {"prev_close": None, "avg_gain": 0, "avg_loss": 0, "count": 0, "initialized": False}) if s["prev_close"] is None: s["prev_close"] = close return None delta = close - s["prev_close"] gain, loss = max(delta, 0), max(-delta, 0) if not s["initialized"]: s["avg_gain"] += gain s["avg_loss"] += loss s["count"] += 1 if s["count"] >= rsi_period: s["avg_gain"] /= rsi_period s["avg_loss"] /= rsi_period s["initialized"] = True else: s["avg_gain"] = ((s["avg_gain"] * (rsi_period - 1)) + gain) / rsi_period s["avg_loss"] = ((s["avg_loss"] * (rsi_period - 1)) + loss) / rsi_period s["prev_close"] = close if not s["initialized"]: return None return 100 if s["avg_loss"] == 0 else 100 - (100 / (1 + s["avg_gain"] / s["avg_loss"])) ``` **Step 4: Calculate VWAP (rolling window)** We maintain a small rolling window of prices and volumes per token for a simple, streaming VWAP. ```python from collections import defaultdict vwap_period = 20 vwap_state = defaultdict(lambda: {"prices": [], "volumes": [], "pv_sum": 0.0, "vol_sum": 0.0}) def update_vwap(address, price, volume): s = vwap_state[address] s["prices"].append(price) s["volumes"].append(volume) if len(s["prices"]) > vwap_period: rp = s["prices"].pop(0) rv = s["volumes"].pop(0) s["pv_sum"] -= rp * rv s["vol_sum"] -= rv s["pv_sum"] += price * volume s["vol_sum"] += volume return s["pv_sum"] / s["vol_sum"] if s["vol_sum"] > 0 else price ``` **Step 5: Putting It Together** Inside your subscription loop: ```python for t in data["Trading"]["Tokens"]: address = t["Token"]["Address"] close = t["Price"]["Ohlc"]["Close"] sma = t["Price"]["Average"]["SimpleMoving"] ema = t["Price"]["Average"]["ExponentialMoving"] wsma = t["Price"]["Average"]["WeightedSimpleMoving"] volume = (t.get("Volume", {}).get("Base") or t.get("Volume", {}).get("Quote") or t.get("Volume", {}).get("Usd") or 1.0) rsi = update_rsi(address, close) vwap = update_vwap(address, float(close), float(volume)) if rsi is not None: print(f"{address} SMA:{sma} EMA:{ema} WSMA:{wsma} VWAP:{vwap:.6f} RSI:{rsi:.2f} Close:{close}") ``` **Advantages of this approach:** - **No polling** — prices are pushed to you as soon as they’re available. - **Built-in indicators** — SMA/EMA/WSMA come directly from Bitquery. - **Custom logic** — you can add advanced indicators like RSI without losing speed. --- ## Building a Basic UI for Balance Tracker URL: https://docs.bitquery.io/docs/usecases/real-time-balance-tracker/ui/ Building a Basic UI for Balance Tracker: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Building a Basic UI for Balance Tracker In this section we will create a simple UI using HTML, CSS and JavaScript to make the real-time Balance Tracker intuitive. For that we will create a HTML file named `index.js` in the same directory. ## HTML Code ``` html

Enter Address to Track Balance

Current Balance: 0 ETH

``` To run the project run the following command: ```bash node index.js ``` Now we can check the UI and test the project by visiting `localhost:3000`. **Note:** Make sure that the port 3000 is free. ## Final Results This is how the real-time Balance Tracker UI looks. You can enter any wallet address of your choice and track its activities, by tracking its Ethereum Balance changes in real-time. ![Balance Tracker](/img/real_time_balance_tracker_demo.png) To avoid redundancy, it updates the balance every 10 seconds. --- ## Calculating Profit or Loss Over Time URL: https://docs.bitquery.io/docs/usecases/p-l-product/pnl/ Build Calculating Profit or Loss Over Time: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Calculating Profit or Loss Over Time This is the first step, where we calculate the Weighted Average of Buy Price(WABP) by getting all the `buy` trades of the token. For this tutorial, we are getting the PnL of the `0x2107662b0eb1f95a42f47f667c6d4622fe1c9231` address for the following token `0x6982508145454ce325ddbe47a25d4ec3d2311933`. ``` WABP = sum(buyAmount*buyPriceInUSD)/sum(buyAmount) ``` We get the `sell` trades for the entire period and multiply the amount of tokens sold with the `priceInUSD` minus the WABP calculated. ``` pnl = sum(sellPriceInUSD-WABP) ``` 1. Import the necessary libraries: ```javascript const axios = require('axios'); require('dotenv').config() ``` 2. Create the config object with URL and headers, including your API key: ```javascript let data; let config = { method: 'post', maxBodyLength: Infinity, url: 'https://streaming.bitquery.io/graphql', headers: { 'Content-Type': 'application/json', 'X-API-KEY': 'BQYuTITWanwYGz0YLGdcWSADO74o5RTX', 'Authorization': process.env.AUT_TOKEN }, data : data }; ``` 3. Make the `getWeightedAverage()` function to get the Weighted Average for the Buy Price. ```javascript const getWeightedAverage = async (token, wallet) => { } ``` 4. Inside the declared function define the data property of object and get the response: ```javascript let data = JSON.stringify({ "query": "query MyQuery($token: String = \"\", $wallet: String = \"\") {\n EVM(dataset: combined) {\n DEXTrades(\n where: {Trade: {Buy: {Currency: {SmartContract: {is: $token}}, PriceInUSD: {ne: 0}}}, Transaction: {From: {is: $wallet}}}\n ) {\n Trade {\n Buy {\n Amount\n PriceInUSD\n }\n }\n }\n }\n}\n", "variables": `{\n \"token\": \"${token}\",\n \"wallet\": \"${wallet}\"\n}` }); config.data = data; const response = await axios.request(config); const buyTrades = response.data.data.EVM.DEXTrades; ``` 6. Declare variables and Traverse the `buyTrades` array: ```javascript let count = 0; let sum = 0; for( let i in buyTrades){ } ``` 7. Extract the `amount` and `PriceInUSD` from the buyTrades: ```javascript let amount = parseFloat(buyTrades[i].Trade.Buy.Amount); let price = buyTrades[i].Trade.Buy.PriceInUSD; ``` 8. Update the `sum` and `count` variables and return the WABP: ```javascript sum += amount*price; count += amount; return sum/count; ``` 9. Make the `getPnL()` function to calculate the PnL for the address and the token. ```javascript const getPnL = async (token, wallet) => { } ``` 10. Inside the declared function define the data property of object and get the response: ```javascript let data = JSON.stringify({ "query": "query MyQuery($token: String = \"\", $wallet: String = \"\") {\n EVM(dataset: combined) {\n DEXTrades(\n where: {Trade: {Sell: {Currency: {SmartContract: {is: $token}}, PriceInUSD: {ne: 0}}}, Transaction: {From: {is: $wallet}}}\n orderBy: {ascending: Block_Time}\n ) {\n Trade {\n Sell {\n Amount\n PriceInUSD\n }\n }\n }\n }\n}\n", "variables": `{\n \"token\": \"${token}\",\n \"wallet\": \"${wallet}\"\n}` }); config.data = data; const response = await axios.request(config); const sellTrades = response.data.data.EVM.DEXTrades; ``` 11. Get the weighted average for the adress and token: ```javascript const average = await getWeightedAverage(token, wallet); ``` 12. Declare variables and Traverse the `sellTrades` array: ```javascript let pnl = 0; for(let i in sellTrades){ } ``` 13. Update the `pnl` variable: ```javascript let amount = parseFloat(sellTrades[i].Trade.Sell.Amount); let sellPrice = sellTrades[i].Trade.Sell.PriceInUSD; let margin = amount*(sellPrice-average); pnl += margin; ``` 14. Print the `pnl`: ```javascript console.log(pnl); ``` 15. Run the function inside the `JS` script: ```javascript getPnL("0x6982508145454ce325ddbe47a25d4ec3d2311933", "0x2107662b0eb1f95a42f47f667c6d4622fe1c9231"); // (token address, wallet address) ``` You can change the token address and wallet address as per your requirements, or even use these functions for creating a pipeline in a bigger application. The realised Profit for this account by trading this token as of `2024-10-21` is around `4 USD`. --- ## Calculations and Scripting URL: https://docs.bitquery.io/docs/usecases/real-time-balance-tracker/scripts/ Build Calculations and Scripting: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Calculations and Scripting We will read the current balance once with the `Balances` cube, then keep it up to date from a live `TransactionBalances` stream. Each streamed row carries the balance after the transaction (`PostBalance`), so no summing is needed. To do this through code we will build two files (namely balance.js and index.js). ``` Current Balance = Balances (on load), then PostBalance from each streamed TransactionBalances row ``` ## Initialise and Installing Dependencies To initiate the project run the following command. ```bash npm init -y ``` Before going further make sure that all the dependencies are installed. The dependencies are listed below: - axios - dotenv - express - ws Use the following command to install these dependencies. ```bash npm install axios dotenv express ws ``` ## Create Balance Script This script will fetch the current balance from the `Balances` cube, giving the current balance of a particular currency (Ethereum in this example) for the wallet. Follow the steps given below to build the script 1. Import the dependencies ``` js const axios = require('axios'); require('dotenv').config() ``` **NOTE:** Make sure to create a `.env` file and create a AUTH_TOKEN variable to store your Bitquery Access Token. To know more about the access token click [here](https://account.bitquery.io/user/api_v2/access_tokens). 2. Create a standard config for the getBalance function. This includes the API url we are trying to hit, access token to authenticate the request, and the request body as data. ``` js let data = JSON.stringify({ "query": "{\n EVM(dataset: combined, network: eth) {\n Balances(\n where: {Balance: {Address: {is: \"\"}}, Currency: {SmartContract: {is: \"0x\"}}}\n ) {\n Balance {\n Amount\n }\n }\n }\n}\n", "variables": "{}" }); let config = { method: 'post', maxBodyLength: Infinity, url: 'https://streaming.bitquery.io/graphql', headers: { 'Content-Type': 'application/json', 'X-API-KEY': 'YOUR_API_KEY', 'Authorization': `Bearer ${process.env.AUTH_TOKEN}` }, data : data }; ``` Please note that the data part is a standard case, but will be modified upon the user input. 3. Create the asynchronous getBalance function. ``` js const getBalance = async (config) => { try { const response = await axios.request(config); // console.log(response.data.data.EVM.Balances[0].Balance.Amount) return response.data.data.EVM.Balances[0]?.Balance.Amount; } catch (error) { return error; } } ``` 4. Export the requirements from the file. ```js module.exports = {getBalance, config}; ``` ## Create Index Script In this file we will write code to: - Get Balance using the getBalance function from balance.js. - Create an express server so that we can render a simple UI. - Run a websoket connection to get real-time balance updates. 1. Import the requirements. ``` js const express = require('express'); const path = require('path'); const { getBalance, config } = require('./balance.js'); require('dotenv').config(); ``` 2. Initiate the express server and declare the address variable. ```js const app = express(); app.use(express.static(path.join(__dirname))); app.use(express.json()); let currentAddress = ''; // Default address if none provided ``` 3. Creating the endpoint to handle address input from frontend. ``` js app.post('/track-balance', (req, res) => { const { address } = req.body; if (!address) { return res.status(400).json({ success: false, message: 'Address required' }); } currentAddress = address; res.json({ success: true }); }); ``` 4. Creating the endpoint to get the balance from getBalance function. ``` js app.get('/get-balance', async (req, res) => { try { // Update the address in the config dynamically config.data = JSON.stringify({ "query": `{\n EVM(dataset: combined, network: eth) {\n Balances(\n where: {Balance: {Address: {is: \"${currentAddress}\"}}, Currency: {SmartContract: {is: \"0x\"}}}\n ) {\n Balance {\n Amount\n }\n }\n }\n}\n`, "variables": "{}" }); const balance = await getBalance(config); res.json({ balance: balance || '0' }); } catch (error) { console.error('Error fetching balance:', error); res.status(500).json({ error: 'Error fetching balance' }); } }); ``` 5. Start WebSocket connection for real-time updates. `Balances` is query-only, so the live feed uses `TransactionBalances`, which streams the balance before and after each transaction. ``` js const { WebSocket } = require("ws"); const bitqueryConnection = new WebSocket( "wss://streaming.bitquery.io/graphql?token=" + process.env.AUTH_TOKEN, ["graphql-ws"], ); bitqueryConnection.on("open", () => { console.log("Connected to Bitquery."); const initMessage = JSON.stringify({ type: "connection_init" }); bitqueryConnection.send(initMessage); }); bitqueryConnection.on("message", async (data) => { const response = JSON.parse(data); if (response.type === "connection_ack") { const subscriptionMessage = JSON.stringify({ type: "start", id: "1", payload: { query: ` subscription { EVM{ TransactionBalances( where: { TokenBalance: { Address: { is: "${currentAddress}" }, Currency: {SmartContract: {is: "0x"}} } } ) { Block { Time } TokenBalance { PreBalance PostBalance } } } }`, }, }); bitqueryConnection.send(subscriptionMessage); console.log("Subscription message sent."); } }); bitqueryConnection.on("error", (error) => { console.error("WebSocket Error:", error); }); ``` 6. Start the Express server. ``` js const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); ``` Now we need to build a simple UI using HTML, CSS and Javascript to make this project inituitive. --- ## Chain-Scale Screeners with selectWhere URL: https://docs.bitquery.io/docs/trading/query-operators/selectwhere-screeners/ Filter on computed aggregates server-side with selectWhere — the HAVING clause of the Bitquery Trading cube — to build token and wallet screeners in one request. # Chain-Scale Screeners with `selectWhere` `selectWhere` is the Trading cube's `HAVING` clause: it filters on a **computed aggregate** rather than on input rows. That is what makes a screener possible in one request — "every token whose 24-hour volume exceeded $1M", "every wallet with more than 500 trades" — instead of paginating the whole chain into your client and filtering there. A one-day, all-network screener scans a few million one-minute rows covering a few hundred thousand tokens and returns a few hundred survivors in **one to three seconds**. ```graphql { Trading { Tokens( where: { Token: { Network: { is: "Solana" } } Interval: { Time: { Duration: { eq: 60 } } } Block: { Time: { since: "2026-09-01T00:00:00Z", till: "2026-09-02T00:00:00Z" } } } orderBy: { descendingByField: "vol" } limit: { count: 1000 } ) { Token { Id Symbol } vol: sum(of: Volume_Usd, selectWhere: { gt: "1000000" }) } } } ``` ## Where it runs in the pipeline ``` where → GROUP BY → selectWhere (HAVING) → orderBy → limitBy → limit ``` This is ClickHouse's own clause order, and each position matters: - **`selectWhere` binds the aggregate it is attached to**, and only that one. A sibling aggregate in the same selection set is unaffected — which is the test that actually distinguishes HAVING from WHERE. (A contradictory predicate returning zero rows proves nothing; a contradictory `where` does the same.) - **HAVING runs before `limit`.** With N groups in the window and K qualifying, a small `limit: L` returns L **qualifying** rows. If the limit were applied first you would get roughly `L × K / N` — mostly non-qualifying rows. That cardinality argument is the proof. - **`limitBy` runs after ordering**, so it slices an already-filtered, already-sorted result into a per-key top-N. It is not a pre-filter and does not make a screener cheaper. :::caution `limitBy` is a per-key top-N only if you also supply `orderBy` Without `orderBy`, `limitBy` returns an arbitrary N per key that varies between runs. The outer `limit` is applied after `limitBy` and truncates the whole result. ::: ## The operator set depends on what you attach it to | Attached to | Type | Operators | | --- | --- | --- | | An aggregate metric (`sum`, `count`, `median`, …) | `Metric_filter_String` / `_Float` | `gt` `ge` `lt` `le` `eq` `ne` | | A **numeric** field-level selector | `OLAP_Float` | the same six | | A **string** field-level selector | `OLAP_String` | the full 15-operator string set — `like`, `startsWith`, `includes`, … | | A boolean leaf field | `Boolean` | a bare value: `selectWhere: true` | There is **no `between`** anywhere. Multiple clauses AND together, both inside one `selectWhere` object and across different metrics in the same selection set — so express a range with `ge` and `le`: ```graphql vol: sum(of: Volume_Usd, selectWhere: { ge: "10000" le: "1000000" }) ``` ## Literal types follow the metric's return type `sum`, `count` and `uniq` return JSON strings and take **string** literals; every Float-returning metric takes a **numeric** literal. Passing the wrong one is a clean schema error, not a silent failure. String literals are compared **numerically, not lexicographically** — `"100"` is greater than `"20"`, and `{gt: "9", lt: "400"}` correctly admits `19.09` and `318.96`. ## The silent no-op — the most important rule on this page A bare leaf field is a **GROUP BY dimension, not an aggregate**, so a `selectWhere` attached to it has nothing to filter. It is accepted, does nothing, and returns plausible wrong rows with no error. ```graphql # WRONG — silently unfiltered Volume { Usd(selectWhere: { gt: 1000 }) } # RIGHT — maximum: makes it an aggregate Volume { Usd(maximum: Volume_Usd, selectWhere: { gt: 1000 }) } ``` The rule: **the field must carry at least one of `maximum:`, `minimum:` or `if:`.** :::warning `if:` alone satisfies the rule but returns an arbitrary representative `if:` makes the field an aggregate, so the HAVING binds — but the value returned is an arbitrary row's value, not a statistic. Use `maximum:` or `minimum:` to make a leaf field an aggregate; use `if:` only alongside one of them. ::: ## Conditional HAVING Combining `if:` and `selectWhere` on the same metric filters on a **slice** of the group — for example wallets whose buy-side volume alone clears a threshold, or tokens active earlier in the window but dormant in the last two hours: ```graphql buys: sum(of: AmountsInUsd_Quote, if: { Side: { is: "Buy" } }, selectWhere: { gt: "50000" }) ``` ## What you cannot express | Want | Status | Do instead | | --- | --- | --- | | HAVING on a **ratio** | `calculate` takes no `selectWhere` | Compute and **rank** the ratio server-side (see below); apply the final cut client-side | | `in` / `notIn` | Absent from metric filters; present in `OLAP_String` but fails at runtime | Use `like` / `startsWith`, or put set membership in the cube's own `where:` | | Filtering `array_intersect` | The argument exists and type-checks, then fails server-side | Known limitation — filter in your client | | Any of this on a **stream** | `selectWhere` is absent from all four subscription cubes | Query only — see [what does not survive streaming](/docs/subscriptions/what-does-not-survive-streaming/) | ### Ranking by a ratio server-side You cannot put a HAVING on a ratio, but you can compute and order by one: ```graphql ratio: calculate(expression: "sum(of: Volume_Usd) / count") n: count(selectWhere: { gt: "100" }) ``` `calculate` needs **at least one other aggregate in the same selection set** — alone, or beside a bare leaf field, it returns a ClickHouse error (`Column ... is not under aggregate function and not in GROUP BY`). Pair it with `orderBy` on its alias and a `selectWhere` floor on a separate aggregate, and only the final ratio cut happens in your client. ## Reproducibility near a threshold Aggregates built from floating-point sums — `sum`, and anything derived from it — vary in the last few significant figures between identical runs over a frozen window, because summation order across parallel shards is not fixed. A token sitting within about 0.01% of a **sum-based** threshold can therefore move in and out of the result between runs. `count`, `uniq` and `maximum:` / `minimum:` selectors are exactly stable. Prefer a count-based or max-based threshold when a stable membership list matters. ## Related - [Trending Tokens (Multi-Chain)](/docs/trading/crypto-price-api/examples#trending-tokens-multi-chain) — a real multi-chain screener built with `selectWhere` - [Select by metric (general reference)](/docs/graphql/metrics/selectWhere/) - [Conditions with `if`](/docs/graphql/metrics/if/) - [Trading Data Overview](/docs/trading/trading-data-overview/) --- ## Creating a Discord Bot to Fetch Price Data URL: https://docs.bitquery.io/docs/usecases/discord-bot/ Build Creating a Discord Bot to Fetch Price Data: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Tutorial: Creating a Discord Bot to Fetch Price Data In this tutorial, we will walk you through the process of creating a Discord bot using the `discord.js` library. The bot will fetch price data using the dextrades API and respond to a specific command in a Discord server. ### Prerequisites Before we begin, make sure you have the following: - Node.js installed on your machine - A Discord account - A Bitquery API Token (you can sign up for a free API token at [Bitquery](https://ide.bitquery.io/)) #### Setting up the Bot Before we began coding, you need to create a bot on https://discord.com/developers/applications . - Once your application is created, navigate to the Bot tab and set the **MESSAGE CONTENT INTENT**. This permission is necessary for the bot to read and send messages. ![intent](/img/ApplicationExamples/discord-appn.png) - Next, navigate to the URL generator and set scope as `bot` as shown in the image below ![scope](/img/ApplicationExamples/discord-scope.png) - Finally, set the permissions you want the bot to have and copy the URL generated. Go to the URL and add the bot to the channel of your choice. ![permission](/img/ApplicationExamples/discord-permissions.png) You are now ready to write the code. ### Step 1: Setting Up the Project 1. Create a new directory for your project and navigate to it in your terminal. 2. Initialize a new Node.js project by running the command `npm init` and following the prompts. 3. Install the required dependencies by running the command `npm install discord.js axios dotenv`. 4. Create a new file named `index.js` and open it in your preferred code editor. ### Step 2: Importing Dependencies At the top of your `index.js` file, import the necessary dependencies: ```javascript const { Client, GatewayIntentBits } = require("discord.js"); const axios = require("axios"); require("dotenv").config(); ``` - The `discord.js` package provides a convenient interface for interacting with the Discord API. - The `axios` package is used to make HTTP requests to the Bitquery API. - The `dotenv` package allows us to load environment variables from a `.env` file. ### Step 3: Configuring the Discord bot Next, configure the Discord bot by creating a new instance of the `Client` class: ```javascript const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }); ``` - The `intents` property specifies which events the bot will listen to. In this case, we are listening for guild and message related events. ### Step 4: Adding Bot Token and API OAuth Token Retrieve your Discord bot token and Bitquery OAuth token. Replace the placeholders in the following code with your actual tokens: ```javascript const CLIENT_TOKEN = ""; ``` ### Step 5: Handling Bot Events Add the necessary event listeners to handle bot events: ``` client.on("debug", console.log); client.on("ready", () => { console.log(`Logged in as ${client.user.tag}!`); }); client.on("messageCreate", async (message) => { console.log("message", message.content); // Add your command logic here }); ``` - The `"debug"` event will log debug information to the console. - The `"ready"` event is triggered when the bot successfully logs in and is ready to receive commands. - The `"messageCreate"` event is triggered whenever a new message is sent in a guild. We will add our command logic here. ### Step 6: Fetching Price Data Inside the `"messageCreate"` event listener, add the code to fetch price data using the Bitquery API: ``` if (message.content === "price") { try { let data = JSON.stringify({ query: '{\n EVM(dataset: combined, network: eth) {\n buyside: DEXTrades(\n limit: {count: 1}\n orderBy: {descending: Block_Time}\n where: {Trade: {Buy: {Currency: {SmartContract: {is: "0x5283d291dbcf85356a21ba090e6db59121208b44"}}}}}\n ) {\n Block {\n Number\n Time\n }\n Transaction {\n From\n To\n Hash\n }\n Trade {\n Buy {\n Amount\n Buyer\n Currency {\n Name\n Symbol\n SmartContract\n }\n Seller\n Price\n }\n Sell {\n Amount\n Buyer\n Currency {\n Name\n SmartContract\n Symbol\n }\n Seller\n Price\n }\n }\n }\n }\n}\n', variables: "{}", }); let config = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", "X-API-KEY": API_KEY, Authorization: "Bearer YOUR_BEARER_TOKEN", }, data: data, }; axios .request(config) .then((response) => { const priceData = JSON.stringify(response.data.data.EVM.buyside[0]); const priceMessage = `Latest Price: ` + priceData; message.channel.send(priceMessage); }) .catch((error) => { console.log(error); }); } catch (error) { console.error("Error fetching price:", error); message.channel.send("Error fetching price."); } } ``` - Replace `'YOUR_GRAPHQL_QUERY'` with your actual GraphQL query. This query is used to fetch the latest price data. - Replace `'YOUR_BEARER_TOKEN'` with your actual Bearer token from Bitquery. ### Step 7: Logging in the Bot Finally, add the code to log in the bot using the Discord bot token: ``` client.login(CLIENT_TOKEN); ``` ### Step 8: Running the Bot Save the changes to your `index.js` file and run the command `node index.js` in your terminal to start the bot. ### Testing the Bot Type the message `price` in your chat. You will see the latest trade information on the chat. --- ## Creating a Heatmap for Ethereum Token Holders using Python URL: https://docs.bitquery.io/docs/usecases/tokenholder-heatmap/ Build Creating a Heatmap for Ethereum Token Holders using Python: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application. # Creating a Heatmap for Ethereum Token Holders using Python In this tutorial we will see how to visualize top token holders in a heatmap using the [Token Holder APIs](/docs/blockchain/Ethereum/token-holders/token-holder-api/) with Python. 1. **Import libraries**: Necessary Python libraries are imported including `requests` for making HTTP requests, `json` for parsing JSON data, `pandas` for data manipulation and analysis, `seaborn` and `matplotlib` for data visualization. ```python import requests import json import pandas as pd import seaborn as sn import numpy as np import matplotlib.pyplot as plt ``` 2. **Define API URL and headers**: The Bitquery Streaming API URL is defined along with necessary headers including content type, API key, and authorization token. ```python url = "https://streaming.bitquery.io/graphql" headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer ory_at_...' } ``` 3. **Create the payload**: The payload consists of a GraphQL query that fetches the top 50 token holders of a specific Bitfinex LEO Token (`0x2af5d2ad76741191d15dfe7bf6ac92d4bd912ca3`) on a specific date. You can read more about Token Holder APIs [here](/docs/blockchain/Ethereum/token-holders/token-holder-api/) ```python payload = json.dumps({ "query": "{\nEVM(dataset: archive, network: eth) {\nHolders(\ndate: \"2026-08-01\"\nwhere: {Currency: {SmartContract: {is: \"0x2af5d2ad76741191d15dfe7bf6ac92d4bd912ca3\"}}}\nlimit: {count: 50}\norderBy: {descending: Balance_Amount}\n) {\nHolder {\nAddress\n}\nBalance {\nAmount\n}\n}\n}\n}\n", "variables": "{}" }) ``` 4. **Make the API request**: A POST request is sent to the Bitquery Streaming API with the headers and payload. The response is then saved in the `response` variable. ```python response = requests.request("POST", url, headers=headers, data=payload) ``` 5. **Parse the response**: The JSON response is parsed and the relevant data is extracted into a pandas DataFrame. The balance amounts are converted to float type and a new column `Amount_Share` is created which represents the proportion of each holder's balance to the total balance. ```python data_json2 = response.json() holders = data_json2['data']['EVM']['Holders'] df_holders = pd.json_normalize(holders) df_holders['Balance.Amount'] = df_holders['Balance.Amount'].astype(float) total_amount = df_holders['Balance.Amount'].sum() df_holders['Amount_Share'] = df_holders['Balance.Amount'] / total_amount * 100 ``` 6. **Create aliases for Ethereum addresses**: To simplify the visualization, each unique Ethereum address is mapped to an alias. ```python aliases = {address: f"Ad_{i}" for i, address in enumerate(df_holders['Holder.Address'].unique())} df_holders['Alias'] = df_holders['Holder.Address'].map(aliases) ``` 7. **Reshape data**: The aliases and amount shares are reshaped into a 2D array preparing it to be rendered in a heatmap. ```python a = 10 b = 5 address_reshaped = df_holders['Alias'].values.reshape(a, b) amount_reshaped = df_holders['Amount_Share'].values.reshape(a, b) ``` 8. **Create annotations**: Annotations are created for the heatmap. These annotations consist of the pair of address alias and the corresponding amount share. ```python annotations = [ f"{pair}\n{value:.3f}%" for pair, value in zip(address_reshaped.flatten(), amount_reshaped.flatten()) ] annotations = np.array(annotations).reshape(a, b) ``` 9. **Plot the heatmap**: Lastly, a heatmap is plotted using seaborn. The x and y labels represent the pairs of addresses, the color intensity indicates the amount share, and the annotations show the exact value of the amount share for each pair. The title of the plot is 'Token Holder Volume Percentage'. ```python plt.figure(figsize=(10, 8)) sn.heatmap(amount_reshaped, annot=annotations, fmt="", cmap='YlOrRd', cbar_kws={'label': 'Holder Volume %'}) plt.title('Token Holder Volume Percentage') plt.show() ``` Please note that you will need to replace the placeholder values in the script with your actual authorization token, and the smart contract address of the token you are interested in. Also, the reshaping dimensions (a=10, b=5) might need to be adjusted based on the number of token holders. This is how it will look finally, you can change the heatmap palette based on your design. ![heatmap](/img/ApplicationExamples/heatmap.png) --- ## Crypto Currency Price API URL: https://docs.bitquery.io/docs/trading/crypto-price-api/currency/ Query currency-level crypto prices and conversions with Bitquery Trading APIs, including GraphQL examples and streaming options. # Currency Cube The Currency Cube provides a unified, chain-agnostic price for an asset in USD, such as Bitcoin by aggregating prices and volumes from all its representations (e.g., WBTC, cbBTC, and other bridged or wrapped forms) across all supported chains. This multi-chain cryptocurrency price data approach ensures consistent pricing across different blockchain implementations. :::note Use this for cross-chain assets, not for a single token Currency prices aggregate **across chains and token representations**, so use this cube when you want one global number for an asset like BTC or ETH. For the price of a **specific token on a specific market**, use the [Pairs cube with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price). Note that `Ranking` is **not** available on `Currencies` — it exists on `Trades`, `Pairs`, and `Tokens` only. ::: ### How OHLC is Calculated The OHLC values (Open, High, Low, Close) describe a **single computed cross-chain index series** for the asset over the selected interval (e.g., 60 seconds). Within each interval the cube tracks the decay-weighted, volume-weighted blend of the asset's token representations — see [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm) — and records that blend's open, high, low and close: - Open: the index value at the start of the interval. - High: the highest value **the index** reached during the interval. - Low: the lowest value **the index** reached during the interval. - Close: the index value at the end of the interval. :::caution The index is not a min/max envelope across chains `High` and `Low` are **not** the maximum and minimum of the per-chain prices. Because the index is a volume-weighted blend, an extreme print on a single chain — especially a thin one — is damped and may never reach the currency candle at all. A single representation can print far outside the currency's High/Low band without moving it. Individual chain representations can therefore sit **outside** the currency's High/Low band. If you are monitoring a depeg or a single-venue dislocation, query the constituents through [`Tokens`](/docs/trading/crypto-price-api/tokens) filtered on `Currency.Id` rather than relying on the currency candle to surface it. ::: **Volume.Quote vs Volume.Usd**: For USD-based pricing, `Volume.Quote` is the sum of quote token amounts (not USD). Use `Volume.Usd` for USD totals. See [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm) for details. ```graphql { Trading { Currencies( where: { Currency: { Id: { is: "bid:bitcoin" } }, Interval: { Time: { Duration: { eq: 60 } } } }, limit: { count: 1 }, orderBy: { descending: Block_Time } ) { Currency { Id Name Symbol } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base BaseAttributedToUsd Quote # Sum of quote token amounts (not USD); use Usd for USD totals (March 11 2026: see Price Index Algorithm) 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 } } } } } ``` ### Supply - **`Supply`**: Currency-level supply and USD valuation metrics for the asset (aligned with the same `Supply` fields on Tokens and Pairs). See [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for definitions of each subfield. --- ## Crypto MarketCap API & Market Data URL: https://docs.bitquery.io/docs/trading/crypto-price-api/crypto-marketcap-api/ Crypto MarketCap API & Market Data via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. # Crypto MarketCap API and Market Data The **Crypto MarketCap API** is part of the Crypto Price APIs: you can **query** USD **market capitalization** and related **supply** fields, or **stream** them in **real time** for many chains using GraphQL **subscriptions** or the **`trading.prices`** Kafka topic described in the [Crypto Price API introduction](/docs/trading/crypto-price-api/). Those metrics are returned on the **`Supply`** object on **Currencies**, **Tokens**, and **Pairs** rows (`MarketCap`, `CirculatingSupply`, `TotalSupply`, and others). Field semantics are documented in the [Supply fields reference](/docs/trading/crypto-price-api/supply-fields). For intervals, cubes, and streaming setup, use the [Crypto Price API introduction](/docs/trading/crypto-price-api/). ## How do I get the USD market cap of a single token? {#how-do-i-get-usd-market-cap-of-a-single-token} Set token **address** and read **`Supply.MarketCap`** from the query below. See the [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for related supply fields. You can also stream this in real-time by adding the keyword "subscription" at the top. [Run query ➤](https://ide.bitquery.io/marketcap-of-pump-token) ```graphql { Trading { Tokens( where: { Token: { Address: { is: "pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn" } } Interval: { Time: { Duration: { eq: 1 } } } } limit: { count: 1 } orderBy: { descending: Block_Time } ) { Token { Address Id IsNative Name Network Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } Supply { TotalSupply MarketCap FullyDilutedValuationUsd } } } } ``` ## How do I list top tokens by market cap on a blockchain? {#how-do-i-list-top-tokens-by-market-cap-on-a-blockchain} In this API, we fetch top tokens by MarketCap on Solana. Change Network fields to get top tokens for a different network or remove to get top tokens across all. We also add `Volume: {Usd: {gt: 1000}` filter to remove low-volume tokens. See the [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for related supply fields. You can also stream this in real-time by adding the keyword "subscription" at the top. [Run query ➤](https://ide.bitquery.io/top-tokens-by-mcap-on-Solana-vol-gt-1000-USD) ```graphql { Trading { Tokens( limit: { count: 50 } limitBy: { count: 1, by: Token_Id } orderBy: { descending: Supply_MarketCap } where: { Interval: { Time: { Duration: { eq: 1 } } } Volume: { Usd: { gt: 1000 } } Token: { Network: { is: "Solana" } } } ) { Currency { Id Name Symbol } Price { Average { Mean(maximum: Block_Time) } } Volume { Base(maximum: Block_Time) Quote(maximum: Block_Time) Usd(maximum: Block_Time) } Token { Network Symbol Address } Supply { MarketCap(maximum: Block_Time) FullyDilutedValuationUsd(maximum: Block_Time) TotalSupply(maximum: Block_Time) } } } } ``` ## How do I query total supply and circulating supply for a cryptocurrency? {#how-do-i-query-total-supply-and-circulating-supply} Set a **currency id** on **Currencies** and read **`Supply.TotalSupply`** and related fields from the query below. See the [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for related supply fields. You can also stream this in real-time by adding the keyword "subscription" at the top. ```graphql { Trading { Currencies( limit: { count: 1 } orderBy: { descending: Block_Time } where: { Currency: { Id: { is: "pump" } } Interval: { Time: { Duration: { eq: 60 } } } } ) { Currency { Id Name Symbol } Supply { TotalSupply CirculatingSupply MaxSupply MarketCap FullyDilutedValuationUsd } } } } ``` ## How do I rank tokens by 1-hour market cap change? {#how-do-i-rank-tokens-by-1-hour-market-cap-change} Set **`Token: { Network: { is: "..." } }`** and a **1 hour** interval and read ranked **`change_mcap`** from the query below. See the [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for related supply fields and [expressions](/docs/graphql/capabilities/expression/) for `calculate`. You can also stream this in real-time by adding the keyword "subscription" at the top. [Run query ➤](https://ide.bitquery.io/top-tokens-by-mcap-change-1h-on-Solana) ```graphql { Trading { Tokens( limit: { count: 50 } orderBy: { descendingByField: "change_mcap" } where: { Interval: { Time: { Duration: { eq: 3600 } } } Token: { Network: { is: "Solana" } } } ) { Currency { Id Name Symbol } Token { Network Symbol Address } Supply { MarketCap FullyDilutedValuationUsd CirculatingSupply TotalSupply MaxSupply } change_mcap: calculate( expression: "($Price_Ohlc_Close-$Price_Ohlc_Open) * Supply_TotalSupply" ) Price { Ohlc { Open Close } } } } } ``` ## Crypto Market Data APIs — OHLCV, K-Line, Charts, Token Prices, Volume, Supply This section aggregates commonly used queries for market data across chains, pairs, and exchanges. These examples show how to use Bitquery’s **real-time crypto price API** for getting crypto market data. You can also use the chat agent at the bottom of the docs site—it is trained on our documentation and IDE queries. ## How do I stream aggregated OHLC and market cap for all tokens on one chain? {#how-do-i-stream-ohlc-and-marketcap-on-one-chain} Subscribe to **`Trading.Tokens`** with a **network** and **interval** to stream OHLC, volume, and **`Supply`** (including **MarketCap**) for many tokens on that chain in one feed. The following stream provide 60 second aggregated data. For information on all available time intervals, see the supported intervals documentation [here](/docs/trading/crypto-price-api/#supported-time-intervals). [Run Query](https://ide.bitquery.io/Aggregated-Price-of-all-tokens-in-real-time-on-one-chain_1)
Click to expand GraphQL query ```graphql subscription { Trading { Tokens( where: { Token: { Network: { is: "Solana" } } Interval: { Time: { Duration: { eq: 60 } } } } ) { Token { Id Symbol Network } Interval { Time { Start Duration End } } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { Ohlc { Open High Low Close } } } } } ```
## How do I stream OHLC for a token pair across chains? {#how-do-i-stream-ohlc-for-a-token-pair-across-chains} Use **`Trading.Pairs`** with **`Currency`** and **`QuoteCurrency`** ids and your desired **interval** (for example 1s) to stream OHLC, volume, and supply metrics for that pair. [Run Query](https://ide.bitquery.io/Token-OHLC-Stream-1-second-Multi-Chains)
Click to expand GraphQL query ```graphql subscription { Trading { Pairs( where: { Interval: { Time: { Duration: { eq: 1 } } } Currency: { Id: { is: "bid:eth" } } QuoteCurrency: { Id: { is: "usdc" } } } ) { Token { Symbol } QuoteToken { Symbol } Interval { Time { Start } } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { Ohlc { Open High Low Close } } } } } ```
## How do I get 5-minute price change % for high-liquidity tokens? {#how-do-i-get-5-minute-price-change-percent} Query **`Trading.Tokens`** with a **300s** interval, filter by **minimum USD volume**, and use **`calculate`** on OHLC open/close to derive **% change**—ideal for short-term movers and dashboards. [Run Query](https://ide.bitquery.io/5-minute-price-change-api)
Click to expand GraphQL query ```graphql { Trading { Tokens( limit: { count: 10 } orderBy: { descendingByField: "change" } where: { Volume: { Usd: { gt: 100000 } } Interval: { Time: { Duration: { eq: 300 } } } } ) { Token { Address Did Id IsNative Name Network 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") } } } ```
## Recommended: stream trades via `Trading.Trades` For real-time multi-chain trades **with USD price, market cap, and supply on every row** (MEV-filtered, 10 chains in one stream), subscribe to [`Trading.Trades`](/docs/trading/crypto-trades-api/trades-api) — run it [in the IDE](https://ide.bitquery.io/Trading-API-Live-Trades-All-Chains). The chain-level `DEXTrades` subscriptions below are for per-protocol/pool detail and transaction context. ```graphql subscription { Trading { Trades { Block { Time } Price PriceInUsd AmountsInUsd { Base Quote } Trader { Address } Pair { Token { Symbol Network } QuoteToken { Symbol } Market { Protocol Network } } } } } ``` ## How do I subscribe to latest Solana DEX trades in real time? {#how-do-i-subscribe-to-latest-solana-dex-trades} Stream **`Solana { DEXTrades { … } }`** as a **subscription** to receive each swap with **buy/sell**, **price in USD**, and **DEX protocol** as trades are indexed. [Run Query](https://ide.bitquery.io/solana-trades-subscription_3)
Click to expand GraphQL query ```graphql subscription { Solana { DEXTrades { Block { Time Slot } Transaction { Signer Signature Index Result { Success } } Trade { Index Dex { ProgramAddress ProtocolFamily ProtocolName } Buy { Amount AmountInUSD Account { Address Owner } Currency { MintAddress Decimals Symbol Name } Price PriceInUSD } Market { MarketAddress } Sell { Amount AmountInUSD Account { Address Owner } Currency { Decimals Symbol Name } Price PriceInUSD } } } } } ```
## How do I stream Ethereum DEX trades in real time? {#how-do-i-stream-ethereum-dex-trades-in-real-time} Use **`EVM(network: eth) { DEXTrades { … } }`** in a **subscription** to stream decoded swaps with **transaction**, **log**, and **buy/sell** legs for EVM DEX activity. [Run Query](https://ide.bitquery.io/Ethereum-dextrades)
Click to expand GraphQL query ```graphql subscription MyQuery { EVM(network: eth) { DEXTrades { Block { Time Number } Transaction { Hash } Log { Index SmartContract Signature { Signature Name } } Trade { Sender Buy { Buyer AmountInUSD Amount Seller PriceInUSD Price Currency { Name Symbol SmartContract } } Dex { SmartContract ProtocolName ProtocolVersion } Sell { Buyer AmountInUSD Amount Seller PriceInUSD Price Currency { Name Symbol SmartContract } } } } } } ```
## How do I stream BSC (PancakeSwap) DEX trades? {#how-do-i-stream-bsc-pancakeswap-dex-trades} Subscribe to **`EVM(network: bsc).DEXTrades`** for **PancakeSwap** and other BSC DEX fills with **block**, **receipt**, and **trade** details. [Run Query](https://ide.bitquery.io/Latest-BSC-PancakeSwap-v3-dextrades---Stream)
Click to expand GraphQL query ```graphql subscription { EVM(network: bsc) { DEXTrades { Block { Time Number } Receipt { ContractAddress Status } TransactionStatus { Success } Log { Signature { Name } } Trade { Dex { ProtocolName SmartContract OwnerAddress } Buy { Amount PriceInUSD Price Currency { Name Symbol SmartContract } } Sell { Amount PriceInUSD Price Currency { Name Symbol SmartContract } } } } } } ```
## How do I stream Uniswap v1, v2, and v3 trades on Ethereum? {#how-do-i-stream-uniswap-v1-v2-v3-trades-on-ethereum} Filter **`DEXTrades`** with **`ProtocolName` in `uniswap_v3`, `uniswap_v2`, `uniswap_v1`** to stream only **Uniswap** family pools on **mainnet**. [Run Query](https://ide.bitquery.io/uniswap-all-versions-trades-stream)
Click to expand GraphQL query ```graphql subscription { EVM(network: eth) { DEXTrades( where: { Trade: { Dex: { ProtocolName: { in: ["uniswap_v3", "uniswap_v2", "uniswap_v1"] } } } } ) { Block { Time } Trade { Dex { ProtocolName } Buy { Amount Currency { Symbol } } Sell { Amount Currency { Symbol } } } } } } ```
## How do I stream 1-second OHLC for a specific pair (e.g. BTC/USDT)? {#how-do-i-stream-1-second-ohlc-for-a-trading-pair} **`Trading.Pairs`** with **1s** **`Interval`** streams open/high/low/close, **volume**, and **market cap** fields for the selected **base/quote** currency ids. [Run Query](https://ide.bitquery.io/Token-OHLC-Stream-1-second-Multi-Chains)
Click to expand GraphQL query ```graphql subscription { Trading { Pairs( where: { Interval: { Time: { Duration: { eq: 1 } } } Currency: { Id: { is: "bid:bitcoin" } } QuoteCurrency: { Id: { is: "usdt" } } } ) { Market { Name Network Address } Token { Symbol } QuoteToken { Symbol } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { Ohlc { Open High Low Close } } } } } ```
## How do I find cross-chain price arbitrage for the same pair? {#how-do-i-find-cross-chain-price-arbitrage} Compare **`Trading.Pairs`** rows for the same **currency** / **quote** across **markets** and **networks**; use **`limitBy`** on **market address** to sample best venues for **arbitrage** research. [Run Query](https://ide.bitquery.io/Find-arbitrage-opportunity-with-same-token-across-chains)
Click to expand GraphQL query ```graphql { Trading { Pairs( where: { Currency: {Id: {is: "bid:bitcoin"}} QuoteCurrency: {Id: {is: "usdt"}} } limit: {count: 10} orderBy: {descending: Block_Time} limitBy: {by: Market_Address, count: 1} ) { Currency { Name Id } Market { Name NetworkBid Network Address } Price { IsQuotedInUsd Average { Mean } } QuoteCurrency { Id Symbol Name } QuoteToken { Symbol Name Id NetworkBid Network Did Address } Token { Name Id NetworkBid } } } } ```
## How do I stream second-level Uniswap pair OHLC (K-line)? {#how-do-i-stream-uniswap-pairs-ohlc-seconds} Subscribe to **`Trading.Pairs`** filtered by **Uniswap protocols** and **1s** interval to power **sub-minute** charts and **HFT** analytics. [Run Query](https://ide.bitquery.io/Stream-all-Uniswap-Seconds-OHLC-Kline)
Click to expand GraphQL query ```graphql subscription { Trading { Pairs( where: {Market: {Protocol: {in: ["uniswap_v3", "uniswap_v2"]}}, Interval: {Time: {Duration: {eq: 1}}}} ) { Currency { Name Id } Market { Name NetworkBid Network Address } Price { IsQuotedInUsd Average { Mean } } QuoteCurrency { Id Symbol Name } QuoteToken { Symbol Name Id NetworkBid Network Did Address } Token { Name Id NetworkBid } } } } ```
## How do I stream PancakeSwap v3 liquidity adds on BSC? {#how-do-i-stream-pancakeswap-v3-liquidity-adds-on-bsc} Listen to **`Mint`** logs on the **PancakeSwap v3** pool manager contract to catch **new liquidity** added to pools on **BNB Chain**. [Run Query](https://ide.bitquery.io/Stream---Liqiidity-add-for-all-tokens-on-PancakeSwap-v3)
Click to expand GraphQL query ```graphql subscription { EVM(network: bsc) { Events( orderBy: [ { descending: Block_Time } { descending: Transaction_Index } { descending: Log_Index } ] where: { Log: { Signature: { Name: { is: "Mint" } } } Transaction: { To: { is: "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364" } } } ) { Block { Time Number Hash } Receipt { ContractAddress } Topics { Hash } TransactionStatus { Success } LogHeader { Address Index Data } Transaction { Hash From To } Log { EnterIndex ExitIndex Index LogAfterCallIndex Pc SmartContract Signature { Name Signature } } Arguments { Name Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } } } } ```
## How do I stream PancakeSwap v3 liquidity removals on BSC? {#how-do-i-stream-pancakeswap-v3-liquidity-removals-on-bsc} Filter **`Burn`** signatures on the same **PancakeSwap v3** manager to track **liquidity withdrawals** in real time. [Run Query](https://ide.bitquery.io/Stream---Liquidity-remove-for-all-tokens-on-PancakeSwap-v3)
Click to expand GraphQL query ```graphql subscription { EVM(network: bsc) { Events( orderBy: [ { descending: Block_Time } { descending: Transaction_Index } { descending: Log_Index } ] where: { Log: { Signature: { Name: { is: "Burn" } } } Transaction: { To: { is: "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364" } } } ) { Block { Time Number Hash } Receipt { ContractAddress } Topics { Hash } TransactionStatus { Success } LogHeader { Address Index Data } Transaction { Hash From To } Log { EnterIndex ExitIndex Index LogAfterCallIndex Pc SmartContract Signature { Name Signature } } Arguments { Name Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } } } } ```
## How do I stream Raydium liquidity adds on Solana? {#how-do-i-stream-raydium-liquidity-adds-on-solana} Subscribe to **`Solana.DEXPools`** with **Raydium’s program** and **positive base change** to detect **new liquidity** deposited into **Raydium** pools. [Run Query](https://ide.bitquery.io/liquidity-addition-for-radium_1)
Click to expand GraphQL query ```graphql subscription { Solana { DEXPools( where: { Pool: { Dex: { ProgramAddress: { is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" } } Base: { ChangeAmount: { gt: "0" } } } } ) { Pool { Market { MarketAddress BaseCurrency { MintAddress Symbol Name } QuoteCurrency { MintAddress Symbol Name } } Dex { ProtocolFamily ProtocolName } Quote { PostAmount PriceInUSD PostAmountInUSD } Base { ChangeAmount PostAmount } } } } } ```
## How do I stream Raydium liquidity removals on Solana? {#how-do-i-stream-raydium-liquidity-removals-on-solana} Use the same **Raydium** program filter with **negative** **`ChangeAmount`** on the base side to stream **liquidity withdrawals**. [Run Query](https://ide.bitquery.io/liquidity-removal-for-radium_1)
Click to expand GraphQL query ```graphql subscription { Solana { DEXPools( where: { Pool: { Dex: { ProgramAddress: { is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" } } Base: { ChangeAmount: { lt: "0" } } } } ) { Pool { Market { MarketAddress BaseCurrency { MintAddress Symbol Name } QuoteCurrency { MintAddress Symbol Name } } Dex { ProtocolFamily ProtocolName } Quote { PostAmount PriceInUSD PostAmountInUSD } Base { ChangeAmount PostAmount } } } } } ```
## How do I stream Orca Whirlpool liquidity adds on Solana? {#how-do-i-stream-orca-whirlpool-liquidity-adds} Filter **`DEXPools`** by **Orca Whirlpool program address** and **positive** base **change** to stream **liquidity deposits** into **Whirlpool** pools. [Run Query](https://ide.bitquery.io/liquidity-addition-for-orca-whirlpool_1)
Click to expand GraphQL query ```graphql subscription { Solana { DEXPools( where: { Pool: { Dex: { ProgramAddress: { is: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc" } } Base: { ChangeAmount: { gt: "0" } } } } ) { Pool { Market { MarketAddress BaseCurrency { MintAddress Symbol Name } QuoteCurrency { MintAddress Symbol Name } } Dex { ProtocolFamily ProtocolName } Quote { PostAmount PriceInUSD PostAmountInUSD } Base { ChangeAmount PostAmount } } } } } ```
## How do I stream Orca Whirlpool liquidity removals on Solana? {#how-do-i-stream-orca-whirlpool-liquidity-removals} With **Orca’s program** and **negative** base **change**, stream **liquidity removals** from **Whirlpool** markets. [Run Query](https://ide.bitquery.io/liquidity-removal-for-orca-whirlpool_1)
Click to expand GraphQL query ```graphql subscription { Solana { DEXPools( where: { Pool: { Dex: { ProgramAddress: { is: "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc" } } Base: { ChangeAmount: { lt: "0" } } } } ) { Pool { Market { MarketAddress BaseCurrency { MintAddress Symbol Name } QuoteCurrency { MintAddress Symbol Name } } Dex { ProtocolFamily ProtocolName } Quote { PostAmount PriceInUSD PostAmountInUSD } Base { ChangeAmount PostAmount } } } } } ```
## How do I query Uniswap Router Burn events (liquidity removed) on Ethereum? {#how-do-i-query-uniswap-router-burn-liquidity-removed} Query **`EVM(network: eth, dataset: combined).Events`** for **`Burn`** logs on the **Uniswap V2 Router** to track **liquidity** pulled from **pairs**. [Run Query](https://ide.bitquery.io/uniswap-v2-liquidity-removed)
Click to expand GraphQL query ```graphql { EVM(network: eth, dataset: combined) { Events( limit: {count: 100} where: {Transaction: {To: {is: "0x7a250d5630b4cf539739df2c5dacb4c659f2488d"}}, Log: {Signature: {Name: {in: ["Burn"]}}}} ) { Transaction { Hash From To } Block { Number } Log { Signature { Name } SmartContract } Transaction { From To Type } LogHeader { Address Index } Arguments { Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_Boolean_Value_Arg { bool } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } } Name } } } } ```
## How do I find addLiquidityETH calls for a token pair on Ethereum? {#how-do-i-find-addliquidityeth-calls-on-ethereum} Use **`EVM(dataset: archive).Calls`** with **`Call.Signature.Name`** **`addLiquidityETH`** and **argument** filters to audit **liquidity adds** routed through the **router** for a given **token** and **tx** pattern. [Run Query](https://ide.bitquery.io/addLiquidityETH_function)
Click to expand GraphQL query ```graphql query MyQuery { EVM(dataset: archive, network: eth) { Calls( where: { Transaction: { Hash: { is: "0x60ce9acd0053f20092e7871868afe5187c95ff6d7750ad65a8d4ff99a052c357" } } Call: { Signature: { Name: { is: "addLiquidityETH" } } } Arguments: { length: { eq: 6 } includes: [ { Index: { eq: 0 } Value: { Address: { is: "0x9cbc0be914e480beee4014e190fdbfc48ed5a4a8" } } } { Index: { eq: 3 } Value: { BigInteger: { ge: "1000000000000000000" } } } { Index: { eq: 5 }, Value: { BigInteger: { ge: "1690878863" } } } ] } } limit: { count: 10 } ) { Arguments { Index Name Type Path { Name Index Type } Name Value { ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_Boolean_Value_Arg { bool } } } Call { Signature { Name Signature } } } } } ```
## Related DEX, launchpad & trading APIs {#related-dex-launchpad-trading-apis} Use these pages when you need **protocol-specific** filters, **bonding curves**, **pool** events, or **full DEX** examples that pair with **Crypto Price** / **Trading** metrics above. ### Solana - [Pump.fun API](/docs/blockchain/Solana/Pumpfun/Pump-Fun-API) — live trades, OHLCV, ATH, market cap, bonding curve, migrations to PumpSwap - [PumpSwap API](/docs/blockchain/Solana/Pumpfun/pump-swap-api) — PumpSwap trades, prices, pools, historical + realtime datasets - [Raydium DEX API](/docs/blockchain/Solana/Solana-Raydium-DEX-API) — Raydium swaps, pools, and liquidity - [Orca DEX API](/docs/blockchain/Solana/solana-orca-dex-api) — Orca / Whirlpool trades and pools - [Jupiter API](/docs/blockchain/Solana/solana-jupiter-api) — Jupiter aggregator swaps, routing, limit orders - [Solana DEX trades (hub)](/docs/blockchain/Solana/solana-dextrades) — `DEXTrades`, `DEXTradeByTokens`, and chain-wide patterns - [gRPC: Pump.fun streams](/docs/grpc/solana/examples/pump-fun-grpc-streams) — low-latency CoreCast example ### BNB Chain (BSC) - [PancakeSwap API (BSC)](/docs/blockchain/BSC/pancake-swap-api) — PancakeSwap v2/v3 trades and pools - [PancakeSwap Infinity (BSC)](/docs/blockchain/BSC/bsc-pancakeswap-infinity-api) — Infinity pools and hooks (where documented) - [BSC DEX trades](/docs/blockchain/BSC/bsc-dextrades) — general BSC DEX query patterns - [Four.meme API](/docs/blockchain/BSC/four-meme-api) — Four.meme launchpad and bonding on BSC - [BSC mempool stream](/docs/blockchain/BSC/bsc-mempool-stream) — pending / pre-confirmation monitoring ### Ethereum & cross‑chain - [DEX API (Ethereum hub)](/docs/blockchain/Ethereum/dextrades/dex-api) — Uniswap-style DEX patterns on Ethereum - [Crypto Price API introduction](/docs/trading/crypto-price-api/) — Tokens, Pairs, Currencies cubes and Kafka `trading.prices` - [OHLC / K-line API](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api) — candlesticks and intervals for charting --- ## Crypto Price API: Real-Time Token Prices, OHLC and Market Cap URL: https://docs.bitquery.io/docs/trading/crypto-price-api/ Crypto price API for any token on ten chains: live and 30-day USD prices, OHLC from one second, market cap and supply via GraphQL, WebSocket and Kafka. # Crypto Price API: Real-Time Token Prices, OHLC and Market Cap The Bitquery Crypto Price API returns the price of any token that trades on-chain, not only listed coins: real-time and the last 30 days for Solana, Ethereum, BNB Smart Chain, Base, Arbitrum, Optimism, Polygon, Tron, Robinhood Chain and Arc, in USD or against any quote token. Every row carries OHLC for intervals from one second up, mean and moving-average prices, volume, market cap and supply, over GraphQL queries, WebSocket subscriptions and Kafka. Prices are built from DEX trades with outlier filtering, and a cross-chain view aggregates the same asset across networks, so `bid:bitcoin` returns one Bitcoin price. Every query below runs as written in the [Bitquery IDE](https://ide.bitquery.io). For older candles, use [DEXTradeByTokens](/docs/cubes/dextradesbyTokens) or the [Solana historical page](/docs/blockchain/Solana/historical-aggregate-data/). :::tip Real-time + last ~30 days only The Crypto Price API cubes (`Trading.Tokens`, `Trading.Currencies`, `Trading.Pairs`) are part of the **Trading cube** family — built for **real-time and the last ~30 days**. For OHLC built from raw trades **older than ~30 days**, use chain-level [`DEXTradeByTokens`](/docs/cubes/dextradesbyTokens) instead. See the [**Trading Data Overview**](/docs/trading/trading-data-overview) for the side-by-side comparison. For a product-level overview — supported chains, OHLCV, USD pricing and plans — see the [Crypto Price API](https://bitquery.io/products/crypto-price-api) page. ::: Bitquery provides **Crypto Price APIs and Streams** via GraphQL and [Kafka](https://github.com/bitquery/streaming_protobuf/blob/feature/trading/market/price_index.proto). This **cryptocurrency price API** delivers comprehensive pricing data across multiple blockchains. These tools let you stream and query aggregated price data in USD or other paired currencies based on time and volume for all tokens across EVM, Solana, Tron, and other supported chains. Our **crypto prices api** is the **main** source for **real-time** pricing and **OHLC**; for **historical** OHLC from DEX trades, use **`DEXTradeByTokens`** as described in the [OHLC documentation](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/). The Crypto Price API provides more than just pair prices, it also includes [OHLCV (K-Line)](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/), Simple Moving Averages (SMA), Weighted Moving Averages (WMA), mean prices, and other key price-related statistics. While you can access the aggregated price for a single trading pair, the Price Index also supports cross-chain and cross-DEX aggregation, giving you a unified view of token prices across multiple ecosystems. **OHLC & K-lines:** Use this **Crypto Price API** as the **primary** source. For **historical** OHLC (long backfills, archive data, or trade-built candles), use **`DEXTradeByTokens`** (see [OHLC guide — when to use DEX](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/#crypto-price-api-vs-dextradebytoken)). ## Key Features of These APIs - **Pre-aggregated data**: OHLC, SMA, WMA, EMA, and mean prices calculated automatically - **Real-time streaming**: 1-second granularity via GraphQL subscriptions and Kafka - **Multi-chain support**: Ethereum, Solana, BSC, Arbitrum, Base, Optimism, Polygon, Tron, Robinhood and Arc - **Clean data**: Automatic filtering of low-quality trades and outliers - **Cross-chain aggregation**: Unified view of token prices across multiple ecosystems ## List of Supported Networks - Arbitrum - Base - Matic - Ethereum - Solana - Binance Smart Chain - Tron - Optimism - Robinhood - Arc :::tip Faster queries: filter with `NetworkBid` instead of `Network` When you scope **Tokens**, **Pairs**, or related **Trading** queries by chain, prefer **`NetworkBid`** over the **`Network`** field in your `where` clause for lower latency. Use the same **`bid:`** convention as **`Token.Id`**. | Network Name | `NetworkBid` Value | | ------------------- | ------------------ | | Solana | `bid:solana` | | Ethereum | `bid:eth` | | Binance Smart Chain | `bid:bsc` | | Base | `bid:base` | | Arbitrum | `bid:arbitrum` | | Matic (Polygon) | `bid:matic` | | Tron | `bid:tron` | | Optimism | `bid:optimism` | | Robinhood | `bid:robinhood` | | Arc | `bid:arc` | ::: ## Available Endpoints - **[Streaming Endpoint](https://ide.bitquery.io/?endpoint=https://streaming.bitquery.io/graphql)** - **[NPM SDKs](https://www.npmjs.com/package/bitquery-crypto-price)** ## Quick Start {#quick-start} :::tip Pricing one specific token? Start with Pairs + rank 1 The stream below is a **firehose** of every token on every chain, and the `Tokens` cube is the right choice for it. If instead you want the price of **one token**, query the [**Pairs** cube with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) — it returns the price from the token's **top market** rather than a blend across all of its pools, which matters for tokens with fragmented liquidity. ::: [Run Stream >](https://ide.bitquery.io/1-second-crypto-price-stream) > Note: A `Volume: {Usd: {gt: 5}}` filter is applied to remove extreme outliers; the price stream already pre-filters outliers—this is an additional check. ```graphql subscription { Trading { Tokens( where: { Interval: { Time: { Duration: { eq: 1 } } } Volume: { Usd: { gt: 5 } } } ) { Token { Address Id IsNative Name Network Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Supply { MarketCap FullyDilutedValuationUsd CirculatingSupply TotalSupply MaxSupply } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } } } } ``` ## What is the Crypto Price API (Price Index) and Why Does It Matter? The Crypto price API/Stream (Price Index) provides real-time, aggregated price data with ultra-low latency across multiple trading pairs, tokens, decentralized exchanges (DEXs), and blockchains. It's the first product of its kind, designed to help developers access accurate, up-to-the-second prices for building financial applications. This real-time crypto price API delivers multi-chain cryptocurrency price data essential for trading bots, DeFi protocols, and blockchain price feed applications. Whether you're looking for a **free crypto price api** for development or production-grade pricing data, our platform offers flexible access to comprehensive market data. ### Data Processing Pipeline 1. **Data Ingestion**: Onchain data is collected and parsed from supported DEXs across all chains 2. **Quality Filtering**: Trades with zero amount or below decimal precision (e.g. < 10^decimals/10,000) are filtered out. See [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm) for details. 3. **Aggregation**: Price data is aggregated by time intervals (1s, 3s, 5s, etc.) and volume thresholds 4. **Metric Calculation**: OHLC, moving averages, and other statistical measures are pre-calculated 5. **Distribution**: Data is made available via GraphQL subscriptions and Kafka streams This API serves as the foundational layer of our trading-focused data suite, designed to provide developers with accurate, up-to-the-second prices for building financial applications. ## Technical Specifications ### Data Format - **Response Format**: GraphQL JSON or Protobuf ( For Kafka) - **Streaming Protocol**: WebSocket (GraphQL subscriptions) or Kafka Stream in Protobuf - **Update Frequency**: Real-time with 1-second granularity - **Data Retention**: Rolling ~30 days of historical data available for querying; older history via chain-level [`DEXTradeByTokens`](/docs/trading/trading-data-overview/) ### Supported Price Metrics - **OHLC**: Open, High, Low, Close prices - **Moving Averages**: Simple (SMA), Weighted (WMA), Exponential (EMA) - **Volume**: Base token, Quote token, USD equivalent - **Price Statistics**: Mean, median, weighted average ### Supported Supply Metrics - Market Cap - Fully Diluted Valuation Usd - Circulating Supply - Total Supply - Max Supply ### Moving Average Calculation Window All moving averages (Simple Moving Average, Weighted Moving Average, and Exponential Moving Average) use a **window size of 5 entries** for calculation, regardless of the time interval: - **1-second interval**: 5 entries of 1 second each - **60-second interval**: 5 entries of 60 seconds each - **3600-second interval**: 5 entries of 3600 seconds (1 hour) each This consistent window size ensures comparable moving average calculations across all time intervals. ## Accessing the API Crypto Price API stream with pre-calculated OHLC in the response 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 --- ## Crypto Profit and Loss Calculator URL: https://docs.bitquery.io/docs/usecases/p-l-product/overview/ Build a JavaScript crypto PnL calculator with Bitquery DEX trades for cost basis, proceeds, realized gains, and reporting. # Overview Profit and Loss calculation in crypto is not easy to build. However, it is an important metric while evaluating the financial performance of an asset. In this tutorial, we will see how to build a simple profit and loss calculator in Javascript using Bitquery's [DEXTrades API](/docs/blockchain/Ethereum/dextrades/dex-api/) ## Realized PnL We will be calculating realised profit and loss. Realized PnL is calculated after traders have sold their holdings of a token. Only the executed price of the orders is taken into account in realized PnL. For this purpose, we will the weighted average of the buy price(WABP) in USD. This is the formula we will be using: ``` WABP = sum(buyAmount*buyPriceInUSD)/sum(buyAmount) pnl = sum(sellAmount*(sellPriceInUSD-WABP)) ``` ### Example Scenario: An account buys and sells a token as follows: 1. **Buy Transactions**: - Buys 10 tokens at $5 each. - Buys 20 tokens at $6 each. 2. **Sell Transaction**: - Sells 15 tokens at $7 each. #### Step 1: Calculate the Weighted Average Buy Price (WABP) Using the formula: WABP = (Sum of each buy amount multiplied by its buy price in USD) divided by (Sum of all buy amounts). ``` WABP = ( (10 * 5) + (20 * 6) ) / (10 + 20) WABP = (50 + 120) / 30 WABP = 170 / 30 WABP = 5.67 ``` So, the weighted average buy price (WABP) is $5.67. #### Step 2: Calculate Realized PnL Using the formula: For the sell of 15 tokens at $7 each: ``` Realized PnL = Sum of each sell amount multiplied by (sell price in USD minus WABP). Realized PnL = 15 * (7 - 5.67) Realized PnL = 15 * 1.33 Realized PnL = 19.95 ``` ### Result: The realized PnL for this transaction is $19.95. Click [here](/docs/usecases/p-l-product/pnl) to get started with the project. ## Complete Video Tutorial --- ## Crypto Token Price API URL: https://docs.bitquery.io/docs/trading/crypto-price-api/tokens/ Fetch token USD prices, market stats, and history with Bitquery crypto price APIs using GraphQL queries and live subscriptions. # Tokens Cube :::tip Looking for one token's price? Use the Pairs cube with rank 1 The price on this cube is a **volume-weighted blend of every pool** where the token is the base asset. That is what you want for a chain-wide number or a firehose of all tokens on a chain. But for a **specific token** — especially one whose liquidity is spread across many pools — thin pools contribute to the blend and can pull the price away from where the token actually trades. For a single token's price, query the [Pairs cube with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) to get the quote from its top market. ::: The **Tokens** cube provides chain-specific, aggregated price and volume data for individual tokens. For a **query** example that returns tokens with volume and average price over the last 24h (including conditional metrics for 1h, 4h, 24h), see [Aggregated Token Data](https://ide.bitquery.io/aggregated-data) or the [Crypto Price API examples](/docs/trading/crypto-price-api/examples#aggregated-token-data-volume--price-last-24h). ### Fields in the Schema ```graphql subscription { Trading { Tokens(where: { Interval: { Time: { Duration: { eq: 60 } } } }) { Token { Address Id IsNative Name Network Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base # Volume of the token itself Quote # Volume of all tokens it traded against Usd # Combined USD volume across all trades } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { IsQuotedInUsd # Whether price values are in USD (true/false) Ohlc { Open High Low Close } Average { Mean SimpleMoving WeightedSimpleMoving ExponentialMoving } } Currency { Name Symbol Id } } } } ``` ### Key Points to Understand: - **What is the Tokens Cube?** The **Tokens** cube provides chain-specific, aggregated price and volume data for individual tokens. This includes OHLC values, moving averages, and volume across **all pairs** the token is traded with. - **Volume Section Explained:** - `Base`: Volume of the token itself (the token in question) for all pairs. - `Quote`: Sum of **quote token** amounts (the tokens it traded against). This is not USD—for USD amounts use `Usd`. (As of March 11 2026, see [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm).) - `Usd`: Total volume in USD. Use this when you need USD amounts. - **IsQuotedInUsd**: A boolean indicating whether the OHLC and average prices are expressed in USD (`true`) or in the quote token's value (`false`). - **Clarification on "Quote":** The **Tokens** cube **does not show the specific quote tokens** used in each trade. Instead, it aggregates across all pairs the token is involved in—regardless of which token acted as the quote in those trades. - If you need **pair-level granularity** (i.e., to know exactly which token was the quote in a specific pair), use the **Pairs Cube** instead. - **Where the blend can mislead:** because every pool contributes in proportion to its decay-weighted volume, tokens with **fragmented liquidity** (one primary pool plus a tail of thin ones) can report a price that drifts from the primary market. Check `Ranking.Weight` on the [Pairs cube](/docs/trading/crypto-price-api/pairs#most-accurate-token-price): a top-market weight near 1 means the blend is effectively one pool and this cube's price is equivalent; a low weight means you should price the token from its [rank-1 market](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) instead. - **`Supply`**: Currency-level metrics for the asset (aggregated across chains); price and volume on the row remain chain-specific. See [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for each subfield (`CirculatingSupply`, `TotalSupply`, `MaxSupply`, `MarketCap`, `FullyDilutedValuationUsd`). ## Field notes ### `Price.Average.Estimate` — the valuation price `Estimate` is the price the supply valuations are built from: `Supply.FullyDilutedValuationUsd` is `TotalSupply x Estimate`, and `MarketCap` is `CirculatingSupply x Estimate`. If you are reconciling a market cap, use `Estimate` — not `Mean`, and not `Ohlc.Close`. ### `Volume.BaseAttributedToUsd` The portion of base-token volume the index could attribute to a USD value. It is always **less than or equal to** `Volume.Base`; how often the two are equal varies by chain. Use it when you need volume you can trust a USD figure for. ### `Ranking` `Ranking.Position` and `Ranking.Weight` are present on `Tokens` as well as `Pairs`. See [how Position and Weight behave](/docs/trading/crypto-price-api/pairs#ranking-how-position-and-weight-behave). ### Identity fields | Field | Notes | | --- | --- | | `Token.Id` | Chain-qualified: `bid::
`. **A bare address returns 0 rows silently** — use `Token.Address` for a raw address | | `Token.Address` | The raw contract address. Empty for native assets | | `Token.NetworkBid` | Populated — the chain bid, e.g. `bid:solana` | | `Token.Did` | A **chain-level** DID, empty on essentially every row. Not a per-token identifier | | `Token.Decimals` | **Does not exist** on this cube | `Price.IsQuotedInUsd` is a constant `true` here — filtering on `false` returns nothing. :::caution `(Token.Id, Interval.Time.Start)` is not a unique key A small share of mints return **two rows** for the same token and bar. The duplicates are identical on identity and ranking and differ only in mutable metadata — typically one row has an empty `Token.Symbol` while the other has arrived — which is a metadata-arrival split on freshly launched tokens. Aggregate by `Token.Id` rather than treating one row as one token. Do not build a natural key from metadata columns: `Symbol` can change. ::: --- ## Crypto Tokens OHLC Candle K-Line API URL: https://docs.bitquery.io/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/ Crypto Tokens OHLC Candle K-Line API via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. # Crypto Tokens OHLC Candle K-Line API - Real-Time & Historical Price Data Get real-time and historical OHLC (Open, High, Low, Close) candle data, K-line charts, and price analytics for crypto tokens across supported blockchains including Ethereum, Solana, BSC, Polygon, Tron, Robinhood and Arc. **Recommendation:** Use the **[Crypto Price API](/docs/trading/crypto-price-api/)** as your **main source** for OHLC and K-line data—**real-time streams** and **pre-aggregated** bars (low-latency, easy to use). When you need **full historical OHLC** (deep backfill, archive ranges, or candles built from raw DEX trades), use **`DEXTradeByTokens`** on **`EVM`** or **`Solana`** and aggregate trades into candles—see [comparison below](#crypto-price-api-vs-dextradebytoken). ## How do I get OHLCV data for a token using Bitquery? {#how-do-i-get-ohlcv-data-for-a-token-using-bitquery} **Use the [Crypto Price API](/docs/trading/crypto-price-api/) first** (`Trading` → `Tokens`, `Pairs`, or `Currencies`): **pre-aggregated OHLC**, streaming, and intervals such as **1-second** and **1-minute**, with a **clean, multi-DEX index** view. **For historical OHLC** beyond what the Price API covers, switch to **`DEXTradeByTokens`** (or chain-specific DEX docs): bucket with **`Block { Time(interval: { count, in: minutes | hours | days }) }`** and derive open/high/low/close from **`PriceInUSD`** / **`Trade_Price`**. See [Crypto Price API vs DEXTradeByTokens](#crypto-price-api-vs-dextradebytoken) and the quick table below. ## OHLCV & price data — quick answers {#ohlcv-and-price-data-quick-answers} | Question | Where to read / run | |----------|---------------------| | How do I get OHLCV data for a token using Bitquery? | **Last ~30 days:** [Crypto Price API](/docs/trading/crypto-price-api/) ; **older history:** [DEXTradeByTokens OHLC](/docs/cubes/dextradesbyTokens/#how-do-i-get-ohlc-in-a-dextradebytokens-query) | | How do I get OHLC in a DEXTradeByTokens query? | [DEXTradeByTokens OHLC](/docs/cubes/dextradesbyTokens/#how-do-i-get-ohlc-in-a-dextradebytokens-query) (for **historical** OHLC or DEX-level control) | | How do I get historical OHLCV for a Solana token? | **Main OHLC:** [Crypto Price API — Quick start](/docs/trading/crypto-price-api/#quick-start) · [Tokens cube](/docs/trading/crypto-price-api/tokens/) · **Historical / DEX:** [Historical OHLCV on Solana](/docs/blockchain/Solana/solana-dextrades/#how-do-i-get-historical-ohlcv-for-a-solana-token) · [Solana OHLC API](/docs/blockchain/Solana/solana-dextrades/#solana-ohlc-api) | | How do I get the current price of a token using Bitquery API? | **Recommended:** [Pairs + rank 1 (top market)](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) · [Quick start](/docs/trading/crypto-price-api/#quick-start) · [Examples](/docs/trading/crypto-price-api/examples/) | | Which cube gives the most accurate price for one token? | [Pairs with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) — prices from the token's top market rather than a blend across all its pools | | How do I get 1-minute OHLC candles for a DEX pair? | **Main:** [Your first OHLC query](#your-first-ohlc-query) (`Duration: { eq: 60 }`) · [Pairs cube](/docs/trading/crypto-price-api/pairs/) · **Historical:** [DEX OHLC pattern](/docs/cubes/dextradesbyTokens/#how-do-i-get-ohlc-in-a-dextradebytokens-query) | | How do I get the all-time high (ATH) price of a token? | [Solana ATH example](/docs/blockchain/Solana/solana-dextrades/#get-ath-market-cap-of-tokens)| | Is there an API to get token price in USD on Solana? | [Pairs + rank 1 (top market)](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) · [Crypto Price API — Quick start](/docs/trading/crypto-price-api/#quick-start) · [Latest USD (Solana DEX trades)](/docs/blockchain/Solana/solana-dextrades/#latest-usd-price-of-a-token) | | How do I use DEXTradeByTokens vs DEXTrades for OHLCV? | [OHLCV: which cube?](/docs/cubes/dextradesbyTokens/#how-do-i-use-dextradebytokens-vs-dextrades-for-ohlcv) · [DEXTrades cube](/docs/cubes/dextrades/) | ## What is OHLC Data? OHLC (Open, High, Low, Close) data, also known as candlestick or K-line data, is the foundation of technical analysis in cryptocurrency trading. Each OHLC candle represents price movement over a specific time interval: - **Open**: The first price recorded in the interval - **High**: The highest price reached during the interval - **Low**: The lowest price reached during the interval - **Close**: The last price recorded in the interval Our [Crypto Price API](/docs/trading/crypto-price-api/) provides pre-aggregated OHLC data with ultra-low latency—**use it as the main source** for live charts and typical OHLC needs. **Historical OHLC:** For **deep history** or candles computed from **raw DEX trades**, use **[DEXTradeByTokens](/docs/cubes/dextradesbyTokens/)** on **EVM** or **Solana** (with `dataset: combined` or `archive` as needed). ## Getting Started ### **Quick Start Steps** 1. **Get API Key**: Sign up at [Bitquery IDE](https://ide.bitquery.io) to get your API key 2. **Choose your method** - **GraphQL queries**: For one-off APIs - **WebSocket Streams**: For real-time data feeds - **Kafka**: For high-throughput applications with high degree of relibility 3. **Select the Right Cube**: Choose between Currency, Tokens, or Pairs based on your needs 4. **Start with Examples**: Use our ready-to-run examples below ### **Your First OHLC Query** {#your-first-ohlc-query} Get real-time Bitcoin OHLC data across all chains: > 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 { Currencies( where: { Currency: { Id: { is: "bid:bitcoin" } }, Interval: { Time: { Duration: { eq: 60 } } }, Volume: { Usd: { gt: 5 } } } ) { Currency { Id Name Symbol } Price { Ohlc { Open High Low Close } } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } } } } ``` [Run this query ➤](https://ide.bitquery.io/bitcoin-currency-price-stream) ## Why Use Our OHLC API? ### **Pre-Aggregated Data** - No need to calculate OHLC from raw trade data - Ready-to-use candlestick data for any time interval - Optimized for performance and accuracy - Uses pre-aggregated price data updated in real-time ### **Multi-Chain Support** - Get OHLC data across all major blockchains - Chain-agnostic currency views (e.g., Bitcoin across all chains) - Cross-chain price aggregation ### **Real-Time Streaming** - WebSocket subscriptions for live OHLC updates - Kafka streams for high-throughput applications - Sub-second latency for trading applications ### **Advanced Analytics** - Moving averages (SMA, WMA, EMA) - Volume-weighted prices - Price change calculations - Technical indicators ## Crypto Price API vs DEXTradeByToken {#crypto-price-api-vs-dextradebytoken} **Default for OHLC:** Use the **Crypto Price API** as the **primary** source for real-time and standard OHLC. Use **`DEXTradeByTokens`** when you need **full historical** OHLC or trade-based aggregation over long ranges. | Feature | Crypto Price API | DEXTradeByTokens | |---------|------------------|-----------------| | **Data Availability** | Real-time + **recent** OHLC (Price Index) | Real-time + **full** historical (with `dataset: combined` / `archive` as supported) | | **Data Processing** | Pre-aggregated and real time aggregation of price data | Raw trades aggregated on-the-fly in your query | | **Data Quality** | Filtered, clean price feed, second level | Trade Level Price | ### **When to Use Crypto Price API:** - **Default for OHLC and K-lines**—live streaming and pre-aggregated bars - Real-time trading applications requiring normalized pricing - Live charting and dashboards with aggregated price feeds - High-frequency trading strategies with sub-second updates - Limit order execution with reliable mark prices - Futures trading and derivatives pricing - Lending and borrowing protocols requiring accurate rates - DeFi applications needing real-time price oracles - Any application requiring reliable real-time price streams - Fixed time intervals available due to pre-aggregated data ### **When to Use DEXTradeByTokens API:** - **Historical OHLC** and long-range backfills - When you need **actual per-trade** detail not only index OHLC - **Full** price history, custom intervals, or archive-backed ranges - Any time interval can be used because aggregation is defined in the query over trades ## Supported Time Intervals Our [Crypto Price API](/docs/trading/crypto-price-api/) OHLC API supports fixed time intervals optimized for different trading strategies: | Interval | Duration | |----------|----------| | 1 second | 1s | | 3 seconds | 3s | | 5 seconds | 5s | | 10 seconds | 10s | | 30 seconds | 30s | | 1 minute | 60s | | 5 minutes | 300s | | 15 minutes | 900s | | 30 minutes | 1800s | | 1 hour | 3600s | These **ten values are the complete set**. Any other value — 120, 7200, 86400 — is not an error: the query returns `[]` with HTTP 200 and **no `errors` key**, so a typo reads as "this token has no data" rather than "unsupported interval". :::danger The Duration filter is effectively mandatory The cube stores **all ten durations simultaneously**. A query without `Interval: { Time: { Duration: { eq: ... } } }` returns one row per duration for the same timestamp, so any aggregate over it is inflated roughly **ten-fold**: ```graphql where: { Interval: { Time: { Duration: { eq: 3600 } } } # never omit this ... } ``` Without it, a `sum(of: Volume_Usd)` over a window returns close to ten times the real figure. Row counts behave the same way — the unfiltered count equals the sum of the ten per-duration counts exactly. ::: ### Bucket arithmetic - `Interval.Time.End` == `Start + Duration - 1 second`. At Duration 1, `End == Start`. - Buckets are Unix-epoch aligned: `Start_epoch % Duration == 0` for every row. - Candles are **sparse**. A bucket with no trades is simply absent — there is no gap filling and no zero-volume filler. Do not assume a contiguous series; reindex client-side if you need one. ### Multiple timeframes in one request `Duration` accepts the full `OLAP_Integer` comparator set — `eq`, `ne`, `in`, `notIn`, `gt`, `ge`, `lt`, `le` — so you can pull several timeframes at once and split them client-side on `Interval.Time.Duration`: ```graphql Interval: { Time: { Duration: { in: [60, 300, 3600] } } } ``` Combining two comparators applies AND. :::note The 1-second series under-reports totals Aggregate volume at Duration 1 comes in **below** every coarser bucket — by up to a few percent, varying by token and window. Coarser buckets agree with each other closely. Use 60s or larger when totals must reconcile. ::: ## Custom 4-hour, daily and weekly candles Durations above 3600s do not exist natively, but you can **roll native candles up to any multiple** with the `interval:` argument plus argmin/argmax selectors. The aggregation is exact: ```graphql { Trading { Tokens( where: { Token: { Address: { is: "So11111111111111111111111111111111111111112" }, Network: { is: "Solana" } } Interval: { Time: { Duration: { eq: 3600 } } } Block: { Time: { since: "...", till: "..." } } } limit: { count: 1000 } ) { Block { Time(interval: { in: hours, count: 4 }) } Price { Ohlc { Open(minimum: Interval_Time_Start) High(maximum: Price_Ohlc_High) Low(minimum: Price_Ohlc_Low) Close(maximum: Interval_Time_Start) } } volume: sum(of: Volume_Usd) n: count } } } ``` `interval:` accepts `seconds`, `minutes`, `hours`, `days`, `weeks`, `months` and `years` with a `count` and an optional `offset`, so 4-hour, 12-hour, daily and weekly candles all come from the same pattern. :::warning Do not add `orderBy` to a roll-up query Any `orderBy` un-groups the aggregation and you silently get the raw source rows back instead of your buckets — a 21-row daily series becomes 504 hourly rows. Sort client-side. ::: ## Volume-Based Aggregation :::caution Not currently available Volume-based intervals are **not currently available**. `Interval.VolumeBased` is `false` and `Interval.TargetVolume` is `0` on every row, and filtering on either (`Interval: { VolumeBased: true }` or any `Interval: { TargetVolume: ... }` value) returns **zero rows** with HTTP 200 and no error — so a query written against them looks like a token with no data rather than an unsupported feature. Use time-based intervals via `Interval: { Time: { Duration: { eq: } }}` instead, and apply a volume threshold in the `where` clause with `Volume: { Usd: { gt: ... } }`. ::: ## Choosing the Right Cube for OHLC Data The [Crypto Price API](/docs/trading/crypto-price-api/) offers three different cubes for accessing OHLC data. Understanding which cube to use is crucial for getting the right data for your specific use case: Before we dive into the cubes, let's clarify the key terminology: **Currency** - The underlying asset (e.g., BTC, ETH, SOL) **Token** - Specific implementations of a currency on blockchains (e.g., cbBTC, WBTC are Bitcoin tokens) **Pair** - Trading pairs between two assets (e.g., cbBTC/ETH, WBTC/ETH, WBTC/SOL) ### **Currency Cube** - Chain-Agnostic Aggregated View Use the **Currency** cube when you want a unified price view of an asset across all blockchains. **Key Features:** - Aggregates all token representations of the same underlying asset for example for BTC it will combine multiple tokens like WBTC, cbBTC, LBTC etc. - Can provide both chain specific and chain agnostic prices - Can provide only USD-quoted prices - Can combine volume and price data from all chains [Learn more about Currency Cube ➤](/docs/trading/crypto-price-api/currency/) ### **Tokens Cube** - Chain-Wide Blended Candles Use the **Tokens** cube when you want one candle per token per chain, or a stream of candles for every token on a chain. **Key Features:** - Aggregates across all pairs for that token - Can provide both chain specific and chain agnostic prices - Can provide only USD-quoted prices - Can combine volume and price data from all chains > Because the candle blends every pool where the token is base, thin pools contribute to it as well. For candles on **one specific token**, prefer the Pairs cube with rank 1 (below). [Learn more about Tokens Cube ➤](/docs/trading/crypto-price-api/tokens/) ### **Pairs Cube** - Top Market and Pair-Specific Candles Use the **Pairs** cube for OHLC on a specific market — and, with the rank filter, for the most accurate candles on a specific **token**. **Key Features:** - **Recommended for a single token:** add `Ranking: { Position: { eq: 1 } }` to get candles from the token's top market instead of a blend across all pools ([how and why](/docs/trading/crypto-price-api/pairs#most-accurate-token-price)) - Pair-specific OHLC data (e.g., ETH/USDC on Uniswap) - Can be quoted in USD or quote token - Market/DEX-specific data - Most granular level of price data [Learn more about Pairs Cube ➤](/docs/trading/crypto-price-api/pairs/) ## Real-Time OHLC Stream Examples ### 1. Live Bitcoin OHLC Across All Chains Stream real-time Bitcoin OHLC data aggregated from all supported blockchains (Bitcoin, Ethereum WBTC, Solana, etc.) with 60-second intervals: > 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 { Currencies( where: { Currency: { Id: { is: "bid:bitcoin" } }, Interval: { Time: { Duration: { eq: 60 } } }, Volume: { Usd: { gt: 5 } } } ) { Currency { Id Name Symbol } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base BaseAttributedToUsd Quote Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { IsQuotedInUsd Ohlc { Open # First price in interval High # Highest price in interval Low # Lowest price in interval Close # Last price in interval } Average { Mean SimpleMoving WeightedSimpleMoving ExponentialMoving } } } } } ``` [Run Live Stream ➤](https://ide.bitquery.io/OHLC-of-a-currency-on-multiple-blockchains) ### 2. Ethereum OHLC on All DEXs Get real-time Ethereum OHLC data from all decentralized exchanges: > 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: {Currency: {Id: {is: "bid:eth"}}, Interval: {Time: {Duration: {eq: 60}}}, Volume: {Usd: {gt: 5}}} ) { Token { Symbol Network Address } QuoteToken { Symbol Network Address } Market { Name Protocol Network Address } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { IsQuotedInUsd Ohlc { Open High Low Close } Average { Mean SimpleMoving WeightedSimpleMoving ExponentialMoving } } } } } ``` [Run Live Stream ➤](https://ide.bitquery.io/All-pairs-of-ETH-currency) ### 3. Solana Token OHLC Stream Monitor all Solana tokens with real-time OHLC data: > 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 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 } } } } } ``` [Run Live Stream ➤](https://ide.bitquery.io/Aggregated-Price-of-all-tokens-in-real-time-on-one-chain) ## Historical OHLC Queries **Note:** These examples use the **Crypto Price API** for **recent** OHLC. For **full historical** OHLC, use **[DEXTradeByTokens](/docs/cubes/dextradesbyTokens/)**. ### 1. Bitcoin OHLC (Crypto Price API) Recent Bitcoin OHLC using the Crypto Price API (time range in the query matches what the Price Index supports). [Run Query](https://ide.bitquery.io/historical-Bitcoin-OHLC-data-for-the-last-7-days) > Note: We include `Volume: { Usd: { gt: 5 } }` in most examples to remove extreme outliers. The example uses a relative time window in `Block.Time`. ```graphql { Trading { Currencies( where: { Currency: { Id: { is: "bid:bitcoin" } }, Interval: { Time: { Duration: { eq: 3600 } } }, Volume: { Usd: { gt: 5 } }, Block: {Time:{ since_relative:{days_ago:7} }} }, limit: { count: 240 }, 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 Ohlc { Open High Low Close } Average { Mean SimpleMoving WeightedSimpleMoving ExponentialMoving } } } } } ``` ### 2. Top 10 Tokens by 5-Minute Price Change Find the biggest movers with OHLC data and price change calculations: > Note: We include `Volume: { Usd: { gt: 5 } }` in most examples to remove extreme outliers; this example 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 Id IsNative Name Network Symbol TokenId } Currency { Symbol Id Name } Interval { 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") } } } ``` [Run Query ➤](https://ide.bitquery.io/5-minute-price-change-api) ## DEX-Specific OHLC Streams ### 1. Uniswap v3 OHLC Stream Monitor all tokens on Uniswap v3 with 1-second OHLC data: > 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 } } } } ``` [Run Stream ➤](https://ide.bitquery.io/Uniswap-v3-DEX-tokens-1-second-price-stream-with-OHLC) ### 2. Raydium OHLC Stream (Solana) Track all tokens on Raydium with real-time OHLC data: > 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: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" } }, 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 } } } } ``` [Run Stream ➤](https://ide.bitquery.io/Raydium-Launchpad-DEX-tokens-1-second-price-stream-with-OHLC) ### 3. PancakeSwap v3 OHLC Stream (BSC) Monitor BSC tokens on PancakeSwap v3: > 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: "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 } } } } ``` [Run Stream ➤](https://ide.bitquery.io/PancakeSwap-v3-DEX-tokens-1-second-price-stream-with-OHLC) ## Kafka Streaming for OHLC Data For high-throughput applications, use our Kafka streams to get real-time OHLC data: ### Kafka Topic: `trading.prices` The Kafka topic delivers real-time, pre-aggregated OHLC data for all supported tokens and currencies. **Schema**: [Protobuf Schema](https://github.com/bitquery/streaming_protobuf/tree/main/market) **Packages**: - [Python Package](https://pypi.org/project/bitquery-pb2-kafka-package/) - [NPM Package](https://www.npmjs.com/package/bitquery-protobuf-schema) ### Kafka Consumer Example (Python) ```python from kafka import KafkaConsumer # Configure Kafka consumer consumer = KafkaConsumer( 'trading.prices', bootstrap_servers=['your-kafka-broker:9092'], value_deserializer=lambda m: bitquery_pb2.PriceIndexMessage().ParseFromString(m) ) # Consume OHLC data for message in consumer: price_data = message.value # Extract OHLC data ohlc = price_data.price.ohlc print(f"Token: {price_data.token.symbol}") print(f"Open: {ohlc.open}") print(f"High: {ohlc.high}") print(f"Low: {ohlc.low}") print(f"Close: {ohlc.close}") print(f"Volume: {price_data.volume.usd}") print("---") ``` ### Kafka Consumer Example (Node.js) ```javascript const kafka = require('kafkajs'); const { PriceIndexMessage } = require('bitquery-protobuf-schema'); const client = kafka({ clientId: 'ohlc-consumer', brokers: ['your-kafka-broker:9092'] }); const consumer = client.consumer({ groupId: 'ohlc-group' }); async function run() { await consumer.connect(); await consumer.subscribe({ topic: 'trading.prices' }); await consumer.run({ eachMessage: async ({ topic, partition, message }) => { const priceData = PriceIndexMessage.decode(message.value); // Extract OHLC data const ohlc = priceData.price.ohlc; console.log(`Token: ${priceData.token.symbol}`); console.log(`Open: ${ohlc.open}`); console.log(`High: ${ohlc.high}`); console.log(`Low: ${ohlc.low}`); console.log(`Close: ${ohlc.close}`); console.log(`Volume: ${priceData.volume.usd}`); console.log('---'); }, }); } run().catch(console.error); ``` ## TradingView Integration Our OHLC API is perfect for TradingView charting. Use our ready-to-use SDK: ### TradingView SDK ```javascript const datafeed = new BitqueryTradingViewDatafeed({ apiKey: 'your-bitquery-api-key', token: 'BTC', // or any supported token interval: '1m', // 1m, 5m, 15m, 1h, etc. }); // Initialize TradingView widget const widget = new TradingView.widget({ symbol: 'BTC/USD', interval: '1m', container: 'tradingview_chart', datafeed: datafeed, library_path: '/tradingview/', locale: 'en', disabled_features: ['use_localstorage_for_settings'], enabled_features: ['study_templates'], charts_storage_url: 'https://saveload.tradingview.com', charts_storage_api_version: '1.1', client_id: 'tradingview.com', user_id: 'public_user_id', fullscreen: false, autosize: true, }); ``` [Get TradingView SDK ➤](https://www.npmjs.com/package/@bitquery/tradingview-sdk) ## Advanced OHLC Analytics ### 1. Price Change Analysis Calculate percentage price changes using expressions: ```graphql { Trading { Tokens( where: { Price: { IsQuotedInUsd: true }, Volume: { Usd: { gt: 100000 } }, Interval: { Time: { Duration: { eq: 300 } } } } ) { Token { Symbol Network } Price { Ohlc { Open Close } } # Calculate price change percentage priceChange: calculate(expression: "((Price_Ohlc_Close - Price_Ohlc_Open) / Price_Ohlc_Open) * 100") # Calculate absolute price change priceDiff: calculate(expression: "Price_Ohlc_Close - Price_Ohlc_Open") } } } ``` ### 2. Volume-Weighted OHLC Get volume-weighted OHLC data for more accurate price representation: > 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: { Currency: { Id: { is: "bid:eth" } }, Interval: { Time: { Duration: { eq: 60 } } }, Volume: { Usd: { gt: 5 } } } ) { Token { Symbol } Volume { Base Quote Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Price { Ohlc { Open High Low Close } Average { WeightedSimpleMoving # Volume-weighted average Mean # Simple average } } } } } ``` ### 3. Cross-Chain Arbitrage Detection Find arbitrage opportunities using OHLC data across chains: > 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 { Pairs( where: { 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 { Name Symbol } Market { Name Network Address } Price { Ohlc { Close } Average { Mean } } QuoteCurrency { Symbol } } } } ``` [Run Query ➤](https://ide.bitquery.io/Find-arbitrage-opportunity-with-same-token-across-chains) ## Can I get 1-minute historical OHLC data for a full year? {#can-i-get-1-minute-historical-ohlc-data-for-a-full-year} **Crypto Price API:** best for **live and recent** OHLC; an unbroken **one-year 1-minute** series may exceed what the Price Index is designed to serve—check your **plan** and try coarser intervals or **DEX-derived** data for deep history. **DEXTradeByTokens:** bucket with **`Time(interval: { count: 1, in: minutes })`** and a **365-day** **`Block.Time`** range; **minutes with no trades** will be **empty** or **sparse**, and the query can be **heavy**. Prefer **hourly/daily** bars or export **raw trades** for backfill. See [Historical Solana aggregate data](/docs/blockchain/Solana/historical-aggregate-data/) and [DEXTradesByTokens OHLC](/docs/cubes/dextradesbyTokens/#how-do-i-get-ohlc-in-a-dextradebytokens-query). ## Supported Blockchains Our OHLC API supports all major blockchains: - **Ethereum** - ETH, ERC-20 tokens - **Solana** - SOL, SPL tokens - **Binance Smart Chain (BSC)** - BNB, BEP-20 tokens - **Polygon** - MATIC, ERC-20 tokens - **Arbitrum** - ETH, ERC-20 tokens - **Optimism** - ETH, ERC-20 tokens - **Base** - ETH, ERC-20 tokens - **Tron** - TRX, TRC-20 tokens ## API Endpoints - **GraphQL Endpoint**: `https://streaming.bitquery.io/graphql` - **Kafka Broker**: `streaming.bitquery.io:9092` - **Topic**: `trading.prices` ## Best Practices 1. **Choose the right API**: Use [Crypto Price API](/docs/trading/crypto-price-api/) as the **main** source for OHLC; use [DEXTradeByTokens](/docs/cubes/dextradesbyTokens/) for **historical** OHLC from DEX trades 2. **Choose the Right Interval**: Use 1s for high-frequency trading, 1m for standard charting 3. **Use USD Quoting**: Set `IsQuotedInUsd: true` for consistent price comparison ## Support - **Documentation**: [Crypto Price API Docs](/docs/trading/crypto-price-api/) - **IDE**: [Bitquery IDE](https://ide.bitquery.io) - **Community**: [Discord](https://discord.gg/bitquery) - **Support**: [Contact Support](https://support.bitquery.io) --- *Get started with real-time OHLC data today and build the next generation of crypto trading applications.* --- ## Crypto Trades API — Real-Time DEX Trade Streams Across URL: https://docs.bitquery.io/docs/trading/crypto-trades-api/trades-api/ Crypto Trades API — Real-Time DEX Trade Streams Across via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. # Crypto Trades API — Real-Time DEX Trade Streams :::tip Which trade API should you use? This page covers **`Trading.Trades`** — the curated, multi-chain trade feed for **real-time and the last ~30 days**. For older / historical data (anything beyond ~30 days) drop down to chain-level [`DEXTrades`](/docs/cubes/dextrades) or [`DEXTradeByTokens`](/docs/cubes/dextradesbyTokens). See the [**Trading Data Overview**](/docs/trading/trading-data-overview) for a full side-by-side comparison. ::: > **Bitquery Crypto Trades API** streams **individual swap-level DEX trades** in **real time** across **Solana**, **Ethereum**, **BSC**, **Base**, **Arbitrum**, and **Polygon**. Each row includes **price**, **USD amounts**, **market cap**, **FDV**, **supply**, **trader address**, and **transaction metadata** via **GraphQL subscriptions**. The **Trades** cube streams individual **swap-level** rows from the **Trading** API: each event includes **side**, **amounts** (base, quote, USD), **price**, **pair** (market, tokens, currencies), **trader**, **transaction** metadata, and a **supply** snapshot (**MarketCap**, **FDV**, circulating/total/max supply) for the token context on that row. For **aggregated** token metrics across all pairs, use the **[Tokens cube](/docs/trading/crypto-price-api/tokens)**. For **pair-level** OHLC and volume intervals, use the **[Pairs cube](/docs/trading/crypto-price-api/pairs)**. Supply field meanings are documented under **[Supply fields](/docs/trading/crypto-price-api/supply-fields)**. ## Video Tutorial | How to Track DEX Trades & Trader PnL Across All Blockchain networks in a single API ### Key points - **Subscriptions**: These examples use **`subscription`** for real-time streams; you can often run the same selection as a **`query`** with an added time window on **`Block`** / **`Interval`** where supported. - **Networks**: Filter by chain with **`Pair.Market.Network`** to filter trades on a particular network. For **faster queries**, use **`Pair.Market.NetworkBid`** as showcased in the tip below. - **Token filter**: Use **`Pair.Token.Id`** with the full id (e.g. **`bid:solana:`**, **`bid:eth:`**) per your dataset conventions. - **Trader filter**: Use **`Trader.Address`** for the wallet executing the trade. - **Aggregations**: Examples at the end of this page use **`query`** with **`limit`**, **`orderBy`**, **`sum`**, **`average`**, **`count`**, **`calculate`**, **`limitBy`**, and **`distinct`** for volume, token, DEX, time-bucket, and fee analytics on **`Trades`**. - **USD vs quote**: **`PriceInUsd`** and **`AmountsInUsd`** are in USD where indexed; see **[Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm)** for how amounts and prices are derived. More patterns: **[Crypto Price API examples](/docs/trading/crypto-price-api/examples)**. :::tip Faster queries: filter with `NetworkBid` instead of `Network` On **`Trading.Trades`**, you can scope by chain with **`Pair.Market.NetworkBid`** instead of **`Pair.Market.Network`** for faster results. se the same **`bid:`** convention as **`Pair.Token.Id`**. | Network Name | `NetworkBid` Value | | ---------------------- | ------------------------- | | Solana | `bid:solana` | | Ethereum | `bid:eth` | | Binance Smart Chain | `bid:bsc` | | Base | `bid:base` | | Arbitrum | `bid:arbitrum` | | Matic (Polygon) | `bid:matic` | | Tron | `bid:tron` | | Optimism | `bid:optimism` | | Robinhood | `bid:robinhood` | | Arc | `bid:arc` | The same **`NetworkBid`** pattern applies on the **[Crypto Price API](/docs/trading/crypto-price-api/)** for **`Token.NetworkBid`** and **`Market.NetworkBid`** on **Tokens** and **Pairs**. ::: ## Before you aggregate: three things about a Trades row These affect every wallet-analytics or volume figure you compute from this cube. ### One row is a swap *leg*, not a transaction A single on-chain transaction can produce several rows. Unlike `DEXTradeByTokens`, rows are not mirrored per token, and aggregator roll-ups are not included. Count transactions with `count(distinct: TransactionHeader_Hash)`, not `count`. ### Use `AmountsInUsd.Quote` for USD, not `.Base` `AmountsInUsd.Base` is `Amounts.Base x PriceInUsd`, and **`PriceInUsd` is a smoothed reference price, not the executed price**. On fast-moving tokens the two USD legs diverge — on Solana, roughly a quarter of rows differ by more than 1%, with outliers of several orders of magnitude during sharp moves. `AmountsInUsd.Quote` is the sound leg: the implied quote-token USD price is consistent to within a rounding error across trades in the same second. ```graphql # prefer this for USD volume and PnL sum(of: AmountsInUsd_Quote, if: { Side: { is: "Buy" } }) ``` :::danger The cube emits some exact duplicate rows A small but material share of rows — on the order of one percent on Solana — are **exact duplicates of the same swap leg**, inflating USD volume by a similar fraction. No field distinguishes the copies; the behaviour is persistent, not a transient glitch. Deduplicate on `(TransactionHeader.Hash, Trader.Address, Side, Amounts.Base)` before reporting exact trade counts or volumes. The rate is much higher on Matic, where Polymarket outcome tokens all share one ERC-1155 address, so distinct markets collapse onto the same token identity. ::: ### Field meanings shift between chains | Field | Solana | EVM | | --- | --- | --- | | `Pair.Pool.Address` | the pool | the pool | | `Pair.Market.Address` | the pool | the protocol **factory** | | `Pair.Market.Program` | the DEX program | the pool | Only `Pool.Address` and `Market.Id` mean the same thing on every chain. `TransactionHeader.Sender` and `.To` are leg-level and their meanings differ between Solana and EVM; on Tron, `Fee`, `FeePayer` and `To` are absent entirely. `Token.Did` and `Token.TokenId` are empty on every row, and `Token.Address` is empty on native assets — group by `Token.Id`. ### Scope and window `Trading.Trades` is **realtime only** — `dataset: archive` and `combined` are rejected — and holds roughly the last 30 days. Both `since` and `till` bounds are inclusive. It is also **not DEX-only**: it carries Seaport NFT fills and Polymarket activity, so filter by `Pair.Market.ProtocolFamily` if you mean DEX swaps specifically. ## How Do I Stream New DEX Trades Across All Chains in Real Time? > *Real-time* **multi-chain DEX trade stream** — subscribe to every new swap on **Solana**, **Ethereum**, **BSC**, **Base**, **Arbitrum**, and **Polygon** in a single **GraphQL subscription**. Each event returns **price**, **USD amounts**, **market cap**, **supply**, **trader wallet**, and **transaction hash** the moment a trade is confirmed on-chain. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/all-chains-New-Trades-Stream---Solana-eth-bsc-base--arbitrum-matic_2#). ```graphql subscription { Trading { Trades { Side Supply { MaxSupply TotalSupply FullyDilutedValuationUsd CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How Do I Get All DEX Trades on Solana With Price, Market Cap, and Supply? > Stream **all Solana DEX trades** in real time with **USD price**, **market cap**, **FDV**, **circulating supply**, and **transaction fee** data. Filter by **`Pair.Market.Network: Solana`** to capture every swap across **Raydium**, **Orca**, **Jupiter**, **PumpSwap**, and other Solana DEXs in a single subscription. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-trades-on-Solana-with-Price-Marketcap-supply). ```graphql subscription { Trading { Trades(where: { Pair: { Market: { Network: { is: "Solana" } } } }) { Side Supply { MaxSupply TotalSupply FullyDilutedValuationUsd CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How Do I Stream Trades for a Specific Token on Solana? > Filter the **Solana trade stream** to a **single token** by its **mint address** using **`Pair.Token.Id`** — get real-time **swap events**, **USD price**, **market cap**, **supply**, and **trader wallet** for any **SPL token** traded on Raydium, Orca, Jupiter, pumpfun, or PumpSwap. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/Trades-of-a-specific-token-on-Solana). ```graphql subscription { Trading { Trades( where: { Pair: { Market: { Network: { is: "Solana" } } Token: { Id: { is: "bid:solana:4YiLHDR4B4pE4R5GUMA8HG8YunyeLwcobtEtvwMupump" } } } } ) { Side Supply { MaxSupply TotalSupply FullyDilutedValuationUsd CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How Do I Get All Trades for a Specific Ethereum Token With Price, Market Cap, and Supply? > Stream **Ethereum ERC-20 token trades** in real time — filter by **contract address** with **`Pair.Token.Id: bid:eth:0x…`** to get every **Uniswap**, **SushiSwap**, or other DEX swap including **USD price**, **market cap**, **FDV**, **supply**, **trader address**, and **transaction hash**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-trades-of-a-specific-Ethereum-token-with-Price-Marketcap-supply_1). ```graphql subscription { Trading { Trades( where: { Pair: { Market: { Network: { is: "Ethereum" } } Token: { Id: { is: "bid:eth:0x8b1484d57abbe239bb280661377363b03c89caea" } } } } ) { Side Supply { MaxSupply TotalSupply FullyDilutedValuationUsd CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How Do I Stream Uniswap v4 Trades on Ethereum With Pool Id and Market Cap? > Stream **Uniswap v4** swaps on **Ethereum** by filtering **`Pair.Market.Network: Ethereum`** and **`Pair.Market.Protocol: uniswap_v4`**. Each event includes **`Pair.Pool.Id`** (the **v4 pool identifier** the protocol uses), **`Pair.Pool.Address`**, **market cap** and **circulating supply** under **`Supply`**, and full **pair** and **trader** context. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/Uniswap-v4-trades-with-pool-id-and-mcap). ```graphql subscription { Trading { Trades( where: {Pair: {Market: {Network: {is: "Ethereum"}, Protocol: {is: "uniswap_v4"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } Pool { Id Address } } } } } ``` On **Uniswap v4**, use **`Pair.Pool.Id`** as the stable **pool id** field (alongside **`Pair.Pool.Address`**). Example shape: ```json "Pool": { "Address": "0x000000000004444c5dc75cb358380d2e3de08a90", "Id": "0x71ad627a0586a06b24834f7af328c5c387a512d183dbd7b8c31189a866adcefa" } ``` --- ## How Do I Stream All DEX Trades on BSC (BNB Chain)? > Stream **every DEX swap on BNB Smart Chain** in real time — filter by **`Pair.Market.Network: Binance Smart Chain`** to capture trades across **PancakeSwap**, **Four.meme**, and other BSC DEXs. Each event returns **side**, **USD amounts**, **market cap**, **circulating supply**, **trader address**, and **pair metadata**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-BNB-Trade-Stream). ```graphql subscription { Trading { Trades(where: {Pair: {Market: {Network: {is: "Binance Smart Chain"}}}}) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } Currency { Symbol Id Name } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Stream All DEX Trades on Base? > Stream **every DEX swap on Base** in real time — filter by **`Pair.Market.Network: Base`** to capture trades across **Aerodrome**, **Uniswap**, **BaseSwap**, and other Base DEXs. Each event returns **side**, **USD amounts**, **market cap**, **circulating supply**, **trader wallet**, and **pair metadata**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-Base-Trade-Stream). ```graphql subscription { Trading { Trades(where: {Pair: {Market: {Network: {is: "Base"}}}}) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } Currency { Symbol Id Name } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Get All Trades for a Specific Wallet on Solana? > Track a **Solana wallet's DEX trading activity** in real time — filter by **`Trader.Address`** to stream every **buy and sell** swap executed by a specific wallet, including **token pair**, **USD price**, **amounts**, **market cap**, and **transaction details**. Useful for **copy trading bots**, **whale watching**, and **wallet PnL tracking**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-trades-of-a-trader). ```graphql subscription { Trading { Trades( where: { Pair: { Market: { Network: { is: "Solana" } } } Trader: { Address: { is: "GWcAopUZKokUUQAMDrNzd1YVHLJqbzJomu2pzNqLe9U3" } } } ) { Side Supply { MaxSupply TotalSupply FullyDilutedValuationUsd CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How Do I Get Trades for a Specific Trader on a Specific Token? > Combine **wallet address** and **token mint** filters to stream only the trades a **specific trader** made on a **specific token** — ideal for **position tracking**, **entry/exit analysis**, and **wallet-level PnL** on a per-token basis across **Solana**, **Ethereum**, or any supported chain. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/trades-of-a-specific-trader-of-a-specific-token). ```graphql subscription { Trading { Trades( where: { Pair: { Market: { Network: { is: "Solana" } } Token: { Id: { is: "bid:solana:4YiLHDR4B4pE4R5GUMA8HG8YunyeLwcobtEtvwMupump" } } } Trader: { Address: { is: "GWcAopUZKokUUQAMDrNzd1YVHLJqbzJomu2pzNqLe9U3" } } } ) { Side Supply { MaxSupply TotalSupply FullyDilutedValuationUsd CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How Do I Stream All PumpFun Trades? > Stream **every pumpfun trade** in real time by filtering on the **pumpfun program address** (`6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P`). Captures all bonding-curve swaps on pumpfun including **token pair**, **USD amounts**, **market cap**, **circulating supply**, and **trader wallet** — useful for **new token sniping**, **bonding-curve monitoring**, and **pumpfun analytics dashboards**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-pumpfun-Trade-Stream_2). ```graphql subscription { Trading { Trades( where: {Pair: {Market: {Program: {is: "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } Currency { Symbol Id Name } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Stream All PumpSwap Trades? > Stream **every PumpSwap trade** in real time by filtering on the **PumpSwap program address** (`pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA`). Captures all AMM swaps on PumpSwap — the successor DEX for tokens that graduated from the pumpfun bonding curve — including **token pair**, **USD amounts**, **market cap**, **supply**, and **trader wallet**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-Pumpswap-Trade-Stream). ```graphql subscription { Trading { Trades( where: {Pair: {Market: {Program: {is: "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } Currency { Symbol Id Name } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Get Only Buy or Sell Trades for a Token? > Stream **buy-side or sell-side trades** for a specific token by filtering on **`Pair.Token.Address`**. The **`Side`** field on each event tells you whether the trade was a **buy** or **sell** — use it client-side to split streams, calculate **buy/sell ratio**, track **buy pressure**, or trigger **sell alerts** for any token across supported chains. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/All-wsol-Trade-Stream). ```graphql subscription { Trading { Trades( where: {Pair: {Token: {Address: {is: "So11111111111111111111111111111111111111112"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Get Recent Trades for WSOL (Last 10 Minutes)? > Query the **last 10 minutes of WSOL trades** using **`Block.Time.since_relative`** with **`Pair.Token.Id`** (indexed field for faster lookups). Returns trades sorted by **most recent first** with **USD amounts**, **market cap**, **supply**, **pool address**, and **trader wallet** — ideal for building **live trade feeds**, **recent activity widgets**, or **short-window analytics**. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Last-10-minutes-trades-for-WSOL). ```graphql { Trading { Trades( orderBy:{descending:Block_Time} where: { Block:{Time:{since_relative:{minutes_ago:10}}} Pair: {Token: {Id: {is: "bid:solana:So11111111111111111111111111111111111111112"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Stream Trades for a Specific Trading Pair? > Stream **trades for a specific token pair** (e.g. **WSOL/USDC**) by filtering both **`Pair.Token.Id`** and **`Pair.QuoteToken.Id`**. This captures every swap between the two tokens **across all pools and DEXs** — useful for **pair-level price feeds**, **liquidity monitoring**, and **arbitrage detection** between Raydium, Orca, Jupiter, and other venues. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/WSOL-USDC-Token-pair-trade-stream). ```graphql subscription { Trading { Trades( where: {Pair: {Token: {Id: {is: "bid:solana:So11111111111111111111111111111111111111112"}}, QuoteToken: {Id: {is: "bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Get the Last 10 Trades for a Token Pair Across All Pools? > Query the **last 10 trades** for a specific pair (e.g. **WSOL/USDC**) across **every pool and DEX** using **`limit`**, **`orderBy: descending Block_Time`**, and a **`since_relative`** time window. Returns the most recent swaps with **pool address**, **USD amounts**, **market cap**, **trader wallet**, and **side** — ideal for **recent trades widgets**, **pair activity tables**, and **cross-pool comparison**. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Last-10-WSOL-USDC-Token-pair-trades). ```graphql { Trading { Trades( limit:{count:10} orderBy: {descending: Block_Time} where: {Block: {Time: {since_relative: {minutes_ago: 10}}}, Pair: {Token: {Id: {is: "bid:solana:So11111111111111111111111111111111111111112"}}, QuoteToken: {Id: {is: "bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Get Trades Above a Minimum USD Value (Whale Trades)? > Stream **whale trades** across all chains by filtering **`AmountsInUsd.Base`** with **`gt`** (greater than) — for example, only swaps worth **over $100,000 USD**. Captures large-size DEX trades in real time with **trader wallet**, **token pair**, **pool**, **market cap**, and **supply** — ideal for **whale alert bots**, **smart money tracking**, **large-order flow analysis**, and **institutional activity monitoring**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/Stream---Trades-over-100k-usd). ```graphql subscription { Trading { Trades(where: {AmountsInUsd: {Base: {gt: 100000}}}) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How Do I Track Trades for Multiple Tokens in One Subscription? > Monitor **multiple tokens in a single subscription** using the **`in`** operator on **`Pair.Token.Id`** and **`Pair.QuoteToken.Id`** with the **`any`** combinator — or filter by **multiple pool addresses** with **`Pair.Pool.Address.in`**. Both approaches let you batch-watch a **token watchlist** or a **set of liquidity pools** without opening separate streams — ideal for **portfolio dashboards**, **multi-token alert bots**, and **pool-level monitoring**. There are two ways to track multiple tokens. You can specify token IDs using the **`any`** combinator to match trades where your tokens appear on either side of the pair. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/How-do-I-track-trades-for-multiple-tokens-in-one-subscription). ```graphql subscription { Trading { Trades(where: { any:[ {Pair:{Token:{Id:{in:["bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "bid:solana:SKRbvo6Gf7GondiT3BbTfuRDPqLWei4j2Qy2NPGZhW3","bid:solana:Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"]}}}} {Pair:{QuoteToken:{Id:{in:["bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "bid:solana:SKRbvo6Gf7GondiT3BbTfuRDPqLWei4j2Qy2NPGZhW3","bid:solana:Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"]}}}} ] Pair: {Market: {Network: {is: "Solana"}}}}) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` You can also specify **pool addresses** directly to monitor all tokens traded in those pools. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/Using-Pairs---How-do-I-track-trades-for-multiple-tokens-in-one-subscription). ```graphql subscription { Trading { Trades( where: {Pair: {Pool: {Address: {in: ["BMhbJpKihPsrQwWTNrzYLFwh2LbuDwWYbHKzEZrV9f6V", "916HUQvjHzJ3UP1LgtyoVYyxhkwqbnBxPshxNKBbz5uN", "aXQtJ9cGr1zgLyrppKJ5BR5jv1RMjyYL5Wetd2KZNtB"]}}}} ) { Side Supply { CirculatingSupply MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Pool { Address Id } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## What Are the Most Traded Tokens on Solana in the Last Hour? > Get the **most traded tokens on Solana** in the last hour ranked by **trade count** — returns **number of trades**, **average trade size in USD**, and **total volume** per token pair. Useful for **trending token feeds**, **volume dashboards**, **hot token detection**, and identifying which tokens have the highest trading activity right now. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Most-traded-token-in-last-1-hour-on-solana-and-its-average-trade-amount-and-total-volume). ```graphql { Trading { Trades( orderBy: {descendingByField: "count"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) sum(of:AmountsInUsd_Quote) Pair { Pool { Address } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I compare buy vs sell volume in USD per pool on Solana (last hour)? > Aggregates **Solana** **`Trades`** from the last **hour** into up to **100** rows per **liquidity pool**, ranked by **trade count**. Each row returns **quoted USD volume** (`AmountsInUsd_Quote`) split into **buy** vs **sell** sums plus **buy/sell counts**. Useful for **per-pool flow** and **directional pressure** dashboards. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Total-buy-vs-sell-volume-in-USD-per-token_1#). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "count"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) Pair { Pool { Address } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I rank pools by net buy-minus-sell trade count on Solana (last hour)? > Same **pool-level** aggregation as above, but adds **`net_flow`** as **`buys − sells`** (difference in **trade counts**, not USD). Ordered by **`net_flow`** so pools with more **buy-side prints** rank higher. For **USD** net flow, define a **`calculate`** on **`buy_volume`** and **`sell_volume`** instead. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Net-flow-buys---sells-per-token-symbol_2). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "net_flow"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) net_flow: calculate(expression: "$buys - $sells") Pair { Pool { Address } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I aggregate quoted USD volume by DEX program on Solana (last hour)? > Groups **Solana** trades by **`Pair.Market.Program`** (and **protocol** metadata), returning **trade count** and **total quoted USD volume** per **DEX program**. Useful for **share-of-volume** charts across **AMMs** and programs. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Volume-distribution-by-DEX-program#). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "Dex_Volume"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { Trades_count: count Dex_Volume: sum(of: AmountsInUsd_Quote) Pair { Market { Program Protocol ProtocolFamily Network } } } } } ``` --- ## How do I rank Solana tokens by market cap using trade-window supply snapshots (last hour)? > One row per **base token** (**`limitBy`** on **`Pair.Token.Address`**) with **`Supply.MarketCap`**, **FDV**, and **total supply** taken at **`maximum: Block_Time`** inside the last hour, plus **volume** and **trade stats**. Replace **`Pair.Market.Network`** or add **`Pair.Token.Id`** filters to narrow the universe. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Tokens-ranked-by-market-cap_1). ```graphql { Trading { Trades( limit: {count: 100} limitBy: {by: Pair_Token_Address, count: 1} orderBy: {descending: Supply_MarketCap} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) Pair { Pool { Address } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Supply { TotalSupply(maximum: Block_Time) FullyDilutedValuationUsd(maximum: Block_Time) MarketCap(maximum: Block_Time) } } } } ``` --- ## How do I find tokens with the highest trade frequency on Solana (last hour)? > Ranks **base tokens** by **`count`** of **`Trades`** in the window (plus **average trade size** and **volume** on **`AmountsInUsd_Quote`**). The selection nests **`Pair.Token`** only — ideal for **“most swapped assets”** style lists. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Tokens-with-highest-trade-frequency). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "count"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) Pair { Token { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I rank tokens by number of unique buyers on Solana (last hour)? > Adds **`unique_buyers`** as **`count(distinct: Trader_Address)`** on **Buy** side trades, ordered by that field — surfaces tokens with the **widest retail participation** in the window (subject to your filters). You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Tokens-with-most-unique-buyers). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "unique_buyers"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) unique_buyers: count(distinct: Trader_Address, if: {Side: {is: "Buy"}}) Pair { Token { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I find the most active liquidity pools on Solana by trade count (last hour)? > Ranks **pools** by **`count`** with full **pair** context (tokens + market). Same shape as **buy/sell volume per pool** but sorted purely by **activity** — good for **“hottest pools”** views. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Most-active-market-pools-by-trades). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "count"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) Pair { Pool { Address } Market { Address Program Network } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I count unique tokens traded per DEX program on Solana (last hour)? > Groups by **`Pair.Market`** program metadata and computes **`Unique_tokens`** with **`count(distinct: Pair_Token_Id)`** plus **volume** and **trade count** — shows how many **different base tokens** touched each **program** in the window. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Unique-tokens-per-DEX-program). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "Unique_tokens"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { Trades_count: count Dex_Volume: sum(of: AmountsInUsd_Quote) Unique_tokens: count(distinct: Pair_Token_Id) Pair { Market { Program Protocol ProtocolFamily Network } } } } } ``` --- ## How do I get per-minute trade counts for a Solana token (last hour)? > Buckets **`Block.Time`** into **1-minute** intervals via **`Time(interval: {in: minutes, count: 1})`**, filtered to one **`Pair.Token.Id`** on **Solana**. Returns **count**, **volume**, and **buy/sell** breakdown **per minute** — useful for **intraday activity** charts. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Trade-count-over-time-by-block-timestamp). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "Block_Timefield"} where: { Block: {Time: {since_relative: {hours_ago: 1}}} Pair: { Token: {Id: {is: "bid:solana:FVo4K9FtXg9A4M6cAovqm4qgM7ALUpgei2da4D6R9FXb"}} Market: {Network: {is: "Solana"}} } } ) { Block { Timefield: Time(interval: {in: minutes, count: 1}) } count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) Pair { Token { Address Id IsNative Symbol TokenId Network } } } } } ``` --- ## How do I find the busiest one-minute volume windows for a Solana token (last hour)? > Same **1-minute** buckets and **token** filter as above, but **`orderBy`** **`total_volume`** so the **largest quoted-USD minutes** float to the top — a simple **volume spike** detector. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Volume-spikes--busiest-1min-time-windows). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "total_volume"} where: { Block: {Time: {since_relative: {hours_ago: 1}}} Pair: { Token: {Id: {is: "bid:solana:FVo4K9FtXg9A4M6cAovqm4qgM7ALUpgei2da4D6R9FXb"}} Market: {Network: {is: "Solana"}} } } ) { Block { Timefield: Time(interval: {in: minutes, count: 1}) } count average_trade_size: average(of: AmountsInUsd_Quote) total_volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Buy"}}) sell_volume: sum(of: AmountsInUsd_Quote, if: {Side: {is: "Sell"}}) buys: count(if: {Side: {is: "Buy"}}) sells: count(if: {Side: {is: "Sell"}}) Pair { Token { Address Id Symbol Network } } } } } ``` --- ## How do I get total Solana DEX trade count, quoted USD volume, and sum of fees (last hour)? > Single-row aggregate over **all Solana** **`Trades`** in the window: **`TransactionHeader.Fee`** summed in **native units** (e.g. **lamports**), plus **trade count** and **`AmountsInUsd_Quote`** sum. Interpret **fees** with your own **SOL** reference price or decimals. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Total-SOL-fees-Total-Volume-Total-count-trades). ```graphql { Trading { Trades( where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { Trades_count_solana: count Solana_Volume: sum(of: AmountsInUsd_Quote) Total_fees_Solana: sum(of: TransactionHeader_Fee) } } } ``` --- ## How do I get average transaction fee per trade by DEX program on Solana (last hour)? > Groups by **`Pair.Market`** program fields, returning **`average(of: TransactionHeader_Fee)`** alongside **trade count**, **total fees**, and **quoted USD volume** per **program**. Fee values are **native**; compare **programs** on a **relative** basis or convert off-chain. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/Average-fee-per-trade-Total-fees-total-volume-trades-count-per-DEX-program). ```graphql { Trading { Trades( limit: {count: 100} orderBy: {descendingByField: "Average_Fee_per_DEX"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Pair: {Market: {Network: {is: "Solana"}}}} ) { Trades_count_dex: count Dex_Volume: sum(of: AmountsInUsd_Quote) Total_fees_DEX: sum(of: TransactionHeader_Fee) Average_Fee_per_DEX: average(of: TransactionHeader_Fee) Pair { Market { Program Protocol ProtocolFamily } } } } } ``` --- ## How do I stream live prices for a Pump.fun token using the Trades API? > Subscribe to **real-time price updates** for a specific Pump.fun token — streams every trade with **USD price**, **market cap**, **FDV**, **total supply**, **buy/sell amounts**, and **transaction details** as they happen. Filter by **`Pair.Market.Program`** (Pump.fun program address) and **`Pair.Token.Id`** to lock onto one token. Useful for **live price tickers**, **trading bots**, **real-time dashboards**, and **token monitoring**. You can run this subscription [in the Bitquery IDE](https://ide.bitquery.io/pump-fun-token-live-prices-using-trades-api_1#). ```graphql subscription { Trading { Trades( where: { Pair: { Market: { Network: { is: "Solana" } Program:{is:"6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"} } Token: { Id: { is: "bid:solana:DhDRgwGeTPzkykcSYgTw42EsH3DBZg1p6FnnnXkrpump" } } } } ) { Side Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## How do I get the first 50 buyers of a token? > Retrieve the **first 50 buy trades** for a specific token ordered by **ascending block time** — returns the **earliest buyers**, their **wallet addresses**, **amounts paid**, **USD values**, **price at entry**, **market cap**, and **supply data** at the time of each trade. Filter by **`Side: "Buy"`** and **`Pair.Token.Id`** to target one token. Useful for **early buyer analysis**, **smart money tracking**, **insider detection**, and **token launch forensics**. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/first-50-buyers-of-a-token_3#). ```graphql { Trading { Trades( limit: {count: 50} orderBy: {ascending: Block_Time} where: {Side: {is: "Buy"}, Pair: {Market: {Network: {is: "Solana"}}, Token: {Id: {is: "bid:solana:DhDRgwGeTPzkykcSYgTw42EsH3DBZg1p6FnnnXkrpump"}}}} ) { Side Supply { TotalSupply FullyDilutedValuationUsd MarketCap } Trader { Address } TransactionHeader { Fee FeePayer Sender To Hash Index } Amounts { Base Quote } AmountsInUsd { Base Quote } Block { Date Time Timestamp } Pair { Currency { Id Name Symbol } Market { Address Program Network } QuoteCurrency { Id Name Symbol } Token { Address Id IsNative Symbol TokenId Network } QuoteToken { Address Id IsNative Symbol TokenId Network } } Price PriceInUsd } } } ``` --- ## Crypto Trading Pairs Price API URL: https://docs.bitquery.io/docs/trading/crypto-price-api/pairs/ Get crypto trading pair prices, volume, and OHLC with Bitquery Trading APIs using GraphQL queries and real-time stream options. # Pairs Cube The Pairs cube provides trading data for a base token traded against a quote token on a particular DEX or protocol. ## Getting the Most Accurate Token Price (Rank 1) {#most-accurate-token-price} :::tip Querying the price of a specific token? Use this pattern Query the **Pairs** cube with **`Ranking: { Position: { eq: 1 } }`**. This returns the token's price on its **top market** — the pool currently contributing the most volume to that token's price — rather than a value blended across every pool the token trades in. ::: ### Why the top market, and not the blended token price The [Tokens cube](/docs/trading/crypto-price-api/tokens) reports one price per token per chain, computed as a **volume-weighted blend of every pool** where the token is the base asset (see [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm)). That blend is the right answer when you want a single chain-wide number, and for a token whose liquidity sits in one deep pool the blended price and the top-market price agree closely. Fragmented tokens behave differently. When the same token trades across many pools — one primary pool plus a long tail of thin ones — every pool contributes to the blend in proportion to its decay-weighted volume. Thin pools quote wider, move on small trades, and can sit at prices the primary market has already left. Their share of the blend pulls the reported number away from the price you could actually trade at. Filtering `Pairs` to `Ranking.Position = 1` avoids that: you get the quote from the single market carrying the most volume for that token, which is the closest thing to an executable price. | You want | Use | | --- | --- | | The price of one specific token | **`Pairs` + `Ranking: { Position: { eq: 1 } }`** | | A firehose of every token on a chain, or one chain-wide number per token | [`Tokens`](/docs/trading/crypto-price-api/tokens) | | One number for an asset across all chains (BTC, ETH) | [`Currencies`](/docs/trading/crypto-price-api/currency) | | A specific pool you already know the address of | `Pairs` + `Pool: { Address: ... }` — see the note below | :::caution `Market.Address` is not the pool on EVM `Pool.Address` is the portable pool key and is the right filter on every chain. `Market.Address` means different things per chain: | Chain family | `Market.Address` | `Market.Program` | `Pool.Address` | | --- | --- | --- | --- | | EVM and Tron | the protocol **factory**, and an empty string for singleton protocols (uniswap_v4, balancer_v2, curve, fluid_dex) | the pool contract | the pool | | Solana | the pool | the DEX program | the pool | Filtering an EVM pool by `Market: { Address: ... }` therefore matches the factory and silently returns a **different pool's** data. ::: ## What Pairs adds over Tokens `Pairs` is `Tokens` **plus venue and quote identity** — there are no Tokens-only field paths. If you do not need `Market`, `Pool` or `QuoteToken`, query `Tokens`: it is the cheaper shape for the same price and volume data. `Pairs` carries **no liquidity, reserve or TVL data** — there is no such field and nothing in its filter surface. For pool depth, use the chain-level `DEXPools` cube, or a [cloud export](/docs/cloud/) for historical depth. ### Row fan-out One row per **(`Market.Address`, `Pool.Address`, `Pool.Id`, `QuoteToken.Id`)** per token per interval — not one row per market. A token quoted against two assets on the same market produces two rows. `Pool.Id` is empty on Solana, Tron and classic factory-per-pool AMMs. It is **populated on singleton-contract protocols** — Uniswap v4 and PancakeSwap Infinity — where it carries the pool identity that `Market.Address` cannot, because there `Market.Address` is empty and `Pool.Address` is the shared PoolManager. ## Ranking: how `Position` and `Weight` behave `Ranking.Position` is a **competition rank** by `Ranking.Weight` descending — not a dense index: - Ties occur, and are followed by a skipped value. - Additional gaps appear that ties alone do not explain, so the **highest `Position` always exceeds the row count**. - `Position` can be `0`, paired with `Weight` `0`. `Ranking.Weight` is in `[0, 1]` per row, and the per-token sum is centred on 1.0 but is **not guaranteed to equal it** — sums well above and below 1 both occur. **Normalise before treating weights as shares**; do not assume the blend is an identity. :::caution `Position: { eq: 1 }` silently drops tokens Filtering to rank 1 returns only tokens whose top market **traded in that interval**. A token whose rank-1 market was quiet is omitted entirely — a few percent of tokens per interval, varying by chain. For coverage, widen the interval or fall back to `Tokens`. ::: Chain-wide rank-1 sweeps are fast — a few seconds for a short interval, longer for hour-to-day windows returning tens of thousands of tokens. ### Latest price of a token from its top market [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 token's latest price on its top market. :::note `Price.IsQuotedInUsd` selects a denomination — it does not de-duplicate `IsQuotedInUsd` is a **denomination mode**, not a row multiplier. Markets are **not** published twice: an unfiltered query returns the USD rendering only, byte-identical to `IsQuotedInUsd: true`. You cannot retrieve both renderings in one result set, even by asking for both explicitly with `any:`. Setting it to `false` re-renders the same markets and intervals in quote-token units: only the `Price.*` fields change, while `Volume`, `Ranking` and `Supply` are identical between modes. You therefore do not need this filter to avoid duplicates — there are none. Set it to `false` only when you deliberately want the price in quote-token terms. Prices are in USD even when the quote token is WSOL or another non-stable asset, because the index normalizes the quote side — see [How Pool Prices Are Normalized](/docs/trading/crypto-price-api/price-index-algorithm#how-pool-prices-are-normalized-to-the-current-quote-token). ::: ### Stream the same price Change `query` to `subscription` and drop `limit`/`orderBy` to receive top-market updates as they happen: [Run Stream ➤](https://ide.bitquery.io/Token-price-stream-from-top-market--rank-1) ```graphql subscription { Trading { Pairs( where: { Token: { Address: { is: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" } Network: { is: "Solana" } } Ranking: { Position: { eq: 1 } } Interval: { Time: { Duration: { eq: 1 } } } Price: { IsQuotedInUsd: true } } ) { Token { Symbol Address } QuoteToken { Symbol } Market { Protocol Address } Price { IsQuotedInUsd Ohlc { Close } } Ranking { Position Weight } } } } ``` To stream the top market of **every** token on a chain, replace the `Token.Address` filter with `Token: { Network: { is: "Solana" } }` and keep the rank filter. ### Watchlist: top-market price for several tokens Add `limitBy` to collapse the result to one current row per token: :::caution Group by `Token_Id`, not `Token_Address` Native assets carry an **empty** `Token.Address` — ETH on Ethereum, Arbitrum, Base and Optimism, BNB on BSC and MATIC on Matic all share `""`. Grouping by `Token_Address` silently collapses every one of them into a single row. `Token_Id` is unique per token per network. ::: [Run query ➤](https://ide.bitquery.io/Multi-token-watchlist--rank-1-per-token) ```graphql { Trading { Pairs( where: { Token: { Address: { in: [ "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" "So11111111111111111111111111111111111111112" "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] } Network: { is: "Solana" } } Ranking: { Position: { eq: 1 } } Interval: { Time: { Duration: { eq: 60 } } } Price: { IsQuotedInUsd: true } Block: { Time: { since_relative: { minutes_ago: 10 } } } } limit: { count: 10 } limitBy: { by: Token_Id, count: 1 } orderBy: { descending: Block_Time } ) { Token { Symbol Address } QuoteToken { Symbol } Market { Protocol } Price { Ohlc { Close } } Ranking { Position Weight } Block { Time } } } } ``` ### Reading `Ranking.Weight` `Weight` is that market's share of the token's total decay-weighted volume, a float in `[0, 1]`; weights across all contributing pools sum to 1. Treat it as a confidence signal on the blended price: - **Weight close to 1** — a single pool drives essentially the whole token price. The blended `Tokens` price and the rank-1 price will be nearly identical, so either works. - **Low weight** — liquidity is fragmented across many pools and the blended price mixes all of them. This is exactly the case where the rank-1 price and the blended price diverge, and where the rank-1 price is the one you want. `Position` and `Weight` are computed over the rolling **1-hour, decay-weighted** window described in the [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm#ranking-on-trades-pairs-and-tokens) — not over the interval of the row you are reading. A rank-1 row can therefore report less `Volume.Usd` for its own interval than a lower-ranked row does for another. ### Things to know - **Rank 1 does not imply USD.** The rank filter selects the market, not the price denomination — always pair it with `Price: { IsQuotedInUsd: true }` (or read the `IsQuotedInUsd` field on each row) so you are not mixing USD and quote-token prices. - **The top market can change.** Ranking is recomputed as volume moves, so a token's rank-1 pool — and its quote token — may flip during a stream. Key your state on `Market.Address` from each message instead of assuming a fixed pool. - **Always scope the query.** A rank filter is not a substitute for a token filter: an unscoped rank-1 query across a whole chain scans very wide and can time out. Filter by `Token.Address` with `Network` (or `Market: { NetworkBid: { is: "bid:eth" } }` for lower latency), and add `Block: { Time: { since_relative: { minutes_ago: N } } }` for broad queries. - **Want the runner-up markets too?** Use `Ranking: { Position: { in: [1, 2, 3] } }` to compare a token's main venues — useful for spread and arbitrage checks. You can also sort by `orderBy: { descending: Ranking_Weight }`. - **`Ranking` does not exist on `Currencies`.** It is available on `Trades`, `Pairs`, and `Tokens` only. ## Schema and Fields ```graphql { Trading { Pairs( where: {Market: {Network: {is: "Solana"}, Address: {in: ["PAIR ADDRESS HERE"]}}, Interval: {Time: {Duration: {eq: 300}}}, Price: {IsQuotedInUsd: true}} orderBy: {descendingByField: "Block_Time"} ) { 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 #Are the values in USD or Native } } } } ``` - `Volume.Base`: Total amount of base token traded during the interval. - `Volume.Quote`: Sum of **quote token** amounts traded (e.g. USDT, USDC). For USD-base pairs this is not USD—it is the total in quote token units. For USD amounts use `Volume.Usd`. (As of March 11 2026, see [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm) for details.) - `Volume.Usd`: Total trade volume in USD. Use this when you need USD amounts. - `Volume.BaseAttributedToUsd`: Portion of the `Volume.Base` that was traded against quote tokens with known USD prices. Used to accurately calculate average USD price. - `Price.Ohlc.*`: OHLC candles (Open, High, Low, Close) for the interval, computed using only trades with known USD values. - `Price.IsQuotedInUsd`: Boolean flag indicating if the price values are quoted in USD. If `false`, the price is in quote token terms. - **`Supply`**: Currency-level metrics for the asset (not pair- or pool-specific). See [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) for definitions of each subfield. A rough pseudo-code of how price is calculated: ``` if quoteInUsd { vol.AveragePrice.Price = vol.AveragePrice.Usd / vol.AveragePrice.BaseAttributedToUsd } else { vol.AveragePrice.Price = vol.AveragePrice.Quote / vol.AveragePrice.Base } ``` For an in-depth breakdown of how quote and base are assigned, see [Breaking Down Price Streams in Detail](/docs/trading/crypto-price-api/in-depth). It is not necessary for basic use. --- ## Custom DataFeed Setup URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/custom_datafeed/ Build Custom DataFeed Setup: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Custom DataFeed Setup In this section, we will create a custom DataFeed for TradingView's Advanced Charting Library, integrating historical OHLC data and real-time data subscription. We will use three main files: - `getBars.js`: Handles fetching historical data and subscribing to real-time updates. - `resolveSymbol.js`: Provides symbol resolution for the chart. - `onReady.js`: Supplies configuration details like supported resolutions. Finally, we will integrate everything into `customDatafeed.js`. --- ### 1. `getBars.js` The `getBars.js` file is responsible for fetching historical data and subscribing to real-time data streams via WebSocket. ```javascript // Fetch historical data bars export const getBars = async ( symbolInfo, resolution, periodParams, // compulsorily needed by TradingView onHistoryCallback, onErrorCallback ) => { try { console.log( "[getBars]: Fetching bars for", symbolInfo, resolution, periodParams.from, periodParams.to ); // Fetch historical data const bars = await fetchHistoricalData(); // Pass bars to the chart if data is available if (bars.length > 0) { onHistoryCallback(bars, { noData: false }); } else { onHistoryCallback([], { noData: true }); } } catch (err) { console.error("[getBars] Error fetching data:", err); onErrorCallback(err); } }; // Subscribe to real-time data using WebSocket export const subscribeBars = ( symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback ) => { subscribeToWebSocket(onRealtimeCallback); }; // Unsubscribe from real-time data export const unsubscribeBars = (subscriberUID) => { delete this.subscribers[subscriberUID]; }; ``` - **getBars**: This function fetches historical OHLC data using `fetchHistoricalData()` and provides it to TradingView. It handles the `periodParams` provided by TradingView, which indicates the required time range. - **subscribeBars**: Subscribes to a WebSocket data feed using `subscribeToWebSocket()` for real-time trade data, emitting bars as new trades come in. - **unsubscribeBars**: Unsubscribes from the real-time WebSocket data feed, stopping the stream of new bars. --- ### 2. `resolveSymbol.js` The `resolveSymbol.js` file defines how a symbol (like a token pair) is resolved. TradingView needs to resolve symbols before fetching data for them. ```javascript export const resolveSymbol = ( symbolName, onSymbolResolvedCallback, onResolveErrorCallback, extension ) => { // Hardcode or query token symbol const tokenSymbol = "BCAT"; if (!tokenSymbol) { onResolveErrorCallback(); } else { const symbolInfo = { ticker: tokenSymbol, name: `${tokenSymbol}/WSOL`, session: "24x7", timezone: "Etc/UTC", minmov: 1, pricescale: 1000,// set it dynamically according to price range of token has_intraday: true, intraday_multipliers: ["1", "5", "15", "30", "60"], has_empty_bars: false, has_weekly_and_monthly: false, supported_resolutions: ["1", "5", "15", "30", "60", "1D", "1W", "1M"], supported_intervals: ["1", "5", "15", "30", "60", "1D", "1W", "1M"], countBack: 30, volume_precision: 2, visible_plots_set: 'ohlcv', }; onSymbolResolvedCallback(symbolInfo); } }; ``` - **resolveSymbol**: Resolves a symbol (like a token's ticker) for TradingView. It provides metadata like the symbol's name, supported time intervals, and price scale. In this case, the symbol is hardcoded as `BCAT`, but it can be dynamic based on the query or user input. --- ### 3. `onReady.js` The `onReady.js` file provides the configuration data that TradingView needs when it initializes the chart, such as supported time resolutions. ```javascript const configurationData = { supported_resolutions: ['1', '5', '15', '30', '60', '1D', '1W', '1M'], // ... other configuration data }; export const onReady = (callback) => { setTimeout(() => callback(configurationData), 0); }; ``` - **onReady**: This function provides the configuration of the charting library, including the supported time intervals for the chart (1 minute, 5 minutes, etc.). It’s called when the chart is first initialized. --- ### 4. `customDatafeed.js` This file ties everything together, providing the custom DataFeed object that TradingView requires to interface with the charting library. ```javascript // Datafeed object for TradingView const Datafeed = { onReady, resolveSymbol, getBars, subscribeBars, unsubscribeBars, }; export default Datafeed; ``` - **Datafeed**: This object integrates the `onReady`, `resolveSymbol`, `getBars`, `subscribeBars`, and `unsubscribeBars` functions into a complete custom DataFeed that TradingView uses to fetch historical and real-time OHLC data. --- ### How It Works 1. **Resolving the Symbol**: The chart resolves the symbol via `resolveSymbol.js`, which provides metadata about the token pair (e.g., POPCAT/WSOL). 2. **Fetching Historical Data**: When the chart requests historical data, `getBars.js` fetches the data via the `fetchHistoricalData` method and returns it to TradingView. 3. **Subscribing to Real-Time Data**: The chart subscribes to real-time updates via WebSocket using `subscribeBars` in `getBars.js`. New bars are emitted as trades occur. 4. **Configuration**: `onReady.js` provides configuration data like supported time intervals to TradingView. 5. **Unsubscribing**: When real-time updates are no longer needed, `unsubscribeBars` stops the WebSocket feed. --- ## Dashboard : Top 10 Ethereum Token Pairs URL: https://docs.bitquery.io/docs/usecases/Top-10-ethereum-tokens/ Build Dashboard : Top 10 Ethereum Token Pairs: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Dashboard : Top 10 Ethereum Token Pairs The following tutorial helps build a Top 10 Ethereum Tokens Dashboard with Next JS and Bitquery APIs that fetches and displays the Top 10 Token Pairs in last 1 hour on Ethereum mainnet in desceneding order of number of transactions. ## Video Tutorial of the project ## Github Repository Github Code Repository - [Repository Link](https://github.com/Akshat-cs/Top-10-Ethereum-Tokens-live.git) ## Prerequisites 1. **Node.js** and **npm** installed on your system. 2. **Bitquery Account** with OAuth token (follow instructions [here](/docs/authorization/how-to-generate/)). 3. **Git** installed on your pc. ## Code Walkthrough ### Data Fetching We are going to fetch our dashboard data using these 3 files - #### 1. **data.js file** - We are using Bitquery API and made this `getData` utility function which will fetch top 10 Token Pairs according to the transactions happened in them in last 1 hour. Query used here gives the top pair addresses, tokens in them, total transactions, total buys, total sells, price in usd of the token ( at last 1 hour timestamp, past 5 min timestamp and current timestamp). We will use these prices to get Price change in our `page.js` file. You can test the saved query on ide [here](https://ide.bitquery.io/Top-10-pairs-on-ethereum_2). ```javascript // Function to fetch trade data const getData = async () => { // Get the current UTC time and subtract one hour and five minutes const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString(); const min5backtimestamp = new Date(Date.now() - 5 * 60 * 1000).toISOString(); let data = JSON.stringify({ query: ` query MyQuery($oneHourAgo: DateTime, $min5backtimestamp: DateTime) { EVM(network: eth, dataset: realtime) { DEXTradeByTokens( orderBy: {descendingByField: "total_trades"} limit: {count: 10} where: { Block: {Time: {since: $oneHourAgo}}, Trade: {Side: {Currency: {SmartContract: {is: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}}}}, TransactionStatus: {Success: true} } ) { Trade { hour1: PriceInUSD(minimum: Block_Time) min5: PriceInUSD(minimum: Block_Time, if: { Block: { Time: { after: $min5backtimestamp } } }) end: PriceInUSD(maximum: Block_Time) Currency { Name Symbol } Side { Currency { Name Symbol } } Dex { ProtocolName ProtocolFamily ProtocolVersion SmartContract } } total_trades: count totalbuys: count(if: {Trade: {Side: {Type: {is: sell}}}}) totalsells: count(if: {Trade: {Side: {Type: {is: buy}}}}) total_traded_volume: sum(of: Trade_Side_AmountInUSD) } } } `, variables: { oneHourAgo, min5backtimestamp }, }); let config = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", "X-API-KEY": "BQY7em4KPZ9CMvjl4aUUOPf2hqEBIHF1", Authorization: process.env.NEXT_PUBLIC_BITQUERY_TOKEN, }, data: data, }; let response = await axios.request(config); return response.data.data.EVM.DEXTradeByTokens; }; export default getData; ``` #### 2. **liquidity.js file** - We are using Bitquery API here and have made this utility function `getLiquidityData` to get `current liquidity` and `initial liquidity` of a particular pool. We are going to pass the 10 pool addresses to `getLiquidityData` we are going to get when we call `getData` in `page.js` and get the initial and current liquidities for the top 10 pools. ```javascript // liquidityData.js const getLiquidityData = async (poolAddresses) => { let data = JSON.stringify({ query: ` query MyQuery ($poolAddresses: [String!]){ EVM(dataset: combined, network: eth) { Initial_liquidity: Transfers( limitBy:{by:Transfer_Receiver count:2} orderBy: {ascending: Block_Time} where: {Transfer: {Receiver: {in: $poolAddresses }}, TransactionStatus: {Success: true}} ) { Transaction { Hash } Transfer { Receiver Amount Currency { SmartContract Name Symbol } } } Current_liquidity: Balances( where: {Balance: {Address: {in: $poolAddresses}}} orderBy: {descending: Balance_Amount} ) { Currency { Name SmartContract Symbol } Balance { Address Amount(selectWhere: {gt: "0"}) } } } } `, variables: { poolAddresses }, }); let config = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", "X-API-KEY": "BQYD4KszrYRIhb3cjIylKVLILgZfV0Ai", Authorization: process.env.NEXT_PUBLIC_BITQUERY_TOKEN, }, data: data, }; let response = await axios.request(config); return response.data.data.EVM; }; export default getLiquidityData; ``` #### 3. **wethPrice.js file** - We used Bitquery API here to get the latest USD price of `WETH` token as it will help us in converting Amount of WETH to USD Amount in current liquidity. You can test the saved query on ide [here](https://ide.bitquery.io/get-weth-price). ```javascript const getPrice = async () => { let data = JSON.stringify({ query: ` query MyQuery{ EVM(network: eth, dataset: realtime) { DEXTradeByTokens( limit: {count: 1} orderBy: {descending: Block_Time} where: {Trade: {Currency: {SmartContract: {is: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}}, Side: {Currency: {SmartContract: {is: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}}}}, TransactionStatus: {Success: true}} ){ Trade{ PriceInUSD } } } } `, variables: {}, }); let config = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", "X-API-KEY": "BQYD4KszrYRIhb3cjIylKVLILgZfV0Ai", Authorization: process.env.NEXT_PUBLIC_BITQUERY_TOKEN, }, data: data, }; let response = await axios.request(config); return response.data.data.EVM.DEXTradeByTokens; }; export default getPrice; ``` ### Dashboard frontend #### **page.js file** The `page.js` file is a React component that serves as the main page for displaying the top 10 Ethereum token pairs traded in the last hour. It uses Bitquery APIs to fetch and display various data, including trade information, token prices, and liquidity data. #### 1. Imports The component imports necessary dependencies, including React, hooks (`useState`, `useEffect`), and three functions (`getData`, `getPrice`, `getLiquidityData`) used to fetch trade details, weth price, and liquidity data, respectively. #### 2. State Management It uses the `useState` hook to manage several states, such as `trades`, `liquidityData`, `currentPage`, and `price`. #### 3. Component Structure ##### a. **State Initialization**: - `trades`: Stores the trade data. - `liquidityData`: Stores initial and current liquidity data. - `currentPage`: Keeps track of the current page number for pagination. - `price`: Stores the price data. ##### b. **Data Fetching with `useEffect`**: - The first `useEffect` fetches trade and price data using `getData` and `getPrice` functions and sets the `trades` and `price` states. - The second `useEffect` fetches liquidity data once the `trades` data is available. It extracts pool addresses from the trades and fetches corresponding liquidity data using the `getLiquidityData` function. ##### c. **Utility Functions**: - `truncateAddress(address)`: Truncates a given address for display purposes. - `calculatePriceChange(endPrice, hour1Price, min5Price)`: Calculates the percentage change in price over the last hour and the last 5 minutes. - `formatInitialLiquidity(liquidityList)`: Formats the initial liquidity for display, showing both token amounts. - `formatCurrentLiquidity(liquidityList, rowCurrencySymbol, currentPrice)`: Formats the current liquidity in USD based on the token amounts and prices. ##### d. **Rendering**: - The component displays a table with information about the top 10 Ethereum pairs, including token pair, price, DEX, smart contract address, price change, total traded volume, transaction count, initial liquidity, and current liquidity. - It uses the `currentTrades` variable to slice the trades array for pagination and display the trades of the current page. ##### e. **Export**: - The component is exported as the default export of the file. #### 4. **Key Components in the Table** - **Token Pair**: Displays the symbol of the trading pair. - **Price**: Shows the current price in USD. - **DEX**: The decentralized exchange where the pair is traded. - **Pair Smart Contract**: A link to the smart contract address on the Bitquery explorer. - **Price Change (1 Hour and 5 Min)**: Displays the percentage price change in the last hour and last 5 minutes. - **Total Traded Volume**: The total volume traded for the pair. - **Txns**: The number of transactions, including buys and sells. - **Initial Liquidity**: The initial liquidity provided in the pool. - **Current Liquidity**: The current liquidity in USD. --- ## Derived Values with calculate() URL: https://docs.bitquery.io/docs/trading/query-operators/calculate-expressions/ calculate(expression:) evaluates an expression server-side on the Bitquery Trading cube — the $ reference rule, when it forces a GROUP BY, and the coercion and non-finite traps. # Derived Values with `calculate()` `calculate(expression: "...")` computes a derived value server-side, so ratios, differences and percentages come back with the row instead of being assembled in your client. ```graphql buys: count(if: { Side: { is: "Buy" } }) all: count ratio: calculate(expression: "$buys / $all") ``` ## `$name` refers to a response key Inside an expression, `$name` refers to **a key in the same selection set** — the explicit alias when you give one, otherwise the field's own path with nested levels joined by underscore: | Selection | Reference | | --- | --- | | `all: count` | `$all` | | `Amounts { Base }` | `$Amounts_Base` | | `q: Amounts { Base }` | `$q_Base` | It reaches native fields, native metrics and other `calculate` results alike. **The `$` is required** — a bare `buys / all` is passed through as raw column names and fails. References are **order-independent** (a forward reference is fine) and chain to any depth. They compile to SQL aliases, so a self-reference fails with `Code: 47 MISSING_COLUMNS` and a mutual reference with `Code: 174 CYCLIC_ALIASES`. ## When your query becomes aggregated **`calculate` never triggers a GROUP BY on its own.** What matters is whether a *native* metric (`count`, `sum`, `uniq`, `average`, …) is present: - **No native metric** — the query stays in row mode. A bare column reference such as `"Amounts_Base"` evaluates per row and works fine. - **A native metric is present** — the API groups by the selected dimensions. Any bare column in your expression must now be one of those selected dimensions, or ClickHouse raises `Code: 215 NOT_AN_AGGREGATE`. :::caution The error names the wrong field `Code: 215` names the **dimension**, not your expression — for example `Column 'Price' is not under aggregate function and not in GROUP BY`. The fix is usually to add a native metric, or to select the column your expression references. ::: An aggregate expression on its own is fine — `calculate(expression: "sum(Amounts_Base)")` with nothing else selected returns one row. It only needs a companion native metric once you also select a dimension. ## What you can write The expression is evaluated by ClickHouse, so the standard function library is available: arithmetic (`+ - * / %`), `pow`, `sqrt`, `abs`, `log`, `round`, `greatest`, `least`, conditionals (`if`, `multiIf`), aggregates (`sum`, `count`, `avg`, `min`, `max`, `uniqExact`, `argMax`, `sumIf`, `countIf`, `quantile(0.5)(...)`) and window forms such as `row_number() OVER ()`. ```graphql buyShare: calculate(expression: "countIf(Side = 'Buy') / count()") ``` Use **single quotes** for SQL string literals — the expression is a GraphQL string, so a double quote must be escaped. Commas, nested parentheses, newlines and SQL comments are all fine. ## Ranking by a derived value `orderBy` accepts a `calculate` alias, and it composes with `limitBy` — this is the rank-by-derived-ratio pattern: ```graphql orderBy: { descendingByField: "buyShare" } limitBy: { by: Pair_Token_Address, count: 1 } ``` `calculate` itself takes **no `selectWhere` and no `if:`** — `expression` is its only argument. To gate a ranked ratio, put a `selectWhere` floor on a sibling aggregate (for example `count(selectWhere: { ge: "100" })`) and apply the final cut on the ratio in your client. ## Traps | Trap | Behaviour | | --- | --- | | **Everything coerces to Float** | The expression is wrapped in `toFloat64(...)` and the field's type is `Float`. A string-producing expression fails with `Code: 6` unless the string parses as a number — `toString(42)` works, `concat('a','b')` does not | | **`^` is not exponentiation** | `2^3` fails with `Code: 62`. Use `pow(2,3)` | | **The ternary `? :` is unusable** | `?` collides with the SQL builder's placeholder — `not enough args when interpolating`. Use `if(cond, a, b)` | | **Booleans and dates flatten** | `Bool` → `1`/`0`, `DateTime` → unix seconds, `Date` → days since epoch | :::danger Infinity, NaN and NULL are silently returned as `0` A divide-by-zero ratio comes back as `0`, indistinguishable from a genuine zero — and under `ascendingByField` those rows sort **first**. The values really are non-finite inside ClickHouse (`isInfinite(count()/0)` returns `1`), so guard the denominator yourself: ```graphql safe: calculate(expression: "$numerator / nullIf($denominator, 0)") ``` ::: ## In subscriptions The grammar is much narrower on a stream: **arithmetic only**. Every function call — `round()`, `floor()`, `abs()` — returns `null`, and a single unsigiled reference makes the whole expression `null` with no error. See [what does not survive streaming](/docs/subscriptions/what-does-not-survive-streaming/). ## Related - [Expressions (general reference)](/docs/graphql/capabilities/expression/) - [Screeners with `selectWhere`](/docs/trading/query-operators/selectwhere-screeners/) - [Statistics](/docs/trading/query-operators/statistics/) --- ## Detect Sandwich Attack Opportunities URL: https://docs.bitquery.io/docs/usecases/sandwich-detection/ Build Detect Sandwich Attack Opportunities: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # How to Detect Sandwich Opportunities - Tutorial ### MEV Opportunities Detection Bitquery's queries can help users in DeFi space by providing them with the potential MEV opportunities. By providing real-time data from Mempool on transaction amount, and other key metrics, Bitquery can help users to identify such money making opportunities. ## Tutorial This is a tutorial to build a simple webpage using PHP code that connects to the Bitquery API and retrieves data for sandwich attack opportunities on the Ethereum network. The code then displays the data on a webpage built using PHP and HTML. ## GraphQL query For this tutorial, we will use [this](https://ide.bitquery.io/Sandwitch-Opportunity_1) query given below. The sandwich attack observes the DEX Trade transaction in Mempool with a large purchase amount as it will highly influence the price of the token. For this tutorial, the logic is that any transaction with a 'Buy Amount' greater than 1000 USD would influence the price of the token in the pool, hence we will place two transactions around the target transaction, so that we could get an instantaneous profit on the same. ```graphql subscription { EVM(mempool: true) { DEXTrades(where: {Trade: {Buy: {AmountInUSD: {gt: "1000"}}}}) { Trade { Buy { AmountInUSD Buyer Currency { Name Symbol SmartContract } } Dex { OwnerAddress SmartContract ProtocolFamily } Sell { AmountInUSD Currency { Name Symbol SmartContract } } } Block { Time } } } } ``` ## Required Libraries The code uses the following libraries: HTTP_Request2: A PHP library for interacting with APIs ## Step by Step Code Implementation ### Installation Before writing any code, make sure that the required libraries are installed using the following command: ```shell sudo pear install http_request2 ``` ### Importing the Required Libraries The first step in the code is to import the required libraries using the import statement: ```php ``` ### Establishing Connection with the Bitquery API Next, the code connects to the Bitquery API using the HTTP_Request2 library and retrieves data on the latest sandwich opportunities across all EVM chains (in support) using a GraphQL query. The query is passed as a JSON payload to the request() method, along with the necessary headers and oAuth Token. ```php $request = new HTTP_Request2(); $request->setUrl('https://streaming.bitquery.io/graphql'); $request->setMethod(HTTP_Request2::METHOD_POST); $request->setConfig(array( 'follow_redirects' => TRUE )); $request->setHeader(array( 'Content-Type' => 'application/json', 'X-API-KEY' => 'BQYVRzw02D5V2rWpWFii1pEbgLWCdx1y', 'Authorization' => 'Your Bitquery oAuth Token' )); $request->setBody('{"query":"subscription {\\n EVM(mempool: true) {\\n DEXTrades(where: {Trade: {Buy: {AmountInUSD: {gt: \\"1000\\"}}}}) {\\n Trade {\\n Buy {\\n AmountInUSD\\n Buyer\\n Currency {\\n Name\\n Symbol\\n SmartContract\\n }\\n }\\n Dex {\\n OwnerAddress\\n SmartContract\\n ProtocolFamily\\n }\\n Sell {\\n AmountInUSD\\n Currency {\\n Name\\n Symbol\\n SmartContract\\n }\\n }\\n }\\n Block {\\n Time\\n }\\n }\\n }\\n}\\n","variables":"{}"}'); $response = $request->send(); $responseBody = $response->getBody(); $responseData = json_decode($responseBody, true); $sandwichOpportunityData = $responseData['data']['EVM']['DEXTrades'] ``` The code retrieves an array of necessary data to execute trade from the response data and stores it in the sandwichOpportunityData variable. ### Building the webpage The code then displays the retrieved data in a PHP webpage built using basic HTML. The webpage includes a simple table with all the real time data retrieved from the Bitquery API. ```php echo ''; echo '

Sandwich MEV Opportunities

'; echo '

Possibilities on EVM chains

'; echo '
';
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 = ''; $html .= ''; $columns = getTableHeaders($flattenedData); foreach ($columns as $column) { $html .= ''; } $html .= ''; $html .= ''; foreach ($flattenedData as $row) { $html .= ''; foreach ($columns as $column) { $html .= ''; } $html .= ''; } $html .= '
' . htmlspecialchars($column) . '
' . htmlspecialchars($row[$column] ?? '') . '
'; return $html; } ``` ### Important Note Make sure that all the above code snippets lies within the php tag shown below. ```php ``` ## Here's how it looks If you want to build up query from scratch you are welcome or you can use the [premade examples](https://ide.bitquery.io/explore/All%20queries) as well. ## Video Tutorial on How to Detect Sandwich Opportunities --- ## End of Day Balances - Historical Balance Snapshots URL: https://docs.bitquery.io/docs/usecases/end-of-day-balances/ Get an address's token balance as of a past date on EVM, Tron and Solana, for accounting, tax reporting and reconciliation. # End-of-day balances Accounting, tax reporting and reconciliation all need the same thing: what an address held at the close of a given day, not what it holds now. This page covers that on EVM, Tron and Solana. For current holdings instead, see the [wallet portfolio recipe](/docs/usecases/wallet-portfolio-api/). ## EVM and Tron The `Holders` cube takes a `date` argument and returns the holder set as of that date. Filter it to a single address and you have that address's end-of-day balance. ```graphql query EndOfDayBalance { EVM(network: eth, dataset: archive) { Holders( date: "2026-07-01" where: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } Holder: { Address: { is: "0x28c6c06298d514db089934071355e5743bf21d60" } } } ) { Holder { Address } balance: sum(of: Balance_Amount) } } } ``` Run it across consecutive dates and you get a genuine daily series, not a running total: | Date | USDT balance | |---|---:| | 2026-06-28 | 520,591,652.30 | | 2026-06-29 | 638,001,909.50 | | 2026-06-30 | 561,901,590.46 | | 2026-07-01 | 819,349,860.88 | | 2026-07-02 | 550,887,384.37 | (An exchange hot wallet, so the swings are real activity rather than an artefact.) `date` is a single scalar, so a range means one request per day. Issue them in parallel and assemble the series client-side. The same query shape works on Tron with `Tron { Holders(...) }`. :::caution Use `dataset: archive` for past dates The default realtime dataset only carries a recent window, so an older `date` comes back empty and looks like the address held nothing. Empty results here almost always mean the wrong dataset rather than a zero balance. ::: ### The same series from `Balances` `Balances` carries **daily aggregates**, one row per address per day, exposed as `Block.Date`. That makes it the more natural source for a series, because a single query returns every day at once instead of one request per date: ```graphql query DailyBalanceSeries { EVM(network: eth, dataset: archive) { Balances( where: { Balance: { Address: { is: "0x28c6c06298d514db089934071355e5743bf21d60" } } Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } } orderBy: { descending: Block_Date } limit: { count: 30 } ) { Block { Date } Balance { Amount AmountInUSD } } } } ``` For a single past date, add `Block: { Date: { till: "2026-07-01" } }` and keep the descending order — the first row is that day's closing balance. :::caution Always order by `Block_Date` Without `orderBy: { descending: Block_Date }` you get an arbitrary day's row, not the latest or the one you filtered to. The query still succeeds, so the mistake is silent — it looks like a current balance and is not. With the ordering, `Balances` and `Holders(date: …)` agree exactly. Both return `819349860.876615` for the wallet and date above. ::: ## Solana Solana has no `Holders` cube, so take the last balance update at or before the cutoff. `limitBy` collapses to one row per mint: ```graphql query SolanaEndOfDayBalance { Solana { BalanceUpdates( where: { BalanceUpdate: { Account: { Owner: { is: "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" } } } Block: { Time: { till: "2026-08-02T23:59:59Z" } } } orderBy: { descending: Block_Time } limitBy: { by: BalanceUpdate_Currency_MintAddress, count: 1 } limit: { count: 50 } ) { Block { Time } BalanceUpdate { PostBalance PostBalanceInUSD Currency { Symbol MintAddress } } } } } ``` Three parts do the work, and dropping any one of them gives a wrong answer: - `Block: { Time: { till: ... } }` sets the cutoff. - `orderBy: { descending: Block_Time }` puts the most recent update first. - `limitBy` on the mint keeps exactly one row per token. `PostBalance` is the balance immediately after that update, which is the balance the address carried into the next day. The returned `Block.Time` tells you how stale the figure is: a token last touched weeks before the cutoff still reports its correct balance, just with an older timestamp. ## Reconciliation notes - **Set your day boundary explicitly.** `till: "2026-08-02T23:59:59Z"` is UTC. If your books close in another timezone, convert before querying rather than after. - **A missing row is not a zero balance.** On Solana, an address that never held a token has no balance update for it at all. Treat absent and zero as different cases. - **Large tokens can time out.** A dated `Holders` query filtered to one address is cheap, but the same query without an address filter on a token with millions of holders may exceed the request timeout. Always include the address filter when you want one balance. - **Cross-check one date against a block explorer** before trusting a whole series. It is the fastest way to catch a timezone or dataset mistake. ## Related - [Wallet portfolio](/docs/usecases/wallet-portfolio-api/) — current holdings - [Balances & Holders cubes](/docs/cubes/balances-cube/) - [Build your own crypto P&L calculator](/docs/usecases/p-l-product/overview/) --- ## Fetching Real-time OHLC URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/realtime_OHLC/ Build Fetching Real-time OHLC: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Fetching Real-time OHLC We now use the new [Price Index Streams](/docs/trading/crypto-price-api/examples/#ohlc-stream-on-a-chain) to fetch **pre-aggregated OHLC data** directly from Bitquery's GraphQL WebSocket API. This removes the need to manually calculate candlesticks from raw trade data. To learn more about streaming data via graphQL, visit [Bitquery subscriptions](/docs/subscriptions/subscription/). ### Imports and Configuration ```javascript ``` - **createClient**: From the `graphql-ws` library, used to subscribe to GraphQL streams. - **config**: A local file storing your Bitquery API token. ```javascript let client; /** Last emitted bar time and close — used to stitch new candles to the previous close. */ let lastEmittedBarTime = null; let lastEmittedClose = null; const BITQUERY_ENDPOINT = "wss://streaming.bitquery.io/graphql?token=" + config.authtoken; ``` ### Subscription Query :::tip Charting one specific token? Prefer Pairs + rank 1 The subscription below uses the `Tokens` cube, whose price blends every pool where the token is base. For a chart of **one** token, subscribe to [`Pairs` with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) instead — the same `Price.Ohlc` fields, taken from the token's top market. Note that the top market can change during a stream, so read `Market.Address` from each message rather than assuming a fixed pool. ::: [Run Stream on IDE](https://ide.bitquery.io/1-second-crypto-price-stream) **We have used Solana as an example below, you can remove it and get data for all chains provided by the Price API** ```javascript const subscriptionQuery = ` subscription{ Trading { Tokens( where: {Token: {Network: {is: "Solana"}, Address: {is: "6ft9XJZX7wYEH1aywspW5TiXDcshGc2W2SqBHN9SLAEJ"}}, Interval: {Time: {Duration: {eq: 1}}}} ) { Block { Time } Price { Ohlc { Open High Low Close } } Volume { Base Quote } Supply { TotalSupply MarketCap FullyDilutedValuationUsd } } } } `; ``` - This query subscribes to **pre-aggregated 1-second OHLC bars** for a token on the Solana network (interval duration `1` in the `where` clause). - It requests: - **Block** — `Time` (bar timestamp) - **Price.Ohlc** — `Open`, `High`, `Low`, `Close` - **Volume** — `Base`, `Quote` - **Supply** — `TotalSupply`, `MarketCap`, `FullyDilutedValuationUsd` --- ### Subscribing to the Stream Keep the **last emitted bar’s time and close** in module-level variables. When the stream moves to a **new** candle (timestamp changes), set the new bar’s **open** to **last candle’s close** and widen **high** / **low** to include that price—same idea as [historical bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/), but applied live as bars arrive. ```javascript export function subscribeToWebSocket(onRealtimeCallback) { lastEmittedBarTime = null; lastEmittedClose = null; client = createClient({ url: BITQUERY_ENDPOINT }); const onNext = (data) => { const tokenData = data.data?.Trading?.Tokens?.[0]; if (!tokenData) return; const bar = { time: new Date(tokenData.Block.Time).getTime(), open: tokenData.Price.Ohlc.Open, high: tokenData.Price.Ohlc.High, low: tokenData.Price.Ohlc.Low, close: tokenData.Price.Ohlc.Close, volume: tokenData.Volume.Base, }; const isNewCandle = lastEmittedBarTime !== null && bar.time !== lastEmittedBarTime; if (isNewCandle && lastEmittedClose != null) { bar.open = lastEmittedClose; bar.high = Math.max(bar.high, lastEmittedClose); bar.low = Math.min(bar.low, lastEmittedClose); } lastEmittedBarTime = bar.time; lastEmittedClose = bar.close; onRealtimeCallback(bar); }; client.subscribe( { query: subscriptionQuery }, { next: onNext, error: console.error } ); } ``` - **subscribeToWebSocket**: - Connects to Bitquery using `graphql-ws`. - On each message, normalizes the bar for continuity when the interval rolls forward, then passes it to `onRealtimeCallback`. ### Unsubscribing from the Stream ```javascript export function unsubscribeFromWebSocket() { if (client) { client.dispose(); } lastEmittedBarTime = null; lastEmittedClose = null; } ``` - **unsubscribeFromWebSocket**: Terminates the WebSocket connection and clears continuity state so a later reconnect does not stitch against stale closes. --- ## Fetching Solana DEX Trades for the Wash Trading Detector URL: https://docs.bitquery.io/docs/usecases/wash-trading-detector/prepare-data/getTrades/ Data collection for the Solana wash trading detector: a Bitquery GraphQL query for the latest DEX trades with both sides and USD prices, wrapped in Python. # Fetching Solana DEX Trades for the Wash Trading Detector The [wash trading detector](/docs/usecases/wash-trading-detector/overview) starts from a batch of recent Solana DEX trades. This module fetches them from Bitquery with one GraphQL query and returns them as a list of dictionaries that the [labelling rules](/docs/usecases/wash-trading-detector/prepare-data/rules) and the model read. Everything the rules need is in the row: the buy and sell side with the trading account, amount and USD price, the DEX, the block and the signature. The full project is on [GitHub](https://github.com/Kshitij0O7/wash-trading-detector). ## The query The newest successful trades across all Solana DEXs. `limit` bounds the batch; raise it for training runs and lower it for the live dashboard. Solana queries go to the `eap` endpoint. Run it in the [IDE](https://ide.bitquery.io/Solana-dextrades) first. ```graphql { Solana { DEXTrades( limit: { count: 200 } orderBy: { descending: Block_Time } where: { Transaction: { Result: { Success: true } } } ) { Trade { Dex { ProtocolName ProtocolFamily } Buy { Account { Address } Amount AmountInUSD Currency { Symbol Name MintAddress } PriceInUSD } Sell { Account { Address } Amount AmountInUSD Currency { Symbol Name MintAddress } PriceInUSD } } Block { Time Height } Transaction { Signature FeePayer } } } } ``` Both sides carry an `Account`, so the rules can compare buyer and seller, and `FeePayer` identifies who paid for the transaction, which is what a self-trade check needs. ## The `get_trades` function The function reads the access token from Streamlit secrets so the key never sits in the code, posts the query, and returns the list of trades or an empty list on any failure. Generate a token on the [access tokens page](https://account.bitquery.io/user/api_v2/access_tokens). ```python URL = "https://streaming.bitquery.io/graphql" QUERY = """ { Solana { DEXTrades( limit: { count: 200 } orderBy: { descending: Block_Time } where: { Transaction: { Result: { Success: true } } } ) { Trade { Dex { ProtocolName ProtocolFamily } Buy { Account { Address } Amount AmountInUSD Currency { Symbol Name MintAddress } PriceInUSD } Sell { Account { Address } Amount AmountInUSD Currency { Symbol Name MintAddress } PriceInUSD } } Block { Time Height } Transaction { Signature FeePayer } } } } """ def get_trades(): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {st.secrets['token']}", } try: response = requests.post(URL, headers=headers, data=json.dumps({"query": QUERY}), timeout=60) response.raise_for_status() return response.json()["data"]["Solana"]["DEXTrades"] except (requests.exceptions.RequestException, KeyError, json.JSONDecodeError) as err: print(f"Error fetching DEX trades: {err}") return [] ``` ## What the rest of the project does with the rows - The [labelling rules](/docs/usecases/wash-trading-detector/prepare-data/rules) flag trades where the same account is on both sides, repeated round trips between two accounts, and prices far from the token's recent range. - The [labelling module](/docs/usecases/wash-trading-detector/prepare-data/label) applies those rules and writes an `is_wash_trades` column. - [Training](/docs/usecases/wash-trading-detector/training) fits an XGBoost model on the labelled rows, and the [app](/docs/usecases/wash-trading-detector/app) scores fresh batches from this same function. ## Related pages - [Wash trading detector overview](/docs/usecases/wash-trading-detector/overview) - [Solana DEX trades API](/docs/blockchain/Solana/solana-dextrades) - [WebSocket subscriptions](/docs/subscriptions/websockets/) --- ## Getting Historical Data URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/historical_OHLC/ Build Getting Historical Data: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Getting Historical Data In this section, we will write the code to get historical OHLC data to populate a chart with candlesticks up to the current timestamp. Create a new file called `histOHLC.js` and add the following code. Each part of the code is explained below. ### Imports ```javascript ``` - **axios**: Axios is used to make HTTP requests to the Bitquery API. - **config**: A local JSON file storing your API token securely. - **connectBarContinuity**: Adjusts each bar’s open, high, and low so candles connect visually to the previous bar’s close (see [Bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/)). ### API Endpoint and Query We are using the [Tokens Cube from the Crypto Price API](/docs/trading/crypto-price-api/) which gives you price of a token on different chains in **USD**. You can use Pairs Cube as well to get price against specific currency. :::tip Charting one specific token? Prefer Pairs + rank 1 The `Tokens` candle blends every pool where the token is base, so thin pools contribute to the bar. For a chart of **one** token, query [`Pairs` with `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) instead — same `Price.Ohlc` fields, but taken from the token's top market. Keep the `Market.Address` from each row if you want to show which venue the candle came from. ::: [You can test the query here](https://ide.bitquery.io/Historical-price-data) **We have used Solana as an example below, you can remove it and get data for all chains provided by the Price API** ```javascript const endpoint = "https://streaming.bitquery.io/graphql"; ``` ```javascript const TOKEN_DETAILS = ` { Trading { Tokens( where: { Token: { Network: {is: "Solana"}, Address: {is: "6ft9XJZX7wYEH1aywspW5TiXDcshGc2W2SqBHN9SLAEJ"} }, Interval: {Time: {Duration: {eq: 60}}} }, orderBy: {descending: Block_Time}, limit: {count: 10000} ) { Token { Address Name Symbol Network } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Price { IsQuotedInUsd Ohlc { Open High Low Close } Average { SimpleMoving ExponentialMoving } } } } } `; ``` - **TOKEN_DETAILS**: This GraphQL query fetches token trading data on the Solana blockchain for 1-minute intervals (`Duration: {eq: 60}`), including OHLC prices in USD. It’s scoped to a specific token by address: `6ft9XJZX7wYEH1aywspW5TiXDcshGc2W2SqBHN9SLAEJ` for the sake of this explanation. You can pass the variables `quote` from url and set it here. ### fetchHistoricalData Function ```javascript export async function fetchHistoricalData(from) { const requiredBars = 360; // Hardcoding the value ``` - **fetchHistoricalData**: Retrieves at least 360 one-minute OHLC bars, starting from the `from` timestamp. ### API Request ```javascript try { const response = await axios.post( endpoint, { query: TOKEN_DETAILS }, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.authtoken}`, }, } ); console.log("API called"); ``` - **axios.post**: Sends a POST request to Bitquery's streaming endpoint with the GraphQL query and an authorization token. - **Authorization**: Token is stored securely in `configs.json`. You can generate one by following [these instructions](/docs/authorization/how-to-generate/). ### Data Processing ```javascript const trades = response.data.data.Trading.Tokens; let bars = trades.map((trade) => { const blockTime = new Date(trade.Block.Time).getTime(); return { time: blockTime, open: trade.Price.Ohlc.Open || 0, high: trade.Price.Ohlc.High || 0, low: trade.Price.Ohlc.Low || 0, close: trade.Price.Ohlc.Close || 0, volume: trade.Volume.Base || 0, }; }); ``` - **Preprocessing the Data**: Maps the raw response into `bars`, where each bar has: - **time**: Unix timestamp in milliseconds. - **open/high/low/close**: OHLC prices. - **volume**: Trading volume in base tokens. ### Sorting the Data ```javascript bars.sort((a, b) => a.time - b.time); ``` Sorts the bars chronologically, as the API returns them in descending order by default. You can skip this step by changing query to return it in **ascending** order. ### Bar continuity (historical) Aggregated OHLC from the API does not always have **open = previous close** (interval boundaries, revisions, or how venues report bars). TradingView still draws each candle from the OHLC you provide, so gaps between the prior close and the next open can look like disjoint candles. After sorting, call `connectBarContinuity` so each bar’s **open** is set to the **previous bar’s close**, and **high** / **low** are expanded to include that price. This only affects presentation; it does not change the close or volume. ```javascript connectBarContinuity(bars); ``` Create `barContinuity.js` in the same folder as `histOHLC.js`: ```javascript /** * Forces each bar's open to the previous close so candles meet visually. * Mutates the array in place. */ export function connectBarContinuity(bars) { if (!bars || bars.length < 2) return bars; for (let i = 1; i < bars.length; i++) { const prevClose = bars[i - 1].close; const bar = bars[i]; bar.open = prevClose; bar.high = Math.max(bar.high, prevClose); bar.low = Math.min(bar.low, prevClose); } return bars; } ``` Run this **after** sorting and **before** any optional placeholder padding below. ### Handling Missing Bars The new price stream will handle all intervals, and this step can be skipped. ```javascript if (bars.length < requiredBars) { const earliestTime = bars[0]?.time || from; const missingBarsCount = requiredBars - bars.length; for (let i = 1; i <= missingBarsCount; i++) { bars.unshift({ time: earliestTime - i * 60000, open: 0, high: 0, low: 0, close: 0, volume: 0, count: 0, }); } } ``` - **Missing Bar Padding**: If fewer than 360 bars are returned, the function prepends placeholder bars (zero OHLC) so the chart has enough points for TradingView’s range request. That is **not** the same as OHLC continuity between real bars; use `connectBarContinuity` on the real API bars for that. ### Return the Processed Data ```javascript return bars; } catch (err) { console.error("Error fetching historical data:", err); throw err; } } ``` - Returns the final processed bar array or throws an error if the API request fails. --- ## How to Build a Solana Copy Trading Bot - Tutorial URL: https://docs.bitquery.io/docs/usecases/copy-trading-bot/ Build How to Build a Solana Copy Trading Bot - Tutorial: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # How to Build a Solana Copy Trading Bot - Tutorial This project is a Solana copy trading bot that allows users to replicate trades executed by a specified account on the Solana blockchain. The bot fetches trading data using the Bitquery API. For real-time price monitoring, consider using our [Crypto Price API](/docs/trading/crypto-price-api/). However since this is a tutorial project we don't execute a trade but rather store the trade info in an excel document. This is to provide an understanding on how Bitquery APIs could be used to build a full product. > Note: This material is for educational and informational purposes only and is not intended as investment advice. The content reflects the author's personal research and understanding. While specific investments and strategies are mentioned, no endorsement or association with these entities is implied. Readers should conduct their own research and consult with qualified professionals before making any investment decisions. Bitquery is not liable for any losses or damages resulting from the application of this information. ## How do I backtest a copy-trading strategy using Bitquery? **Backtesting** means replaying history: pull past **DEX trades** (or transfers) for the leader wallet and optionally **token prices** over the same windows, then simulate entries/exits in your code. Use **`Solana.DEXTrades`** / **`DEXTradeByTokens`** with **`Block.Time`** ranges and pagination. This tutorial shows how to **fetch** live trades; extend it with archived time ranges and your own PnL rules—Bitquery supplies data, not a built-in backtester. ## Understanding the Code You can view the entire codebase [here](https://github.com/Kshitij0O7/copy-trading-bot/tree/main). The major logical part is in the `main.py` file, so lets try to breakdown the code written here. ### Imports This code snippet will cover all the imports needed for running the script. ```python from constant import token ``` If any error is encountered due to import statement then try running the `pip install ...` command. ### Get Trades Function This function provides the trade info for a particular address, `HH3BmVQoVsH2c5H3nonkw2ySGogyohBXGGgF7vM7MRdk` in this case that could be stored in a doc or copied by adding custom logic. This function hits the Bitquery API with [this query](https://ide.bitquery.io/Get-Trade-Activities_1) to retrieve the latest trades by this account. ```python def getTrades(): ``` #### Declaring URL, payload and headers ```python url = "https://streaming.bitquery.io/graphql" payload = json.dumps({ "query": """subscription { Solana { DEXTrades( where: { Trade: {Buy: {Account: {Address: {is: "HH3BmVQoVsH2c5H3nonkw2ySGogyohBXGGgF7vM7MRdk"}}}}, Transaction: {Result: {Success: true}} } ) { Trade { Buy { Amount Currency { Name Symbol MintAddress } Price } Dex { ProtocolName ProgramAddress ProtocolFamily } Sell { Currency { MintAddress Name Symbol } Price Amount } } Transaction { Signature } } } }""", "variables": "{}" }) headers = { 'Content-Type': 'application/json', 'X-API-KEY': 'BQYuTITWanwYGz0YLGdcWSADO74o5RTX', 'Authorization': token } ``` #### Response Handling ```python response = requests.post(url, headers=headers, data=payload) if response.status_code == 200: return response.json() else: print(f"Error: {response.status_code}") return None ``` ### Execute Trades Function Note that the function doesn't actually execute/replicate the trades retrieved but stores it in an excel document. However, if you wish to build an actual bot or contribute to the project then you can easily do that by adding your own logic to the `executeTrades(trades_data)` function. ```python def executeTrades(trades_data): ``` #### Error Handling and Variable Declaration This snippet handles the case where empty `trades_data` is returned. Also, we define variables such as `dex_trades` to call data in a more direct manner and `trade_records` to mould data into the format of our dataframe. ```python if not trades_data or "data" not in trades_data or "Solana" not in trades_data["data"]: print("No trade data found.") return dex_trades = trades_data["data"]["Solana"]["DEXTrades"] # Prepare the data for DataFrame trade_records = [] ``` #### Iterating the Dex Trades In this section we are iterating the `dex_trades` and updating the `trade_records` simultaneously. ```python for trade in dex_trades: buy_currency = trade['Trade']['Buy']['Currency'] sell_currency = trade['Trade']['Sell']['Currency'] dex_info = trade['Trade']['Dex'] transaction_info = trade['Transaction'] trade_records.append({ 'Buy Amount': trade['Trade']['Buy']['Amount'], 'Buy Currency Name': buy_currency['Name'], 'Buy Currency Symbol': buy_currency['Symbol'], 'Buy Mint Address': buy_currency['MintAddress'], 'Buy Price': trade['Trade']['Buy']['Price'], 'Sell Amount': trade['Trade']['Sell']['Amount'], 'Sell Currency Name': sell_currency['Name'], 'Sell Currency Symbol': sell_currency['Symbol'], 'Sell Mint Address': sell_currency['MintAddress'], 'Sell Price': trade['Trade']['Sell']['Price'], 'Dex Protocol Name': dex_info['ProtocolName'], 'Dex Program Address': dex_info['ProgramAddress'], 'Dex Protocol Family': dex_info['ProtocolFamily'], 'Transaction Signature': transaction_info['Signature'] }) ``` #### Creating a DataFrame and Saving Trade Info to an Excel File ```python df = pd.DataFrame(trade_records) excel_file = 'trades_data.xlsx' df.to_excel(excel_file, index=False) print(f"Data saved to {excel_file}") ``` ### Running the Script This part of code actually runs the script and calls the functions. ```python trades_data = getTrades() executeTrades(trades_data) ``` To run this script enter the following command: ```bash python3 main.py ``` ## Video Tutorial --- ## How to Filter Abnormal Prices URL: https://docs.bitquery.io/docs/usecases/how-to-filter-anomaly-prices/ Build How to Filter Abnormal Prices: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # How to Filter Abnormal Prices You might see abnormal prices when you fetch data from Bitquery APIs. There can be two possibilities as to why these abnormal prices associated with trades are appearing in your API response. - In the first case, the trade is correct (check this from any other explorer eg. Etherscan) but anomalous i.e. both tokens’ amounts in USDs are not near to equal (bots are generally responsible for this), we calculate the price using Amount in USD of both the tokens involved and that results in abnormal price. - In the second case, the Bitquery DB itself has incorrect trade data, then create a ticket [here](https://support.bitquery.io). In the first case, we are going to see 3 different methods to filter anomaly trades. Anomaly trades are the trades that result in abnormally high or low prices in USD. Bitquery provides raw trade data and does not omit any trades that are happening over the network. But this also results in some issues for the Bitquery data consumers if they are trying to build something around the Price of tokens, such as trying to get All time high price or building OHLC/K-line charts. For pre-filtered, clean price data, consider using our [Crypto Price API](/docs/trading/crypto-price-api/). 3 ways to omit these types of anomaly trades: ## 1. Using PriceAsymmetry Price Asymmetry represents the difference in TradeAmount in USD of main currency and side currency. We want to include the optimal trades which have approximately the same Amounts in USD on both sides. We generally use priceAsymmetry: \{lt: 0.1\} as a filter in our APIs as this will filter out trades with more than 10% difference in their trade amounts. Also, filter out low AmountinUSD trades from this, say `{Trade: {AmountInUSD: \{lt: "10"\}}}` Read more about Price Asymmetry [here](/docs/graphql/metrics/priceAsymmetry/). Here's an example [query on ethereum trades](https://ide.bitquery.io/Price-based-on-DEX-trades-in-USD). ```graphql subscription { EVM { DEXTradeByTokens( where: { Trade: { Currency: { Symbol: { is: "WETH" } } PriceAsymmetry: { le: 0.1 } } } ) { Block { Time } median(of: Trade_PriceInUSD) } } } ``` ## 2. Using Quantile Bitquery APIs have the quantile metric, that can be used to provide insights into gas consumption, transaction amounts, or any other measurable field. Quantiles are useful in understanding the distribution of numerical data by dividing it into intervals. For example, the median represents the middle point of the data. Half of the responses had amounts lower than the median, and half had amounts higher. The level: 0.75 represents the 75th percentile and it shows that 75% of the responses had values lower than this, while level: 0.25 represents the 25th percentile and it shows that 25% of the values were lower. Read more about quantile [here](/docs/graphql/metrics/quantile/). We can also remove anomaly trades using the quantile metric. Also add one more filter to remove low AmountinUSD trades from this, say `{Trade: {AmountInUSD: {lt: "10"}}}` **Strategy**: Remove extreme trades using quantile Find out `quantile(of: Transfer_AmountInUSD, level: 0.05)` & `quantile(of: Transfer_AmountInUSD, level: 0.95)` This is just an example that gives the 5th percentile and 95th percentile of Trade Price in USD, each token can have a different distribution of anomaly trades so try different levels. And then when you have figured out what is the optimum level, then remove the trades and get your desired data from the subset trades. **Usage Example**: Calculate All Time High Price using quantile Use `quantile(of: Trade_PriceInUSD, level: 0.85)` and `Side: {AmountInUSD: {gt: "10"}}`. Choose the appropriate `level` value, it might be the case that the last 5 percentile trades are too extreme i.e. anomalous in Trade PriceinUSDs so we are skipping that and just getting the 85th percentile Price in USD. ```graphql query AllTimeHighTokenPriceQuery( $tokenAddress: String! $startTime: DateTime! ) { EVM(dataset: archive, network: eth) { DEXTradeByTokens( where: { Trade: { Currency: { SmartContract: \{ is: $tokenAddress \} } Side: { AmountInUSD: \{ gt: "10" \} } } Block: { Time: \{ since: $startTime \} } } limit: \{ count: 1 \} ) { quantile(of: Trade_PriceInUSD, level: 0.85) Trade { PriceInUSD Amount AmountInUSD Currency { Name } Side { Amount AmountInUSD Currency { Name } } } Transaction { Hash } } } } ``` ## 3. Get all trades and filter on your end Get all the trades from Bitquery API and then filter trades using your own custom logic so that you can remove the anomaly trades with abnormal prices. One such example we have shown [here](/docs/usecases/solana-ohlc-calculator/) where custom logic is a very basic one, fetching quantile values of Trade USD Price with level: 0.05 and level: 0.95 and then only fetching trades between these 2 Trade Prices and thus removing extremes. ## Conclusion Whenever you see abnormal trades with extremely high trade Prices in the API response, first try to get the transaction hash of it and check with another explorer whether the trade amounts are correct or not. If they are correct then apply above mentioned methods to omit these trades. And after clarifying you found that Bitquery gave the wrong Trade data, then create a ticket [here](https://support.bitquery.io) --- ## How to Stream Moonshot Live Prices URL: https://docs.bitquery.io/docs/usecases/streaming-moonshot-prices/ Build How to Stream Moonshot Live Prices: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Tutorial: How to Stream Moonshot Live Prices In this tutorial, you will learn how to create a simple React application to stream live prices from the Moonshot protocol using WebSocket and Bitquery's [Moonshot APIs](/docs/blockchain/Solana/Moonshot-API/). You can find the complete repo [here](https://github.com/Divyn/streaming-moonshot-prices) The final output would look something like this ![Streaming Moonshot token prices](/img/ApplicationExamples/moonshot.png) ### Prerequisites - Basic knowledge of React and JavaScript. - Node.js installed on your machine. - [Bitquery OAuth token.](/docs/authorization/how-to-generate/) ### Step 1: Setting Up the Project First, create a new React project if you don’t have one already: ``` npx create-react-app moonshot-stream cd moonshot-stream ``` ### Step 2: Install Required Packages Install the necessary dependencies for WebSocket support: ```bash npm install reconnecting-websocket ``` ### Step 3: Create the `useWebSocket` Hook The `useWebSocket` hook will manage the WebSocket connection, handle reconnection attempts, and process the incoming data. #### **1. `useWebSocket.js` - Custom WebSocket Hook** This file contains a custom React hook named `useWebSocket`, which manages the WebSocket connection. - **Step 1: Initialize State Variables** ```javascript const [data, setData] = useState(null); const [error, setError] = useState(null); const [isConnected, setIsConnected] = useState(false); const [retryCount, setRetryCount] = useState(0); ``` - `data`: Stores the incoming data from the WebSocket. - `error`: Stores any error messages that may occur during connection. - `isConnected`: Tracks whether the WebSocket is currently connected. - `retryCount`: Counts the number of reconnection attempts made. - **Step 2: Define the `useEffect` Hook** ```javascript useEffect(() => { const connectWebSocket = () => { const ws = new ReconnectingWebSocket(url, ["graphql-ws"], options); ``` - `useEffect` is used to initiate the WebSocket connection when the component mounts or when the `retryCount` changes. - `connectWebSocket` function is defined inside the `useEffect` to handle the connection logic. - **Step 3: Handle WebSocket Events** - **onopen Event** ```javascript ws.onopen = () => { setIsConnected(true); setRetryCount(0); ws.send(JSON.stringify({ type: "connection_init" })); setTimeout(() => { ws.send( JSON.stringify({ type: "start", id: "1", payload: { query }, }) ); }, 1000); }; ``` - `onopen`: Triggered when the WebSocket connection is established. - Sends an initial `connection_init` message. - Sends the GraphQL query after a short delay. - **onmessage Event** ```javascript ws.onmessage = (event) => { const response = JSON.parse(event.data); if (response.type === "data") { setData(response.payload.data); } }; ``` - `onmessage`: Triggered when a message is received from the server. - Parses the JSON data and updates the `data` state with the payload. - **onclose Event** ```javascript ws.onclose = () => { setIsConnected(false); if (retryCount < maxRetries) { setRetryCount(retryCount + 1); setTimeout(connectWebSocket, 2000); } else { setError("Max retry attempts reached. Could not connect to Bitquery."); } }; ``` - `onclose`: Triggered when the WebSocket connection is closed. - Attempts to reconnect if the maximum retry count has not been reached. - **onerror Event** ```javascript ws.onerror = (event) => { console.error("WebSocket Error:", event); setError("WebSocket error occurred. See console for details."); }; ``` - `onerror`: Triggered when an error occurs with the WebSocket. - Logs the error to the console and updates the `error` state. - **Step 4: Close Function** ```javascript return () => { ws.close(); }; ``` - Ensures that the WebSocket connection is properly closed when the component unmounts. - **Step 5: Return Values** ```javascript return { data, error, isConnected }; ``` - The hook returns the current state values (`data`, `error`, `isConnected`) to be used in the component. --- ### Step 4: `App.js` - Main Component This file contains the main React component that uses the `useWebSocket` hook to display the data. - **Step 1: Define the GraphQL Query** ```javascript const query = ` subscription MyQuery { Solana { DEXTrades( where: { Trade: { Dex: { ProtocolFamily: { is: "Moonshot" } } } Transaction: { Result: { Success: true } } } ) { Instruction { Program { Method } } Trade { Dex { ProtocolFamily ProtocolName } Buy { Amount Account { Address } Currency { Name Symbol MintAddress Decimals Fungible Uri } } Sell { Amount Account { Address } Currency { Name Symbol MintAddress Decimals Fungible Uri } } } Transaction { Signature } } } } `; ``` - This query subscribes to real-time data from the Solana blockchain, filtering for DEX trades related to the "Moonshot" protocol. - **Step 2: Define the WebSocket URL and Options** ```javascript const url = "wss://streaming.bitquery.io/graphql?token=YOUR_TOKEN"; const options = { maxReconnectionDelay: 10000, minReconnectionDelay: 1000, reconnectionDelayGrowFactor: 1.3, connectionTimeout: 5000, maxRetries: Infinity, debug: true, }; ``` - `url`: The WebSocket endpoint, including your Bitquery Token. - `options`: Configuration for the WebSocket connection, such as reconnection delays and retry limits. - **Step 3: Use the `useWebSocket` Hook** ```javascript const { data, error, isConnected } = useWebSocket(url, options, query); ``` - The hook is called with the `url`, `options`, and `query`, and returns the current connection status, any errors, and the received data. - **Step 4: Render the Component** ```javascript return (

Bitquery WebSocket

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...

)}
); ``` - The component renders the connection status, any errors, and the data in a table format if available. - If the data is not yet available, it displays a "Loading data..." message. --- ## How to Track Ethereum Price in USD with Google Sheets URL: https://docs.bitquery.io/docs/usecases/real-time-historical-ethereum-price-excel-google-sheets/ Build How to Track Ethereum Price in USD with Google Sheets: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # How to Track Ethereum Price in USD with Google Sheets Below is a step-by-step tutorial on how to use Bitquery APIs to get token price information into a Google Sheets spreadsheet using Python. In this we code will authenticate with Google Sheets, create a new spreadsheet, fetch data from Bitquery, and then populate the spreadsheet with that data. Adjust the query and data fields according to your specific requirements. ### Prerequisites: - Google Developers account. - Bitquery API access token. - Python installed on your system. - Libraries: `gspread`, `oauth2client`, and `requests`. ### Step 1: Set up your Google Cloud project and service account 1. **Create a Google Cloud Project**: Go to https://console.cloud.google.com/ and create a new project. 2. **Enable the Google Sheets API**: In the API & Services section, enable the Google Sheets API and Google Drive API. 3. **Create a Service Account**: Create a new service account in the IAM & Admin section. 4. **Create and download a JSON key**: Generate a new JSON key for the service account and download it. Save this file as `google_sheets.json`. You will be able to download the JSON file on cloud console as shown Below ![Google Cloud Console API setup](/img/ApplicationExamples/google_cloud_console.png) ### Step 1: Install Required Libraries First, you need to install the necessary Python libraries if you haven't already: ```bash pip install gspread oauth2client requests ``` ### Step 2: Authenticate and Set Up Google Sheets Create a Python function to authenticate and set up Google Sheets: ```python from oauth2client.service_account import ServiceAccountCredentials def authenticate_gsheets(): # Define the scope of the application scope = [ "https://spreadsheets.google.com/feeds", 'https://www.googleapis.com/auth/spreadsheets', "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/drive" ] # Authenticate using the service account JSON key file creds = ServiceAccountCredentials.from_json_keyfile_name('google_sheets.json', scope) client = gspread.authorize(creds) # Create a new spreadsheet spreadsheet = client.create("test_bitquery_DEXTrades") worksheet = spreadsheet.sheet1 # Share the spreadsheet with your email spreadsheet.share('your-email@example.com', perm_type='user', role='writer') print(f"Spreadsheet URL: {spreadsheet.url}") return worksheet ``` ### Step 3: Fetch USD Price Data from Bitquery DEX Trades API Set up a function to fetch data from the Bitquery API using OAuth. In this query we fetch latest DEX Trades along with USD Price information. ```python def fetch_bitquery_data(): url = "https://streaming.bitquery.io/graphql" headers = { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN', 'Content-Type': 'application/json' } # GraphQL query query = """ { EVM(dataset: archive, network: eth) { DEXTrades(limit: {count: 10}, orderBy: {descending: Block_Time}) { Block { Number Time } Transaction { From To Hash } Trade { Buy { Amount Buyer Currency { Name Symbol SmartContract } Seller Price PriceInUSD } Sell { Amount Buyer Currency { Name SmartContract Symbol } Seller Price } Dex { ProtocolFamily ProtocolName SmartContract Pair { SmartContract } } } } } } """ response = requests.post(url, json={'query': query}, headers=headers) if response.status_code == 200: return json.loads(response.text) else: raise Exception(f"Query failed and return code is {response.status_code}. {response.text}") ``` ### Step 4: Write Data to Google Sheets Create a function to update the Google Sheet with the fetched data: ```python def update_sheet(worksheet, data): headers = [ "Block Time", "Transaction Hash", "Buy Price", "Buy Amount", "Buy Currency Symbol", "Sell Amount", "Sell Currency Symbol", "Dex Protocol Name" ] worksheet.update(['A1:H1'], [headers]) trades = data['data']['EVM']['DEXTrades'] for i, trade in enumerate(trades, start=2): values = [ trade['Block']['Time'], trade['Transaction']['Hash'], # Additional fields from your data ] worksheet.update(f'A{i}:H{i}', [values]) ``` ### Step 5: Run the Main Function Finally, set up your main function to orchestrate the flow: ```python def main(): worksheet = authenticate_gsheets() data = fetch_bitquery_data() update_sheet(worksheet, data) if __name__ == "__main__": main() ``` ### Final Output --- ## How to build a Crypto Price Change Signal Bot on Telegram URL: https://docs.bitquery.io/docs/usecases/price-change-signal-bot/ Build How to build a Crypto Price Change Signal Bot on Telegram: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application. # How to build a Crypto Price Change Signal Bot on Telegram This bot fetches real-time trading data from Bitquery and sends alerts on significant token price changes. For pre-aggregated price data with OHLC, consider using our [Crypto Price API](/docs/trading/crypto-price-api/). The bot is built using the `python-telegram-bot` library to handle interactions with Telegram users and `aiohttp` for making asynchronous API requests. Github Repository Link is available [here](https://github.com/bitquery/Price-Change-Signal-Telegram-Bot/tree/main) ## Tutorial Video ## Components 1. **Libraries and Dependencies** - `re`, `asyncio`, `json`, `logging`, `aiohttp`, `os`: For general utilities, asynchronous handling, JSON parsing, and logging. - Telegram Libraries (`telegram`, `ApplicationBuilder`, etc.): For Telegram bot interaction. - `datetime`: For time manipulation in API requests. 2. **Configuration and Constants** - `BOT_TOKEN`: The Telegram bot token from the BotFather. Get it from [here](https://telegram.me/BotFather) - `OAUTH_TOKEN`: The authorization token for the Bitquery API. Check out the steps on how to get it [here](/docs/authorization/how-to-generate/) - `logging.basicConfig`: Configures logging to track bot operations and potential errors. 3. **Helper Functions** - `split_text(text, max_length)` - **Purpose**: Splits long messages into smaller chunks to avoid Telegram's message length limit (4096 characters). - **Parameters**: - `text (str)`: Message to be split. - `max_length (int)`: Character limit. - **Returns**: List of text parts. - `send_long_message(update, context, long_message, max_message_length=4000)` - **Purpose**: Sends long messages in parts to avoid Telegram’s message length limit. - **Parameters**: - `update`, `context` (Telegram update and context). - `long_message (str)`: Message content. - `max_message_length` (default=4000). - **Operation**: Handles `RetryAfter` exceptions if Telegram’s flood control limit is reached, retrying after a delay. 4. **Core Functions** - `send_query_and_process(update, context)` - **Purpose**: Sends GraphQL query to Bitquery API, retrieves data, processes, and sends alerts. - **Operation**: - Creates a query with specified variables. - Sends a POST request to Bitquery. - Processes the response to calculate price changes over different timeframes (5 minutes and 1 hour). - Sorts and formats the data, then sends it to Telegram using `send_long_message`. - `calculate_percentage_change(start_price, end_price)` - **Purpose**: Calculates and formats the percentage change between two prices. - **Parameters**: - `start_price`, `end_price (float)`: Starting and ending prices. - **Returns**: Formatted string showing percentage change with symbols (📈 or 📉). - `format_message(data)` - **Purpose**: Formats the retrieved data for user-friendly display in Telegram. - **Parameters**: - `data (dict)`: JSON data from Bitquery API. - **Operation**: - Iterates over the trading data items. - Formats each data point with trade and volume information, using fallback values if data is missing. - Adds a "Trade Now" link for easy access to trading. 5. **Global Flag** - `is_task_running`: A global flag to prevent multiple instances of `send_query_and_process` from running concurrently, which could lead to excessive API calls or repeated messages. 6. **Command Handlers** - `start_regular_requests(update, context)` - **Purpose**: Starts a recurring task to fetch data every 30 minutes. - **Operation**: - Checks if a task is already running (using `is_task_running`). - Sets up a loop to call `send_query_and_process` every 30 minutes. - `start(update, context)` - **Purpose**: Command handler for `/start`. Triggers `start_regular_requests` to begin data polling. - **Usage**: Users send `/start` to the bot to start receiving regular updates. 7. **Main Execution Block** - **Purpose**: Initializes the Telegram bot using `ApplicationBuilder` and starts polling for incoming `/start` commands. ## How to Run 1. **Clone the Repository** ```bash git clone https://github.com/bitquery/Price-Change-Signal-Telegram-Bot.git ``` 2. **Set up virtual environment** ```bash python3 -m venv venv source venv/bin/activate # For Windows: venv\Scripts\activate ``` 3. **Install Dependencies** ```bash pip install -r requirements.txt ``` 4. **Run the bot** ```bash python bot.py ``` That's it for this tutorial. Happy coding, happy trading! --- ## Hyperliquid API Docs — Real-time Trades, Orders, Liquidations & Prices URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/ Query and stream Hyperliquid data with Bitquery: trades, orders, order book updates, OHLCV candles, liquidations, funding, positions, TWAPs, mark prices and signed actions over GraphQL and WebSocket. # Hyperliquid API These are the Bitquery **Hyperliquid API docs**: how to query and stream trades, orders, the order book, candles, liquidations, funding and positions for every trader on the exchange. Bitquery indexes **Hyperliquid core** (the L1 order-book exchange) and exposes it through the `Hyperliquid` cube on the [streaming API](https://streaming.bitquery.io/graphql). Every dataset is available both as a **GraphQL query** and as a **WebSocket subscription** (real-time stream) — change `query` to `subscription` and drop `limit`/`orderBy`. ## Video Tutorials :::info How far back the API goes The `Hyperliquid` cubes serve a **rolling ~30-day window**. That covers live streaming and recent history, which is what most bots, dashboards and monitoring need. For deeper history — backtests, multi-year research, full archive exports — use the [Blockchain Data Lake](/docs/data-lake/), which serves the complete archive over S3 using the same protobuf schema as the Kafka streams. Contact [sales@bitquery.io](mailto:sales@bitquery.io) for archive access and custom export ranges. ::: :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Available datasets ```graphql query { Hyperliquid { BookUpdates # order book deltas (new / change / remove) Candles # OHLCV candles per market and interval CurrentPositions # current open perp positions per trader MarkPrices # latest mark price per market Orders # order lifecycle: placed, filled, canceled, rejected PerpFundings # per-trader funding payments PerpLiquidations # perp liquidations PriceUpdates # oracle / reference price updates SignedActions # raw signed L1 actions (order, cancel, modify, ...) TraderLeverageUpdates # leverage / margin-mode changes Trades # fills with direction, fees, leverage, PnL Twaps # TWAP order lifecycle } } ``` | Page | Cubes covered | | --- | --- | | [Trades & Candles](/docs/perpetuals/hyperliquid/hyperliquid-trades-api) | `Trades`, `Candles` | | [Orders, Order Book & TWAPs](/docs/perpetuals/hyperliquid/hyperliquid-orders-api) | `Orders`, `BookUpdates`, `Twaps` | | [Mark Prices & Price Updates](/docs/perpetuals/hyperliquid/hyperliquid-prices-api) | `MarkPrices`, `PriceUpdates` | | [Liquidations, Funding, Positions & Leverage](/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api) | `PerpLiquidations`, `PerpFundings`, `CurrentPositions`, `TraderLeverageUpdates` | | [Signed Actions](/docs/perpetuals/hyperliquid/hyperliquid-signed-actions-api) | `SignedActions` | | Guide | What it covers | | --- | --- | | [Track Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet) | Market-maker tracking, cancel-to-fill ratios, wallet-level order lifecycles using L3 data | | [Funding Payments by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-funding-payments-api) | Every funding payment a wallet paid or received, per-market totals, date ranges | | [Liquidation History by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-liquidation-history-by-wallet) | Every liquidation of one address, backstop liquidations, live alerts | | [Vaults API](/docs/perpetuals/hyperliquid/hyperliquid-vaults-api) | Vaults ranked by volume and PnL, vault fills, HLP deposits and withdrawals, vault liquidations | | [TWAP Orders & Child Fills](/docs/perpetuals/hyperliquid/hyperliquid-twap-fills) | Join a TWAP order to every child fill it produced | | [HIP-3 Stocks API](/docs/perpetuals/hyperliquid/hip3-stocks-api) | S&P 500, US stock and commodity perps deployed through HIP-3 | | [Track ZEC on Hyperliquid](/docs/perpetuals/hyperliquid/track-zec-on-hyperliquid) | Positions, liquidations, funding and whale trades for one market | | [Bitquery vs Hyperliquid's free data](/docs/perpetuals/hyperliquid/vs-hyperliquid-api) | What the native Info API and `s3://hyperliquid-archive` do and don't cover | ## Why Bitquery instead of the native Hyperliquid WebSocket API? The [native Hyperliquid API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions) is built for trading your own account. Bitquery is built for seeing the whole market: | Capability | Hyperliquid native WS | Bitquery | | --- | --- | --- | | Scope of user data (orders, fills, funding, TWAPs, positions) | Any single address you already know, one subscription each — no market-wide stream, no trader discovery | Every trader on the exchange in one stream, or filter to any list of addresses | | Order book | `l2Book`, aggregated (L2), 5–20 levels max | **L3 (market-by-order)** deltas, unlimited depth, order id + trader address on every order | | Liquidations | Per-address only, via `userEvents` — no exchange-wide feed | All liquidations exchange-wide, with liquidated user, method, mark price, leverage | | Open positions | Per-address snapshot (`clearinghouseState`) — cannot enumerate or rank the market | Whole market queryable (`CurrentPositions`): every open position, sortable and filterable | | Historical data | Live + snapshot only; separate REST with pagination limits. The public `s3://hyperliquid-archive` bucket holds **L2 book snapshots and asset contexts only** — no trades, candles or spot — updated roughly monthly with no completeness guarantee | Same GraphQL query for history and live stream over a rolling ~30-day window; full archive via the [Data Lake](/docs/data-lake/) | | Filtering | Per-coin or per-user only | Any field: market, trader, side, size, leverage, status | | Fill context | Rich (PnL, direction) only on per-user feeds; the public `trades` feed is bare price/size/side | Direction, fees, leverage, size-before, realized PnL on every fill, market-wide | | Raw L1 actions | Not exposed | `SignedActions`: action type, signer vs user (agent wallets), bundle, broadcaster | | Delivery | WebSocket only; reconnect/gap handling yours; some feeds base64+DEFLATE encoded | GraphQL WS + Kafka (protobuf, offsets, consumer groups, no gaps) | | Subscription model | One subscription per coin / per user, per-connection limits | One stream can carry everything unfiltered, or a list of values on any filter field (many markets or wallets in one stream); runs 1,000+ concurrent streams at scale | | Latency at market scale | Fast for a single coin/user feed, but covering the whole market means hundreds of subscriptions, client-side merging and rate limits | Lowest latency for the entire market in one pipeline — Kafka delivers every event exchange-wide, keyed by block, with no fan-out to assemble | ## Markets: perp, spot and HIP-3 Every cube carries a `Market` object that identifies the instrument: | Field | Meaning | Example values | | --- | --- | --- | | `Symbol` | Human-readable market symbol | `BTC`, `HYPE`, `AAPL` | | `CoinRaw` | Raw Hyperliquid coin id; HIP-3 markets are prefixed with their deployer namespace | `BTC`, `xyz:SMSN`, `mkts:AAPL` | | `Kind` | Market class | `perp`, `spot`, `hip3` | | `IsPerp` | `true` for perpetual markets (including HIP-3 perps) | `true` / `false` | | `Protocol` | HIP-3 deployer namespace, empty for native markets | `xyz`, `mkts` | | `MaxLeverage` | Maximum leverage allowed on the market | `40` | **HIP-3** markets are builder-deployed perps — tokenized stocks (`mkts:AAPL`), indices (`xyz:KR200`, `TOTAL2`) and other synthetic assets trade alongside native Hyperliquid perps and appear in the same cubes with `Kind: hip3`. See the [HIP-3 Stocks API](/docs/perpetuals/hyperliquid/hip3-stocks-api) for queries. `ChainId` is `hyperliquid-core` on all event cubes. ## Common fields - `Block { Number Time }` — Hyperliquid L1 block height and timestamp; filter with `Block: {Time: {since_relative: {minutes_ago: 5}}}` or absolute `since`/`till`. - `Trader { Address Vault Signer Broadcaster SignedAt }` — the account behind an event. `Vault` is set when the action is performed on behalf of a vault; `Signer` is the signing key (may be an agent/API wallet distinct from `Address`). - Numeric amounts (price, size, PnL, fees) are returned as **strings** at native precision; candle OHLCV values are floats. ## WebSocket streams Any query becomes a live stream over `wss://streaming.bitquery.io/graphql` (transport `graphql-transport-ws`): ```graphql subscription { Hyperliquid { Trades { Block { Time } Trade { Market { Symbol } Execution { Price Size Side Direction } } } } } ``` See [WebSocket subscriptions](/docs/subscriptions/websockets/) for connection details. ## Kafka streams (protobuf) For the lowest latency, the same data is available as Kafka streams: | Topic | Protobuf schema | | --- | --- | | `hyperliquid.candles.proto` | [hyperliquid/candles.proto](https://github.com/bitquery/streaming_protobuf/blob/main/hyperliquid/candles.proto) | | `hyperliquidcore.messages.proto` | [hyperliquid/hypercore.proto](https://github.com/bitquery/streaming_protobuf/blob/main/hyperliquid/hypercore.proto) | See [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts) for access and consumer setup. ## Historical data (Parquet cloud datasets) For backtesting and research over the last 30 days — trades with realized PnL, liquidations, L4 order book updates, the full order lifecycle, oracle prices and hourly funding — Bitquery delivers the same data as [Parquet cloud datasets](/docs/cloud/hyperliquid) for S3, Snowflake, BigQuery, Athena, or DuckDB. The Parquet files and the live streams share field definitions, so backtest and production code decode identically. --- ## Hyperliquid Funding Payments API URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-funding-payments-api/ Export every funding payment a Hyperliquid wallet paid or received: signed amount, funding rate, position size and market, per hourly funding tick, over GraphQL and WebSocket. # Hyperliquid Funding Payments API **In short:** Hyperliquid pays funding every hour. Each payment is `position size × oracle price × funding rate`, and the hourly rate is one eighth of the computed 8-hour rate. With Bitquery, the `PerpFundings` cube returns every payment a wallet paid or received, with the signed amount, the rate applied and the position size. Every hour, Hyperliquid settles funding between longs and shorts. Each settlement is a **transfer against a specific wallet**, not a market statistic — and it is one of the event types that goes missing most often when people export their history for accounting. This page covers the `PerpFundings` cube: the complete funding history of any address. :::info Funding payments vs funding rates A **funding rate** is a market-level number traders use to size a position. A **funding payment** is what actually left or entered one wallet's account. This page is about payments. For the rate applied at each tick, read `Funding.Rate` on the same records. ::: :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Funding payments of one wallet `Amount` is signed: **negative means the trader paid funding**, positive means they received it. `Rate` is the funding rate applied at that tick and `Size` the position size it applied to. Run it in the IDE: [Hyperliquid Wallet Funding Payments ➤](https://ide.bitquery.io/hyperliquid-wallet-funding-payments) ```graphql query { Hyperliquid { PerpFundings( limit: {count: 100} orderBy: {descending: Block_Time} where: {Funding: {Trader: {Address: {is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2"}}}, Block: {Time: {since_relative: {days_ago: 3}}}} ) { Block { Time } Funding { Market { Symbol Kind } Amount Rate Size Trader { Address } } } } } ``` Sample response: ```json { "Block": { "Time": "2026-09-04T09:00:00.048813Z" }, "Funding": { "Market": { "Symbol": "XYZ100" }, "Amount": "-15.217007", "Rate": "0.0000096689", "Size": "53.1425", "Trader": { "Address": "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2" } } } ``` ## Total funding paid and received per market Rather than paging every tick, aggregate them. This gives one row per market with the net funding and the number of ticks. Run it in the IDE: [Hyperliquid Funding Totals By Market ➤](https://ide.bitquery.io/hyperliquid-funding-totals-by-market) ```graphql query { Hyperliquid { PerpFundings( where: {Funding: {Trader: {Address: {is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2"}}}, Block: {Time: {since_relative: {days_ago: 3}}}} ) { Funding { Market { Symbol } } ticks: count net: sum(of: Funding_Amount) } } } ``` `net` is the signed total: a negative number is net funding **paid** over the whole period. ## Funding paid only Split the two directions by filtering on the sign of `Amount`. Use `lt: "0"` for funding paid, `gt: "0"` for funding received. Run it in the IDE: [Hyperliquid Funding Paid Only ➤](https://ide.bitquery.io/hyperliquid-funding-paid-only) ```graphql query { Hyperliquid { PerpFundings( where: { Funding: { Trader: {Address: {is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2"}} Amount: {lt: "0"} } Block: {Time: {since_relative: {days_ago: 3}}} } ) { Funding { Market { Symbol } } ticks: count paid: sum(of: Funding_Amount) } } } ``` ## Funding over a date range Add a `Block: {Time: ...}` filter to bound the export to a tax year, a quarter or any window. Run it in the IDE: [Hyperliquid Funding By Date Range ➤](https://ide.bitquery.io/hyperliquid-funding-by-date-range) ```graphql query { Hyperliquid { PerpFundings( limit: {count: 100} orderBy: {ascending: Block_Time} where: { Funding: {Trader: {Address: {is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2"}}} Block: {Time: {since: "2026-09-01T00:00:00Z", till: "2026-09-04T00:00:00Z"}} } ) { Block { Time } Funding { Market { Symbol } Amount Rate Size } } } } ``` ## Real-time funding stream Change `query` to `subscription` and drop `limit` and `orderBy` to receive each funding settlement as it happens. Run it in the IDE: [Hyperliquid Funding Payments Stream ➤](https://ide.bitquery.io/hyperliquid-funding-payments-stream) ```graphql subscription { Hyperliquid { PerpFundings( where: {Funding: {Trader: {Address: {is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2"}}}} ) { Block { Time } Funding { Market { Symbol } Amount Rate Size Trader { Address } } } } } ``` Drop the `where` clause entirely to stream funding for every trader on the exchange. ## Notes - Funding settles hourly. A wallet holding several perp positions produces one record per market per tick, so a busy account accumulates thousands of rows per month. - `Market.Kind` distinguishes native perps from HIP-3 builder markets — see the [HIP-3 stocks API](/docs/perpetuals/hyperliquid/hip3-stocks-api). - To pair funding with the fills that opened and closed the position, use the [Trades API](/docs/perpetuals/hyperliquid/hyperliquid-trades-api); for the currently open positions and their accumulated funding, use `CurrentPositions` on the [perpetuals page](/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api#current-positions). --- ## Hyperliquid HIP-3 Stocks API — S&P 500, US Equity & Commodity Perps URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hip3-stocks-api/ Query and stream Hyperliquid HIP-3 markets with Bitquery: S&P 500, NVDA, TSLA and other US stock perpetuals, plus gold, oil and FX. Trades, mark prices, oracle inputs, L4 order book, candles, funding and liquidations over GraphQL and WebSocket. # Hyperliquid HIP-3 Stocks API HIP-3 lets anyone deploy a perpetual market on Hyperliquid. In practice that has produced a full off-chain asset complex trading on-chain: **S&P 500, Nasdaq-100, individual US equities, country ETFs, gold, oil, natural gas and FX** — all as perps settled in USDC on HyperCore. Bitquery indexes every HIP-3 market alongside the native perps, in the same `Hyperliquid` cube. This page covers how to address those markets, and the queries that matter for pricing, market making and basis trading against the underlying. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## How HIP-3 markets are addressed Every market carries a `Market` object. Three fields identify a HIP-3 market: | Field | Native perp | HIP-3 market | | --- | --- | --- | | `Kind` | `perp` | `hip3` | | `Protocol` | `hypercore` | the deployer, e.g. `xyz`, `io`, `para`, `mkts`, `hyna` | | `CoinRaw` | `BTC` | `xyz:SP500` — `deployer:symbol` | | `Symbol` | `BTC` | `SP500` — the bare symbol | Two consequences worth knowing before you write a filter: - **`Symbol` is not unique across deployers.** `SNDK` trades under both `xyz:` and `io:`, `AVGO` under both `xyz:` and `para:`, `UNITREE` under `xyz:` and `para:`. Filter on `CoinRaw` when you mean one specific market, and on `Symbol` only when you deliberately want every deployer's version of the same underlying. - **Filter the whole asset class with `Kind`,** not with a `Protocol` allowlist. New deployers appear without notice; `Kind: {is: "hip3"}` keeps working. ## List every HIP-3 market Ranks all HIP-3 markets by traded size over the last 24 hours, with distinct trader counts. This is the discovery query — run it first to see what exists right now. Run it in the IDE: [Hyperliquid HIP-3 Markets ➤](https://ide.bitquery.io/hyperliquid-hip3-markets) ```graphql query { Hyperliquid { Trades( limit: {count: 300} orderBy: {descendingByField: "volume"} where: { Trade: {Market: {Kind: {is: "hip3"}}} Block: {Time: {since_relative: {hours_ago: 24}}} } ) { Trade { Market { Symbol CoinRaw Protocol } } volume: sum(of: Trade_Execution_Size) trades: count traders: count(distinct: Trade_Trader_Address) } } } ``` Swap `Kind` for `Protocol: {is: "xyz"}` to scope to a single deployer. ## Trades on a stock market Every fill on `xyz:SP500`, both sides of each match. `IsAggressor` marks the taker, and a **negative `Fee` is a maker rebate**. Run it in the IDE: [Hyperliquid SP500 Perp Trades ➤](https://ide.bitquery.io/hyperliquid-sp500-trades) ```graphql query { Hyperliquid { Trades( limit: {count: 50} orderBy: {descending: Block_Time} where: {Trade: {Market: {CoinRaw: {is: "xyz:SP500"}}}} ) { Block { Time } Trade { Market { Symbol CoinRaw Kind Protocol } Execution { Price Size Side Direction IsAggressor Oid Tid } Fees { Fee FeeToken BuilderFee } Position { Leverage IsCross SizeBefore RealizedPnl } Trader { Address } } } } } ``` Change `CoinRaw` to any market from the discovery query — `xyz:NVDA`, `xyz:TSLA`, `xyz:GOLD`, `mkts:USTECH`, `io:GPRO`. ## Mark prices across all HIP-3 markets `MarkPrices` is a **latest-state cube**: one row per market holding its current mark, not a time series. Use it for a pricing snapshot; use `Candles` or `Trades` for history. Run it in the IDE: [Hyperliquid HIP-3 Mark Prices ➤](https://ide.bitquery.io/hyperliquid-hip3-mark-prices) ```graphql query { Hyperliquid { MarkPrices( limit: {count: 200} orderBy: {descending: LastTime} where: {Market: {Kind: {is: "hip3"}}} ) { LastTime LastBlock Mark Market { Symbol CoinRaw Protocol } } } } ``` Note the field names differ from the other cubes: `LastTime` / `LastBlock` rather than `Block { Time }`, and the price is `Mark`. ## Basis: on-chain mark vs off-chain reference This is the query behind the HIP-3 arbitrage trade. `PriceUpdates` carries the oracle feed the market is priced against, and `Kind` separates the components: | `Kind` | Meaning | | --- | --- | | `spotInput` | the off-chain reference price for the underlying | | `markInput` | the input to the on-chain mark | | `oracle` | the published oracle price | | `extPerpInput` | external perp reference, where used | Because `spotInput` is indexed alongside the mark, **the basis is computable from Bitquery alone** — you do not need to bring your own equities feed just to measure the spread. Run it in the IDE: [Hyperliquid HIP-3 Basis Spread ➤](https://ide.bitquery.io/hyperliquid-hip3-basis-spread) ```graphql query { Hyperliquid { PriceUpdates( limit: {count: 100} orderBy: {descending: Block_Time} where: {PriceUpdate: {Market: {CoinRaw: {is: "xyz:NVDA"}}}} ) { Block { Time } PriceUpdate { Market { Symbol CoinRaw } Kind Price DailyPx UpdateClass UpdateTime } } } } ``` Pair the `spotInput` series with `Trades` on the same market to see where fills landed relative to the reference, which is what decides whether a basis trade was actually capturable rather than just visible. ## OHLCV candles for a stock perp `Candles` are pre-aggregated. `Interval.Time.Duration` is in **seconds** — `60` for 1m, `300` for 5m, `3600` for 1h. Run it in the IDE: [Hyperliquid Stock Perp Candles ➤](https://ide.bitquery.io/hyperliquid-stock-perp-candles) ```graphql query { Hyperliquid { Candles( limit: {count: 60} orderBy: {descending: Interval_Time_Start} where: { Market: {CoinRaw: {is: "xyz:NVDA"}} Interval: {Time: {Duration: {eq: 60}}} } ) { Interval { Time { Start Duration } } Market { Symbol CoinRaw } Ohlc { Open High Low Close Volume } } } } ``` If you need an interval the `Candles` cube does not carry, aggregate `Trades` directly: Run it in the IDE: [Hyperliquid HIP-3 OHLC From Trades ➤](https://ide.bitquery.io/hyperliquid-hip3-ohlc-from-trades) ```graphql query { Hyperliquid { Trades( limit: {count: 100} orderBy: {descendingByField: "minute_Time"} where: { Trade: {Market: {CoinRaw: {is: "xyz:SP500"}}} Block: {Time: {since_relative: {hours_ago: 6}}} } ) { minute: Block { Time(interval: {in: minutes, count: 5}) } open: Trade { Execution { Price(minimum: Block_Time) } } high: Trade { Execution { Price(maximum: Trade_Execution_Price) } } low: Trade { Execution { Price(minimum: Trade_Execution_Price) } } close: Trade { Execution { Price(maximum: Block_Time) } } volume: sum(of: Trade_Execution_Size) trades: count } } } ``` ## L4 order book for a stock perp `BookUpdates` are **per-order** deltas, not aggregated price levels. Each row names the order id and the wallet behind it, so you can reconstruct the book at any instant and measure queue position, order lifetime and maker behaviour. Run it in the IDE: [Hyperliquid Stock Book Updates ➤](https://ide.bitquery.io/hyperliquid-stock-book-updates) ```graphql subscription { Hyperliquid { BookUpdates( where: {BookUpdate: {Market: {CoinRaw: {is: "xyz:NVDA"}}}} ) { Block { Time } BookUpdate { Market { Symbol CoinRaw } Kind Side Px Size SizeBefore Oid Trader { Address } } } } } ``` `Kind` is `new`, `update` or `remove`. **`Size` is empty on a `remove`** — the order is gone, so there is no resting size to report; use `Px`, `Side` and `Oid` to find the level it left. Change `subscription` to `query` and add `limit` / `orderBy` for history. ## Funding on HIP-3 markets Funding is per trader, per market, and settles hourly. `Rate` is the funding rate applied and `Amount` the USDC paid or received. Run it in the IDE: [Hyperliquid HIP-3 Funding ➤](https://ide.bitquery.io/hyperliquid-hip3-funding) ```graphql query { Hyperliquid { PerpFundings( limit: {count: 50} orderBy: {descending: Block_Time} where: {Funding: {Market: {Kind: {is: "hip3"}}}} ) { Block { Time } Funding { Market { Symbol CoinRaw Protocol } Trader { Address } Rate Amount Size } } } } ``` Equity perps have no natural funding anchor overnight or at weekends, when the underlying market is closed. Funding is where that dislocation shows up, so this series is worth watching if you carry HIP-3 positions across a session boundary. ## Liquidations on HIP-3 markets Run it in the IDE: [Hyperliquid HIP-3 Liquidations ➤](https://ide.bitquery.io/hyperliquid-hip3-liquidations) ```graphql query { Hyperliquid { PerpLiquidations( limit: {count: 50} orderBy: {descending: Block_Time} where: {Liquidation: {Market: {Kind: {is: "hip3"}}}} ) { Block { Time } Liquidation { Market { Symbol CoinRaw Protocol } LiquidatedUser Liquidator MarkPx Method Execution { Price Size Side } Position { Leverage SizeBefore RealizedPnl } } } } } ``` `Method` distinguishes a `market` liquidation from a `backstop` one. ## Top traders on a stock market Ranks wallets by traded size on one market, with realized PnL. Useful for copy-trading, market-maker identification and flow analysis. Run it in the IDE: [Hyperliquid HIP-3 Top Traders ➤](https://ide.bitquery.io/hyperliquid-hip3-top-traders) ```graphql query { Hyperliquid { Trades( limit: {count: 50} orderBy: {descendingByField: "volume"} where: { Trade: {Market: {CoinRaw: {is: "xyz:SP500"}}} Block: {Time: {since_relative: {hours_ago: 24}}} } ) { Trade { Trader { Address } } volume: sum(of: Trade_Execution_Size) trades: count pnl: sum(of: Trade_Position_RealizedPnl) } } } ``` ## Field notes - **Prices and sizes are decimal strings**, not floats, in every cube except `Candles` (whose `Ohlc` fields are floats). Parse with `Decimal` where precision matters. - **A match produces two `Trades` rows**, one per counterparty. Key a trade on `(Block.Number, Market.CoinRaw, Execution.Tid)` — not on `Hash`, which spans many matches and is all zeroes on both sides of a TWAP fill. - **`Position.SizeBefore` is a signed position size** before the fill, negative for short. It is not money; realized PnL is `Position.RealizedPnl`. - **`Direction`** takes values like `Open Long`, `Close Short` and `Short > Long` — a flip through flat is one fill, not two. - **Retention.** These cubes are served from realtime with roughly a 30-day window (shorter for signed actions). For longer ranges, see [Data Coverage & Retention](/docs/graphql/data-coverage-retention/) or contact [sales@bitquery.io](mailto:sales@bitquery.io) for a historical export. ## Related - [Hyperliquid API overview](/docs/perpetuals/hyperliquid) - [Trades & Candles](/docs/perpetuals/hyperliquid/hyperliquid-trades-api) - [Orders, Order Book & TWAPs](/docs/perpetuals/hyperliquid/hyperliquid-orders-api) - [Mark Prices & Price Updates](/docs/perpetuals/hyperliquid/hyperliquid-prices-api) - [Liquidations, Funding, Positions & Leverage](/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api) --- ## Hyperliquid Historical Data — Bitquery vs Hyperliquid's Free API and S3 Archive URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/vs-hyperliquid-api/ An honest comparison of Hyperliquid's native Info API, its free s3://hyperliquid-archive bucket, and Bitquery: order book granularity (L2 vs L3), what the archive actually contains, rate limits, history depth and cross-chain coverage. # Hyperliquid Historical Data: Bitquery vs Hyperliquid's free data Where to get Hyperliquid historical data depends on what you need: trades, candles, the order book or full archive history. Hyperliquid publishes its own API and its own historical archive, both free. Any honest evaluation should start there, so this page does. ## When you should just use Hyperliquid directly Use the [native Info API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) if: - You are **trading your own account** and need your own orders, fills and positions. It is the canonical source, closest to the matching engine, with the lowest possible latency. - You need the **current** aggregated book for one or two coins, and 5–20 levels is enough. - You are building something small, single-venue, and free matters more than completeness. It is a good API. For those jobs nothing else will be better, and you should not pay for a substitute. Bitquery is for the jobs it is not built for: **seeing the whole market, at order granularity, over time, alongside other chains.** ## What Hyperliquid's free archive actually contains This is the part most evaluations get wrong. The public `s3://hyperliquid-archive` bucket is narrower than people assume: | | Included | Not included | | --- | --- | --- | | `market_data/` | **L2 book snapshots** | — | | `asset_ctxs/` | Asset contexts | — | | Everything else | — | **Trades, candles and spot data are explicitly not provided via S3** | Hyperliquid's own documentation also states that data is uploaded roughly **once a month**, with **no guarantee of timely updates**, and that **data may be missing**. So if you are backtesting on fills, building candles from trades, or studying spot markets, the free archive does not cover it — and that is by design, not an oversight. ## The core difference: L2 vs L3 Hyperliquid's public market-wide book data — both the `l2Book` websocket channel and the S3 snapshots — is **L2**: size aggregated per price level. Order-level detail (`orderUpdates`, `userFills`) exists in the native API, but only for **your own account**. Bitquery serves the book market-wide at **L3 (market-by-order)**. | | Hyperliquid native (market-wide) | Bitquery | | --- | --- | --- | | Book grain | L2 — total size per price level | **L3 — one event per individual order** | | Order identity | — | `Oid`, joins `Orders` ↔ `BookUpdates` ↔ `Trades` | | Who placed the order | — | `Trader { Address }` | | Depth | 5–20 levels | Unlimited | | Order-level data for other traders | Not served | Every order, every trader | Because Hyperliquid settles on a transparent L1, the wallet behind each order is public. That makes this **L3 with named attribution** — something that does not exist on centralised venues, where exchanges anonymise L3 feeds. It is what makes market-maker tracking, cancel-to-fill analysis and wallet-level order flow possible at all. See [Track Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet). :::note This data is on-chain Hyperliquid is a transparent L1, so this information is *derivable* by anyone who indexes the chain — Bitquery's contribution is the indexing, normalisation and delivery, not exclusive access. The claim here is specifically that **Hyperliquid's own API does not serve it market-wide**, which is verifiable in their docs. ::: ## Full comparison | Capability | HL Info API | HL S3 archive | Bitquery | | --- | --- | --- | --- | | Cost | Free | Free | Paid | | Book granularity | L2 aggregated (L3 for own account only) | L2 snapshots | **L3 market-by-order, market-wide** | | Trades / fills market-wide | Bare price/size/side | **Not provided** | Full: direction, fees, leverage, size-before, realized PnL | | Candles | Via REST, pagination-limited | **Not provided** | `Candles` cube, any interval | | Spot markets | Yes | **Not provided** | Yes | | Liquidations market-wide | Per-address only | Not provided | Exchange-wide, with method, mark price, leverage | | Funding per trader | Per-address only | Not provided | `PerpFundings`, market-wide | | Open positions | Per-address snapshot | Not provided | `CurrentPositions`, whole market, sortable | | Raw L1 actions | Not exposed | Not provided | `SignedActions` | | Update cadence | Real time | ~Monthly, no guarantee | Real time | | Completeness guarantee | — | **Stated: data may be missing** | SLA available | | History depth | Snapshot + limited REST | Since inception (L2 only) | Rolling ~30 days via API; **full archive via [Data Lake](/docs/data-lake/)** | | Delivery | WebSocket / REST | S3 files | GraphQL, WebSocket, Kafka (protobuf) | | Rate limits | Yes, per-connection and per-endpoint | S3 egress | Plan-based, no per-coin fan-out | | Other chains | Hyperliquid only | Hyperliquid only | **Solana, EVM, Base, BNB, Tron, Bitcoin — same schema, same key** | ## Same question, both ways **"Give me 1-hour OHLCV for BTC-PERP."** Natively, candles are not in the archive, so you either page through the REST `candleSnapshot` endpoint within its limits, or collect trades live and aggregate them yourself, then persist them. With Bitquery it is one query: ```graphql query { Hyperliquid { Candles( limit: {count: 100} orderBy: {descending: Interval_Time_Start} where: { Market: {Symbol: {is: "BTC"}} Interval: {Time: {Duration: {eq: 3600}}} } ) { Interval { Time { Start Duration } } Market { Symbol Kind } Ohlc { Open High Low Close Volume } } } } ``` The same query becomes a live stream by changing `query` to `subscription`. ## Where Bitquery is genuinely weaker Worth saying plainly: - **Latency to the matching engine.** For your own order placement and fills, the native API is closer to the source. Bitquery indexes blocks; there is a step in between. - **Cost.** Free is free. If your use case fits inside the native API, use it. - **API history depth.** The GraphQL cubes serve a rolling ~30-day window. Deeper history is a [Data Lake](/docs/data-lake/) or enterprise export, not a plan upgrade. ## Where Bitquery is decisive - **L3 with wallet attribution, market-wide** — not available from the native API at all. - **Trades, candles and spot history** — explicitly absent from the free archive. - **One stream for the entire market** rather than per-coin, per-user subscriptions you merge client-side. - **Cross-chain.** A Hyperliquid-only provider can tell you a wallet was liquidated. Only a multi-chain provider can tell you where the money went afterwards. ## Next steps - [Hyperliquid API overview](/docs/perpetuals/hyperliquid) — every cube - [L3 Order Book API](/docs/perpetuals/hyperliquid/hyperliquid-orders-api) - [Track Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet) - [Blockchain Data Lake](/docs/data-lake/) — full archive access - [Data Coverage & Retention](/docs/graphql/data-coverage-retention) — exact windows per cube --- ## Hyperliquid Liquidations — Liquidation History by Wallet URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-liquidation-history-by-wallet/ Pull every liquidation for a single Hyperliquid address: market, method, mark price, execution, the position size before it was closed, and fees, over GraphQL and WebSocket. # Hyperliquid Liquidations: History by Wallet Look up Hyperliquid liquidations for any address: every liquidated position, its market, method, mark price and size, as a query or a live stream. The [perpetuals page](/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api#liquidations) covers the market-wide liquidation feed. This page is the other direction: **one address, its complete liquidation history**. That is the query you need to answer "was this position liquidated or did I close it", to reconstruct a blown-up account, or to fill the gap left when an export shows a position disappearing with no closing trade. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Every liquidation of one address `LiquidatedUser` is the address whose position was closed. `Method` is `market` for an open-market liquidation and `backstop` when the backstop vault takes over. `Position.SizeBefore` is the signed size that existed immediately before the liquidation — negative for a short. Run it in the IDE: [Hyperliquid Wallet Liquidation History ➤](https://ide.bitquery.io/hyperliquid-wallet-liquidation-history) ```graphql query { Hyperliquid { PerpLiquidations( limit: {count: 100} orderBy: {descending: Block_Time} where: {Liquidation: {LiquidatedUser: {is: "0x2b5dba3bc4cbb1b94d1e57fcbe0fbc82731ce5f2"}}, Block: {Time: {since_relative: {days_ago: 3}}}} ) { Block { Time } Liquidation { Market { Symbol Kind } Method MarkPx LiquidatedUser Execution { Price Size Side Direction Hash } Position { Leverage IsCross Side SizeBefore } Fees { Fee FeeToken } } } } } ``` Sample response: ```json { "Block": { "Time": "2026-09-04T09:58:14.257546Z" }, "Liquidation": { "Market": { "Symbol": "SKR" }, "Method": "market", "MarkPx": "0.024767", "LiquidatedUser": "0x2b5dba3bc4cbb1b94d1e57fcbe0fbc82731ce5f2", "Execution": { "Price": "0.024831", "Size": "2663.0", "Side": "Buy", "Direction": "Close Short" }, "Position": { "Leverage": 3, "IsCross": false, "Side": "Short", "SizeBefore": "-2663.0" }, "Fees": { "Fee": "0.028565", "FeeToken": "USDC" } } } ``` `Execution.Direction` reads `Close Short` because liquidating a short is a buy that closes it — the direction describes the effect on the liquidated position, not the trader's intent. ## Liquidation totals per market One row per market and method, with the number of liquidation events, the total size closed and the fees charged. Run it in the IDE: [Hyperliquid Wallet Liquidation Totals ➤](https://ide.bitquery.io/hyperliquid-wallet-liquidation-totals) ```graphql query { Hyperliquid { PerpLiquidations( where: {Liquidation: {LiquidatedUser: {is: "0x2b5dba3bc4cbb1b94d1e57fcbe0fbc82731ce5f2"}}, Block: {Time: {since_relative: {days_ago: 3}}}} ) { Liquidation { Market { Symbol } Method } events: count size: sum(of: Liquidation_Execution_Size) fees: sum(of: Liquidation_Fees_Fee) } } } ``` ## Liquidations over a date range Bound the export to a tax year, a quarter, or the window around a specific market event. Run it in the IDE: [Hyperliquid Liquidations By Date Range ➤](https://ide.bitquery.io/hyperliquid-liquidations-by-date-range) ```graphql query { Hyperliquid { PerpLiquidations( limit: {count: 100} orderBy: {ascending: Block_Time} where: { Liquidation: {LiquidatedUser: {is: "0x2b5dba3bc4cbb1b94d1e57fcbe0fbc82731ce5f2"}} Block: {Time: {since: "2026-09-01T00:00:00Z", till: "2026-09-04T00:00:00Z"}} } ) { Block { Time } Liquidation { Market { Symbol } Method MarkPx Execution { Price Size Side Direction } Position { Leverage IsCross Side SizeBefore } Fees { Fee FeeToken } } } } } ``` ## Backstop liquidations only Filter on `Method` to isolate the liquidations the backstop vault absorbed rather than the open market. These carry different `Execution.Direction` values from ordinary liquidations — `Auto-Deleveraging` and `Partial Borrow Liquidation` both appear here. Run it in the IDE: [Hyperliquid Backstop Liquidations By Wallet ➤](https://ide.bitquery.io/hyperliquid-backstop-liquidations-by-wallet) ```graphql query { Hyperliquid { PerpLiquidations( limit: {count: 100} orderBy: {descending: Block_Time} where: { Liquidation: { LiquidatedUser: {is: "0x2b5dba3bc4cbb1b94d1e57fcbe0fbc82731ce5f2"} Method: {is: "backstop"} } Block: {Time: {since_relative: {days_ago: 3}}} } ) { Block { Time } Liquidation { Market { Symbol } Method MarkPx Execution { Price Size Side Direction } Position { Leverage IsCross SizeBefore } } } } } ``` ## Real-time liquidation alerts for one wallet Run it in the IDE: [Hyperliquid Wallet Liquidation Stream ➤](https://ide.bitquery.io/hyperliquid-wallet-liquidation-stream) ```graphql subscription { Hyperliquid { PerpLiquidations( where: {Liquidation: {LiquidatedUser: {is: "0x2b5dba3bc4cbb1b94d1e57fcbe0fbc82731ce5f2"}}} ) { Block { Time } Liquidation { Market { Symbol } Method MarkPx LiquidatedUser Execution { Price Size Side Direction } Position { Leverage IsCross Side SizeBefore } } } } } ``` Drop the `where` clause to stream every liquidation on the exchange, or swap the address for a list to watch a set of accounts. ## Notes - `Liquidator` is empty on open-market liquidations; it is populated when a specific liquidator address takes the position. - A single blow-up can produce several records — one per market the account held. - To see what the position looked like before it was closed, pair `Position.SizeBefore` here with the fills on the [Trades API](/docs/perpetuals/hyperliquid/hyperliquid-trades-api) and the funding it accrued on the [Funding Payments API](/docs/perpetuals/hyperliquid/hyperliquid-funding-payments-api). --- ## Hyperliquid Liquidations, Funding, Positions & Leverage API URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api/ Stream Hyperliquid perp liquidations, per-trader funding payments, current open positions and leverage changes with Bitquery GraphQL and WebSocket. # Hyperliquid Liquidations, Funding, Positions & Leverage API This page covers the perp risk cubes: `PerpLiquidations`, `PerpFundings`, `CurrentPositions` and `TraderLeverageUpdates`. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Liquidations Each liquidation includes the liquidated user, the `Method` (`market` for open-market liquidation, `backstop` when the backstop vault takes over), the mark price at liquidation and the actual execution. For every liquidation of one address, see [Hyperliquid Liquidation History by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-liquidation-history-by-wallet). Run it in the IDE: [Hyperliquid Liquidations ➤](https://ide.bitquery.io/hyperliquid-liquidations) ```graphql query { Hyperliquid { PerpLiquidations(limit: {count: 50}, orderBy: {descending: Block_Time}) { Block { Time } Liquidation { Market { Symbol Kind } Method MarkPx Liquidator LiquidatedUser Execution { Price Size Side Direction Hash } Position { Leverage IsCross Side SizeBefore } Fees { Fee FeeToken } } } } } ``` ### Real-time liquidation alerts Run it in the IDE: [Hyperliquid Liquidations Stream ➤](https://ide.bitquery.io/hyperliquid-liquidations-stream) ```graphql subscription { Hyperliquid { PerpLiquidations { Block { Time } Liquidation { Market { Symbol } Method MarkPx LiquidatedUser Execution { Price Size Side } Position { Leverage IsCross SizeBefore } } } } } ``` ## Funding payments `PerpFundings` records **per-trader funding transfers** at each hourly funding tick: the signed `Amount` (negative = the trader paid funding), the funding `Rate` applied and the position `Size` it applied to. Run it in the IDE: [Hyperliquid Funding Payments ➤](https://ide.bitquery.io/hyperliquid-funding-payments) ```graphql query { Hyperliquid { PerpFundings(limit: {count: 50}, orderBy: {descending: Block_Time}) { Block { Time } Funding { Market { Symbol Kind } Amount Rate Size Trader { Address } } } } } ``` Filter to one wallet with `where: {Funding: {Trader: {Address: {is: "0x..."}}}}` to compute its total funding paid/received. The [Hyperliquid Funding Payments API](/docs/perpetuals/hyperliquid/hyperliquid-funding-payments-api) page has ready-made wallet queries: per-market totals, paid-only, date ranges and a live stream. ## Current positions `CurrentPositions` is a **state cube** with the currently open perp positions: signed `Size` (negative = short, empty = flat), leverage, margin mode, accumulated `Funding` and `RealizedPnl`. Run it in the IDE: [Hyperliquid Current Positions ➤](https://ide.bitquery.io/hyperliquid-current-positions) ```graphql query { Hyperliquid { CurrentPositions( limit: {count: 50} orderBy: {descending: LastTime} where: {Market: {Symbol: {is: "BTC"}}} ) { LastBlock LastTime Market { Symbol Kind } Position { Size Leverage IsCross Funding RealizedPnl } Trader { Address } } } } ``` Swap the filter to `where: {Trader: {Address: {is: "0x..."}}}` to get every open position of one trader. ## Leverage updates `TraderLeverageUpdates` fires whenever a trader changes leverage or switches between cross and isolated margin on a market. Run it in the IDE: [Hyperliquid Leverage Updates ➤](https://ide.bitquery.io/hyperliquid-leverage-updates) ```graphql query { Hyperliquid { TraderLeverageUpdates(limit: {count: 50}, orderBy: {descending: Block_Time}) { Block { Time } LeverageUpdate { Leverage IsCross Market { Symbol } Trader { Address } } } } } ``` All four cubes stream over WebSocket with the same shape — change `query` to `subscription` and drop `limit`/`orderBy`. ## Related - [Liquidation History by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-liquidation-history-by-wallet) - [Funding Payments by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-funding-payments-api) - [HIP-3 Stocks API](/docs/perpetuals/hyperliquid/hip3-stocks-api) — funding and liquidations on stock, index and commodity perps - [Track ZEC on Hyperliquid](/docs/perpetuals/hyperliquid/track-zec-on-hyperliquid) — positions, liquidations and funding for one market --- ## Hyperliquid Mark Prices & Price Updates API URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-prices-api/ Get real-time Hyperliquid mark prices and oracle price updates with Bitquery GraphQL and WebSocket, covering native perps, spot and HIP-3 builder markets like tokenized stocks. # Hyperliquid Mark Prices & Price Updates API **In short:** the Hyperliquid mark price is what the exchange uses for margin, liquidations, TP/SL triggers and unrealized PnL. It updates about every 3 seconds and is the median of three prices: 1. The oracle price plus a 150-second EMA of (Hyperliquid mid price − oracle price) 2. The median of best bid, best ask and last trade on Hyperliquid 3. A weighted median of Binance, OKX, Bybit, Gate and MEXC perp mid prices You can get the latest mark price of every market with the `MarkPrices` query below. This page covers the `MarkPrices` and `PriceUpdates` cubes: the current mark price of every market, and the underlying oracle / reference price feed. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Mark prices `MarkPrices` is a **state cube**: one row per market with the latest mark price and the block it was last updated at. It includes HIP-3 markets, so tokenized stocks (`mkts:AAPL`, `mkts:GOOGL`) and indices are covered too. Run it in the IDE: [Hyperliquid Mark Prices ➤](https://ide.bitquery.io/hyperliquid-mark-prices) ```graphql query { Hyperliquid { MarkPrices(limit: {count: 100}, orderBy: {descending: LastTime}) { LastBlock LastTime Mark Market { Symbol Kind IsPerp Protocol CoinRaw } } } } ``` Get one market with `where: {Market: {Symbol: {is: "BTC"}}}` (or `CoinRaw` for HIP-3 markets, since symbols can repeat across deployer namespaces). ## Price updates `PriceUpdates` is the event feed the mark prices are built from. `Kind` distinguishes the source — values observed include `spotInput`, `extPerp` and `extPerpInput` — and `DailyPx` carries the daily reference price when present. `UpdateClass` is `Normal` in regular operation. Run it in the IDE: [Hyperliquid Price Updates Stream ➤](https://ide.bitquery.io/hyperliquid-price-updates-stream) ```graphql subscription { Hyperliquid { PriceUpdates { Block { Time } PriceUpdate { Kind Price DailyPx UpdateClass UpdateTime Market { Symbol Kind } } } } } ``` The same shape works as a `query` with `limit`, `orderBy: {descending: Block_Time}` and a `where` filter for historical lookups. For tradeable OHLCV rather than oracle prices, use [Candles](/docs/perpetuals/hyperliquid/hyperliquid-trades-api#ohlcv-candles). --- ## Hyperliquid Order Book API (L3) — Market-by-Order Data with Trader Attribution URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-orders-api/ Stream Hyperliquid's order book at L3 (market-by-order) granularity: every order delta carries its own order id and the wallet address behind it. Covers order lifecycle, book deltas and TWAPs over GraphQL and WebSocket. # Hyperliquid Order Book API (L3) Get the full Hyperliquid order book at order level, with the order id and wallet behind every change. This page covers the `Orders`, `BookUpdates` and `Twaps` cubes: the full order lifecycle, order-book deltas you can rebuild the book from, and TWAP order execution. ## L2 vs L3: what you get here Most order-book feeds — including Hyperliquid's own public `l2Book` websocket channel and the L2 snapshots in its `s3://hyperliquid-archive` bucket — are **L2**: resting size aggregated per price level. You see that 40 BTC sits at $95,000. You cannot see whether that is one order or twenty, whose it is, or which one was pulled a moment later. `BookUpdates` is **L3 (market-by-order)**. Every delta is a single order, and it carries: | | L2 (aggregated) | L3 (market-by-order) — Bitquery | | --- | --- | --- | | Grain | Total size per price level | One event per individual order | | Order identity | — | `Oid`, joinable to `Orders` and `Trades` | | Who placed it | — | `Trader { Address }` — the wallet itself | | Size change | Net level change only | `Size` and `SizeBefore` on that specific order | | Depth | Hyperliquid's `l2Book` returns 5–20 levels | Unlimited | Because Hyperliquid settles on a transparent L1, the wallet behind each order is public. That makes this L3 **with named attribution** — something that does not exist on centralised venues, where L3 feeds are anonymised by the exchange. Hyperliquid's own API only exposes order-level detail for *your own* account (`orderUpdates`, `userFills`); market-wide, it serves aggregated L2. This is what makes wallet-level analysis possible: watching a market maker quote and pull, reconstructing a trader's full order flow rather than just their fills, or spotting orders that were never intended to trade. See [Track Hyperliquid Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet) for worked examples. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Order updates Every order event carries `Status` (`open`, `filled`, `canceled`, `rejected`, ...), `OrderType` (`Limit`, trigger types like Stop Market / Take Profit), time-in-force `Tif` (`Gtc`, `Ioc`, `Alo`), the limit price, the current and original size, and trigger settings for conditional orders. Run it in the IDE: [Hyperliquid Recent Orders ➤](https://ide.bitquery.io/hyperliquid-recent-orders) ```graphql query { Hyperliquid { Orders( limit: {count: 50} where: {Block: {Time: {since_relative: {minutes_ago: 1}}}} ) { Block { Time } Order { Market { Symbol Kind } Oid Status OrderType Tif Side LimitPx Size OrigSz IsTrigger TriggerPx TriggerCondition ReduceOnly Cloid Trader { Address } } } } } ``` - `Oid` links order events to fills (`Trade.Execution.Oid`) and book updates (`BookUpdate.Oid`); `Cloid` is the client-assigned order id. - `Size` is the remaining size, `OrigSz` the original size. - `IsTrigger`, `TriggerPx`, `TriggerCondition` describe stop / take-profit orders; `IsPositionTpsl` marks position-attached TP/SL. ### Real-time order stream Run it in the IDE: [Hyperliquid Orders Stream ➤](https://ide.bitquery.io/hyperliquid-orders-stream) ```graphql subscription { Hyperliquid { Orders(where: {Order: {Market: {Symbol: {is: "ETH"}}}}) { Block { Time } Order { Market { Symbol } Oid Status OrderType Tif Side LimitPx Size OrigSz IsTrigger ReduceOnly Trader { Address } } } } } ``` ## Order book updates `BookUpdates` streams **deltas of the on-chain order book**. `Kind` is `new` (level added), `change` (size changed) or `remove` (order left the book); with `Px`, `Size`, `SizeBefore`, the order `Oid` and the trader behind the order. Consume the stream and apply the deltas to maintain a live book. Run it in the IDE: [Hyperliquid Order Book Stream ➤](https://ide.bitquery.io/hyperliquid-orderbook-stream) ```graphql subscription { Hyperliquid { BookUpdates(where: {BookUpdate: {Market: {Symbol: {is: "BTC"}}}}) { Block { Time } BookUpdate { Kind Side Px Size SizeBefore Oid Market { Symbol } Trader { Address } } } } } ``` This is the L3 grain described above: unlike an aggregated L2 feed, each delta is attributable to an **individual order and trader address** — you can watch a specific market maker's quoting in real time by filtering on `BookUpdate: {Trader: {Address: {is: "0x..."}}}`. ## TWAP orders `Twaps` tracks the lifecycle of TWAP orders: `State.Status` moves from `activated` through execution to `finished` (or `terminated`), with executed size and notional so far. Run it in the IDE: [Hyperliquid TWAP Orders ➤](https://ide.bitquery.io/hyperliquid-twap-orders) ```graphql query { Hyperliquid { Twaps(limit: {count: 50}, orderBy: {descending: Block_Time}) { Block { Time } Twap { TwapId Market { Symbol } Order { Side Size ReduceOnly Randomize } Interval { DurationMinutes StartTime EventTime } State { Status StatusError ExecutedSize ExecutedNotional } Trader { Address } } } } } ``` `TwapId` matches `Trade.TwapId` on fills with `IsTwap: true`, so you can join a TWAP to its individual child fills. [Hyperliquid TWAP Orders & Child Fills](/docs/perpetuals/hyperliquid/hyperliquid-twap-fills) walks through that join, with per-wallet TWAP history and an execution summary. ## Related - [Track Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet) — market-maker tracking with L3 data - [TWAP Orders & Child Fills](/docs/perpetuals/hyperliquid/hyperliquid-twap-fills) - [HIP-3 Stocks API](/docs/perpetuals/hyperliquid/hip3-stocks-api) — order book for stock and commodity perps --- ## Hyperliquid Signed Actions API URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-signed-actions-api/ Query and stream raw signed L1 actions on Hyperliquid with Bitquery: order, cancel, modify, batchModify, updateLeverage and more, with signer, broadcaster, bundle hash and status. # Hyperliquid Signed Actions API `SignedActions` is the lowest-level Hyperliquid cube: every **signed user action** submitted to the L1 — order placements, cancels, modifies, leverage updates, transfers and the rest — before they materialize as orders, trades or position changes. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Latest signed actions Run it in the IDE: [Hyperliquid Signed Actions ➤](https://ide.bitquery.io/hyperliquid-signed-actions) ```graphql query { Hyperliquid { SignedActions(limit: {count: 50}, orderBy: {descending: Block_Time}) { Block { Number Time } ActionType Status User Signer Nonce VaultAddress Bundle { Hash Broadcaster ActionIndex } Leverage { Asset Value IsCross } } } } ``` Field notes: - `ActionType` — the action name as submitted: `order`, `cancel`, `cancelByCloid`, `modify`, `batchModify`, `updateLeverage`, `twapOrder`, and other Hyperliquid exchange actions. - `User` vs `Signer` — `User` is the account the action applies to; `Signer` is the key that signed it, which differs when an **agent / API wallet** acts for the account. `VaultAddress` is set for vault-scoped actions. - `Bundle` — actions arrive in broadcast bundles; `Hash` is the bundle hash, `Broadcaster` the node that broadcast it, `ActionIndex` the action's position in the bundle. - `Status` — `ok` for accepted actions, or an error status for rejected ones; `Response` (raw string field) carries the node response. - `Action` — the raw action payload as a JSON string, when you need parameters beyond the typed fields. - `Leverage { Asset Value IsCross }` is populated for `updateLeverage` actions. Filter examples: - One account's activity: `where: {User: {is: "0x..."}}` - Only leverage updates: `where: {ActionType: {is: "updateLeverage"}}` - Failed actions: `where: {Status: {not: "ok"}}` As with every Hyperliquid cube, changing `query` to `subscription` (and dropping `limit`/`orderBy`) turns this into a real-time WebSocket stream — useful for monitoring an account's or broadcaster's full action flow live. ## Related - [Track Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet) — the order lifecycle behind a wallet's actions - [TWAP Orders & Child Fills](/docs/perpetuals/hyperliquid/hyperliquid-twap-fills) — what a `twapOrder` action produces - [Funding Payments by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-funding-payments-api) - [Liquidation History by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-liquidation-history-by-wallet) --- ## Hyperliquid TWAP Orders & Child Fills URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-twap-fills/ Reconstruct a Hyperliquid TWAP order end to end: the parent order lifecycle and every child fill it produced, joined on TwapId, with size, price, fees and realized PnL. # Hyperliquid TWAP Orders & Child Fills **In short:** a Hyperliquid TWAP splits one large order into suborders sent at a fixed interval (at least 30 seconds apart) over the running time you choose. Each suborder has a 3% max slippage, and if one under-fills, later suborders catch up, up to 3x the normal slice size. With Bitquery you can get every TWAP a wallet placed from the `Twaps` cube, and every fill it produced from `Trades` filtered on `TwapId`. A TWAP on Hyperliquid is one parent order that executes as many small child fills over a set duration. Tools that only read the parent order see a single line; tools that only read fills see dozens of unexplained trades. Neither reconciles. This page joins the two: the `Twaps` cube for the parent order lifecycle, and the `Trades` cube filtered on `TwapId` for every fill it produced. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## TWAP orders of one wallet `State.Status` moves from `activated` through execution to `finished` (or `terminated`). Each status change is its own record, so a completed TWAP appears at least twice — once on activation with zero executed, once at the end with the final totals. Run it in the IDE: [Hyperliquid Wallet TWAP Orders ➤](https://ide.bitquery.io/hyperliquid-wallet-twap-orders) ```graphql query { Hyperliquid { Twaps( limit: { count: 50 } orderBy: { descending: Block_Time } where: { Twap: { Trader: { Address: { is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2" } } } Block: {Time: {since_relative: {days_ago: 3}}} } ) { Block { Time } Twap { TwapId Market { Symbol } Order { Side Size ReduceOnly Randomize } Interval { DurationMinutes StartTime } State { Status StatusError ExecutedSize ExecutedNotional } Trader { Address } } } } } ``` Sample response — the same TWAP at activation and at completion: ```json { "Block": { "Time": "2026-09-04T09:42:28.039330Z" }, "Twap": { "TwapId": "2180078", "Market": { "Symbol": "GOLD" }, "Order": { "Side": "Sell", "Size": "40.0", "ReduceOnly": true, "Randomize": true }, "Interval": { "DurationMinutes": 240 }, "State": { "Status": "finished", "ExecutedSize": "40.0", "ExecutedNotional": "178900.30962" } } } ``` ## Every child fill of one TWAP `TwapId` on the parent matches `Trade.TwapId` on the fills. Filter the `Trades` cube on it to get the complete execution. Run it in the IDE: [Hyperliquid TWAP Child Fills ➤](https://ide.bitquery.io/hyperliquid-twap-child-fills) ```graphql query { Hyperliquid { Trades( limit: { count: 100 } orderBy: { ascending: Block_Time } where: { Trade: {TwapId: {eq: "2180078"}} Block: {Time: {since_relative: {days_ago: 3}}} } ) { Block { Time } Trade { TwapId Market { Symbol } Execution { Price Size Side Direction Tid } Fees { Fee FeeToken } Position { RealizedPnl } Trader { Address } } } } } ``` `TwapId` is a big integer, so the filter operator is `eq`, not `is`. A long TWAP can produce more fills than one page holds — TWAP `2180078` has 522. Raise `limit` or page through with `offset` when you need every one of them. ## TWAP execution summary Aggregate the child fills to reconcile against the parent order's `ExecutedSize` and `ExecutedNotional`. Run it in the IDE: [Hyperliquid TWAP Execution Summary ➤](https://ide.bitquery.io/hyperliquid-twap-execution-summary) ```graphql query { Hyperliquid { Trades( where: { Trade: {TwapId: {eq: "2180078"}} Block: {Time: {since_relative: {days_ago: 3}}} } ) { Trade { TwapId Market { Symbol } } fills: count filled: sum(of: Trade_Execution_Size) avgPrice: average(of: Trade_Execution_Price) fees: sum(of: Trade_Fees_Fee) pnl: sum(of: Trade_Position_RealizedPnl) } } } ``` For TWAP `2180078` this returns 522 fills totalling exactly the 40.0 size on the parent order, at an average price of 4472.40, with 161.01 USDC of fees and 12,061.51 of realized PnL. That reconciliation — parent `ExecutedSize` equals the sum of child fill sizes — is the check most exports fail. ## All TWAP fills of a wallet To pull every TWAP-produced fill for an address without going TWAP by TWAP, filter on `IsTwap` instead. Run it in the IDE: [Hyperliquid Wallet TWAP Fills ➤](https://ide.bitquery.io/hyperliquid-wallet-twap-fills) ```graphql query { Hyperliquid { Trades( limit: { count: 100 } orderBy: { descending: Block_Time } where: { Trade: { IsTwap: true Trader: { Address: { is: "0x1e6db0fdf1a0f6edb78753184eb0fe5485c2eef2" } } } Block: {Time: {since_relative: {days_ago: 3}}} } ) { Block { Time } Trade { TwapId Market { Symbol } Execution { Price Size Side Direction Tid } Fees { Fee FeeToken } Position { RealizedPnl } } } } } ``` ## Real-time TWAP stream Run it in the IDE: [Hyperliquid TWAP Orders Stream ➤](https://ide.bitquery.io/hyperliquid-twap-orders-stream) ```graphql subscription { Hyperliquid { Twaps { Block { Time } Twap { TwapId Market { Symbol } Order { Side Size ReduceOnly Randomize } State { Status ExecutedSize ExecutedNotional } Trader { Address } } } } } ``` ## Notes - `Order.Randomize` indicates the TWAP randomises its slice sizes, which is why child fills are uneven. - `Interval.DurationMinutes` is the configured TWAP length; `StartTime` is a millisecond timestamp. - For non-TWAP order lifecycle and L3 book deltas, see the [Orders API](/docs/perpetuals/hyperliquid/hyperliquid-orders-api). --- ## Hyperliquid Trades & Candles API URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-trades-api/ Query and stream Hyperliquid trades (fills) and OHLCV candles with Bitquery GraphQL and WebSocket: price, size, direction, fees, leverage, realized PnL and per-interval OHLCV. # Hyperliquid Trades & Candles API This page covers the `Trades` and `Candles` cubes: every fill on Hyperliquid with full execution context, and OHLCV candles per market and interval. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Latest trades Each fill carries the execution (price, size, side, aggressor flag), the position it changed (leverage, margin mode, size before, realized PnL) and fees. `Direction` is one of `Open Long`, `Open Short`, `Close Long`, `Close Short`. Run it in the IDE: [Hyperliquid Latest Trades ➤](https://ide.bitquery.io/hyperliquid-latest-trades) ```graphql query { Hyperliquid { Trades( limit: {count: 50} orderBy: {descending: Block_Time} where: {Block: {Time: {since_relative: {minutes_ago: 10}}}} ) { Block { Number Time } Trade { Market { Symbol CoinRaw Kind IsPerp Protocol } Execution { Price Size Side Direction IsAggressor Oid Tid Hash } Fees { Fee FeeToken BuilderFee } IsTwap Position { Leverage IsCross Side SizeBefore RealizedPnl } Trader { Address Vault Signer } } } } } ``` Notes on the payload: - `Execution.Tid` is the trade id, `Oid` the order id that got filled, `Hash` the action hash. - `Fees.Fee` is in `FeeToken` (usually USDC); a **negative fee is a maker rebate**. - `Position.SizeBefore` is the signed position size before the fill (negative = short); `RealizedPnl` is the PnL realized by this fill. - `IsTwap: true` marks fills produced by a TWAP order (see [TWAPs](/docs/perpetuals/hyperliquid/hyperliquid-orders-api#twap-orders)). ### Trades of one market Run it in the IDE: [Hyperliquid BTC Perp Trades ➤](https://ide.bitquery.io/hyperliquid-btc-perp-trades) ```graphql query { Hyperliquid { Trades( limit: {count: 50} orderBy: {descending: Block_Time} where: {Trade: {Market: {Symbol: {is: "BTC"}}}} ) { Block { Time } Trade { Execution { Price Size Side Direction IsAggressor } Position { Leverage IsCross RealizedPnl } Trader { Address } } } } } ``` Filter by trader instead with `where: {Trade: {Trader: {Address: {is: "0x..."}}}}`. ## Real-time trades stream Run it in the IDE: [Hyperliquid Trades Stream ➤](https://ide.bitquery.io/hyperliquid-trades-stream) ```graphql subscription { Hyperliquid { Trades { Block { Number Time } Trade { Market { Symbol Kind IsPerp } Execution { Price Size Side Direction IsAggressor Oid Tid } Fees { Fee FeeToken } IsTwap Position { Leverage IsCross Side RealizedPnl } Trader { Address } } } } } ``` ## OHLCV candles The `Candles` cube provides OHLCV per market and interval. `Interval.Time.Duration` is the candle length in **seconds** (e.g. `60` for one minute), `Start` the interval open time. OHLCV values are floats. Run it in the IDE: [Hyperliquid BTC OHLCV Candles ➤](https://ide.bitquery.io/hyperliquid-btc-ohlcv-candles) ```graphql query { Hyperliquid { Candles( limit: {count: 60} orderBy: {descending: Interval_Time_Start} where: {Market: {Symbol: {is: "BTC"}}, Interval: {Time: {Duration: {eq: 60}}}} ) { Interval { Time { Start Duration } } Market { Symbol Kind CoinRaw } Ohlc { Open High Low Close Volume } } } } ``` HIP-3 markets get candles too — e.g. `where: {Market: {CoinRaw: {is: "mkts:AAPL"}}}` for the tokenized-stock market. ### Real-time candle stream Run it in the IDE: [Hyperliquid Candles Stream ➤](https://ide.bitquery.io/hyperliquid-candles-stream) ```graphql subscription { Hyperliquid { Candles(where: {Market: {Symbol: {is: "BTC"}}}) { Interval { Time { Start Duration } } Market { Symbol Kind } Ohlc { Open High Low Close Volume } } } } ``` Remove the `Symbol` filter to stream candle updates for every market. ## Related - [HIP-3 Stocks API](/docs/perpetuals/hyperliquid/hip3-stocks-api) — trades and candles for S&P 500, US stock and commodity perps - [TWAP Orders & Child Fills](/docs/perpetuals/hyperliquid/hyperliquid-twap-fills) — join fills with `IsTwap: true` to their parent TWAP - [Track ZEC on Hyperliquid](/docs/perpetuals/hyperliquid/track-zec-on-hyperliquid) — whale trades and candles for one market --- ## Hyperliquid Vaults API — Vault Trades, PnL, Deposits and Liquidations URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-vaults-api/ Track Hyperliquid vaults with Bitquery: vaults ranked by volume and PnL, every fill a vault made, HLP deposits and withdrawals, a live vault transfer stream and vault liquidations over GraphQL and WebSocket. # Hyperliquid Vaults API Hyperliquid vaults are accounts that trade with pooled deposits: HLP, the protocol's own liquidity vault, and user vaults run by individual traders. With Bitquery you can rank every vault by volume and PnL, read each fill a vault made, follow deposits and withdrawals as they happen, and see when a vault position was liquidated. Every event a vault makes carries its address in `Trader.Vault`, so one filter covers the whole set. Everything below runs on the `Hyperliquid` cube at `https://streaming.bitquery.io/graphql` over the rolling window described on the [Hyperliquid API overview](/docs/perpetuals/hyperliquid). The worked vault is `0x25773676902085225ae5751075e4161369334897`, one of the most active user vaults, and the HLP vault is `0xdfc24b077bc1425ad1dea75bcb6f8158e10df303`. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Which Hyperliquid vaults trade the most? Filter fills to those with a vault set and group by `Trader.Vault`. Each row is one vault with its fills, markets traded, USD volume and realized PnL over the last day. Run it in the IDE: [Hyperliquid Top Vaults by Volume ➤](https://ide.bitquery.io/hyperliquid-top-vaults-by-volume) ```graphql query HyperliquidTopVaultsByVolume { Hyperliquid { Trades( limit: {count: 25} orderBy: {descendingByField: "volumeUsd"} where: { Trade: {Trader: {Vault: {not: ""}}} Block: {Time: {since_relative: {hours_ago: 24}}} } ) { Trade { Trader { Vault } } fills: count markets: uniq(of: Trade_Market_CoinRaw) volumeUsd: sum(of: Trade_Execution_NotionalUSD) realizedPnl: sum(of: Trade_Position_RealizedPnl) } } } ``` A vault with hundreds of thousands of fills and PnL near zero is a market-making vault. HLP trades through child vaults, so its activity appears under those addresses rather than the HLP address itself. ## Which vaults are the most profitable? Every closing fill carries `Position.NetPnl` and `Position.IsWin`. Group by vault over a week to rank vaults by net realized PnL and count winning closes. Run it in the IDE: [Hyperliquid Vault PnL Leaderboard ➤](https://ide.bitquery.io/hyperliquid-vault-pnl-leaderboard) ```graphql query HyperliquidVaultPnlLeaderboard { Hyperliquid { Trades( limit: {count: 25} orderBy: {descendingByField: "netPnl"} where: { Trade: {Trader: {Vault: {not: ""}}, Position: {IsClosing: true}} Block: {Time: {since_relative: {days_ago: 7}}} } ) { Trade { Trader { Vault } } closes: count wins: count(if: {Trade: {Position: {IsWin: true}}}) netPnl: sum(of: Trade_Position_NetPnl) volumeUsd: sum(of: Trade_Execution_NotionalUSD) } } } ``` Win rate is `wins / closes`. Order by `ascendingByField: "netPnl"` for the biggest losers. ## What did one vault trade? Filter on the vault address. Each row is a fill with market, price, size, direction, leverage, fee and realized PnL. `Trader.Address` is the account that executed it; for a vault trading its own book it equals the vault. Run it in the IDE: [Hyperliquid Vault Fills ➤](https://ide.bitquery.io/hyperliquid-vault-fills) ```graphql query HyperliquidVaultFills { Hyperliquid { Trades( limit: {count: 50} orderBy: {descending: Block_Time} where: { Trade: {Trader: {Vault: {is: "0x25773676902085225ae5751075e4161369334897"}}} Block: {Time: {since_relative: {hours_ago: 24}}} } ) { Block { Time } Trade { Market { Symbol Kind } Execution { Price Size Side Direction NotionalUSD } Position { Leverage RealizedPnl } Fees { Fee } Trader { Address Vault } } } } } ``` Change `query` to `subscription` and drop `limit` and `orderBy` to stream a vault's fills live. ## Who deposits into and withdraws from a vault? Deposits and withdrawals are `vaultTransfer` actions in the `SignedActions` cube. The `Action` payload holds the vault address, `isDeposit` and the amount in `usd` with six decimals, so `100000000` is 100 USDC. `User` is the depositor. The example follows the HLP vault; swap in any vault address. Run it in the IDE: [Hyperliquid Vault Deposits and Withdrawals ➤](https://ide.bitquery.io/hyperliquid-vault-deposits-withdrawals) ```graphql query HyperliquidVaultDepositsAndWithdrawals { Hyperliquid { SignedActions( limit: {count: 50} orderBy: {descending: Block_Time} where: { ActionType: {is: "vaultTransfer"} Action: {includes: "0xdfc24b077bc1425ad1dea75bcb6f8158e10df303"} } ) { Block { Time } User Action Status Bundle { Hash } } } } ``` `Status` is `ok` for accepted transfers. Rejected ones stay in the data with an error status, which is useful for spotting withdrawals that hit a lock-up. ## Live vault deposits and withdrawals The same action as a WebSocket stream, across every vault, filtered to successful transfers. Run it in the IDE: [Hyperliquid Vault Transfers Stream ➤](https://ide.bitquery.io/hyperliquid-vault-transfers-stream) ```graphql subscription HyperliquidVaultTransfersStream { Hyperliquid { SignedActions(where: {ActionType: {is: "vaultTransfer"}, Status: {is: "ok"}}) { Block { Time } User Action Bundle { Hash } } } } ``` Parse `Action` on the client to alert on large deposits or on any transfer into a vault you follow. ## Which vault positions were liquidated? `PerpLiquidations` carries `Trader.Vault` too. This lists every liquidation of a vault position in the last week with the method, mark price and leverage at the time. Run it in the IDE: [Hyperliquid Vault Liquidations ➤](https://ide.bitquery.io/hyperliquid-vault-liquidations) ```graphql query HyperliquidVaultLiquidations { Hyperliquid { PerpLiquidations( limit: {count: 50} orderBy: {descending: Block_Time} where: { Liquidation: {Trader: {Vault: {not: ""}}} Block: {Time: {since_relative: {days_ago: 7}}} } ) { Block { Time } Liquidation { Market { Symbol } Method MarkPx Execution { Price Size Side } Position { Leverage SizeBefore } Trader { Address Vault } } } } } ``` Filter `Liquidation: {Trader: {Vault: {is: "0x..."}}}` to watch one vault, or turn it into a subscription for live alerts. ## Related - [Hyperliquid API overview](/docs/perpetuals/hyperliquid) - [Liquidations, Funding, Positions & Leverage](/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api) - [Signed Actions API](/docs/perpetuals/hyperliquid/hyperliquid-signed-actions-api) - [Track Order Flow by Wallet](/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet) - [Trades & Candles](/docs/perpetuals/hyperliquid/hyperliquid-trades-api) --- ## Labelling Rules for Solana Wash Trades URL: https://docs.bitquery.io/docs/usecases/wash-trading-detector/prepare-data/rules/ Build Labelling Rules for Solana Wash Trades: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Labelling Rules for Solana Wash Trades This module implements a set of domain-specific rules to identify potentially suspicious DEX trades and label the column `is_wash_trades` as true against them. These rules will be applied to prepare labelled data on which the model training will occur. These rules will recieve a list of JSON objects and return a dataframe object. ## Detect Self Trades This rule flags the trades where the buyer and seller are the same wallet address(a classic wash trading indicator) as wash trades. ```py def detect_self_trades(df): return df[df["Trade.Buy.Account.Address"] == df["Trade.Sell.Account.Address"]] ``` ## Detect Repeated Pairs This rule flags wallet pairs that trade with each other more than threshold times, indicating collusion or repetitive trading behavior. ```py def detect_repeated_pairs(df, threshold=5): pairs = df.groupby([ "Trade.Buy.Account.Address", "Trade.Sell.Account.Address" ]).size().reset_index(name="count") return pairs[pairs["count"] > threshold] ``` ## Detect Loops This rule detects looped trades across different wallets, e.g., A → B → A, which may indicate sophisticated wash trading. ```py def detect_loops(df): merged = df.merge(df, left_on="Trade.Buy.Account.Address", right_on="Trade.Sell.Account.Address") loops = merged[merged["Trade.Sell.Account.Address_x"] == merged["Trade.Buy.Account.Address_y"]] return loops ``` ## Detect Spoofing This rule identifies trades with unusually large price spreads between buy/sell sides, possibly faking market depth or price manipulation. ```py def detect_spoofing(df, price_threshold=2.0): df["spread"] = abs(df["Trade.Buy.PriceInUSD"] - df["Trade.Sell.PriceInUSD"]) return df[df["spread"] > price_threshold] ``` ## Labeling Function This function combines all the above rule-based outputs, identifies wallets involved in any suspicious activity and returns the list of suspicious tokens, suspicious wallet addresses and suspicious transaction signatures. ```py def get_suspicious_summary(self_df, repeated_df, loops_df, spoofed_df, original_df): wallets = set(self_df["Trade.Buy.Account.Address"]) wallets |= set(repeated_df["Trade.Buy.Account.Address"]) wallets |= set(loops_df["Trade.Buy.Account.Address_x"]) wallets |= set(spoofed_df["Trade.Buy.Account.Address"]) suspicious_trades = original_df[ original_df["Trade.Buy.Account.Address"].isin(wallets) | original_df["Trade.Sell.Account.Address"].isin(wallets) ] suspicious_tokens = suspicious_trades["Trade.Buy.Currency.MintAddress"].unique().tolist() suspicious_tx = suspicious_trades["Transaction.Signature"].unique().tolist() return suspicious_tokens, suspicious_tx, wallets ``` --- ## Limit Order Price API URL: https://docs.bitquery.io/docs/trading/crypto-price-api/limit-order-price-api/ Limit Order Price API via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. See examples in the Bitquery IDE. # Limit Order Price API: Crypto Price API for Limit Orders ## The Problem: Why Raw Trade Data Fails for Limit Orders Finding reliable price for limit order execution is critical for automated trading systems. Raw DEX trade data creates unreliable price signals that trigger false limit order executions, making a dedicated limit order price API essential for trading platforms. ### **Pool-Based Monitoring Issues** Current platforms monitor pool-based DEX streams filtered by specific addresses, but sometimes even a $500 trade can move prices 20-50% in less liquid pools: - **False triggers**: Price spikes cause unintended executions, requiring complex retry logic - **No pre-execution validation**: Systems skip validation to avoid latency, relying on user slippage tolerance - **User behavior**: Traders gravitate toward liquid pools, leaving smaller pools vulnerable - **System complexity**: Sophisticated retry mechanisms needed for erratic executions - **Poor execution**: Bad price signals reduce user confidence ### **Cross-DEX Price Fragmentation** The same token can trade at vastly different prices (e.g., $100 on Uniswap vs $95 on SushiSwap) due to liquidity isolation, arbitrage delays, and varying AMM formulas. ## The Solution: Aggregated Price Streams Bitquery's [Crypto Price API for limit orders](/docs/trading/crypto-price-api/) provides **aggregated price data** with built-in dampening mechanisms, serving as a reliable limit order price API: **Multi-pool aggregation** across all major DEXs **Cross-chain price discovery** for market-wide pricing **Real-time USD pricing** with built-in conversion **Time & volume thresholds** preventing manipulation [Try the API live ➤](https://ide.bitquery.io/1-second-crypto-price-stream) ### How Price Aggregation Works #### 1. **Time-Based Aggregation (1s to 1h)** - **1-3s**: High-frequency trading with minimal smoothing - **5s**: Optimal for most automated platforms - **30s**: Swing trading with reduced noise - **1h**: Long-term portfolio management #### 2. **Volume Thresholds ($1K to $1M)** - **$1K+**: Small-cap tokens - **$10K+**: Mid-cap tokens - **$100K+**: High-cap tokens - **$1M+**: Enterprise stability #### 3. **Multi-Chain Coverage** - **Ethereum**: Uniswap, SushiSwap, Curve, 1inch - **Solana**: Raydium, Orca, Jupiter - **BSC**: PancakeSwap, Venus - **Cross-chain normalization** for unified pricing #### 4. **Trade Filtering** Trades are excluded only when the trade amount is zero or below decimal precision (e.g. < 10^decimals/10,000), avoiding precision loss. Moving averages (SMA, WMA, EMA) and anomaly-resistant OHLC are still calculated on the resulting feed. For full details see the [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm). ## TradingView Integration TradingView integration can be achieved through a custom datafeed that connects to Bitquery's real-time price streams. The datafeed supports multiple timeframe resolutions (1s to 1h), subscribes to real-time OHLC data via WebSocket, and provides the structured bar data format that TradingView requires for live chart updates. This enables professional-grade charting with institutional-quality aggregated price data. [View live TradingView demo ➤](https://github.com/bitquery/tradingview-subscription-realtime/tree/main#demo) ### **Production Resources** **Complete Tutorial**: Follow our comprehensive [TradingView Real-Time Streaming Tutorial](/docs/usecases/tradingview-subscription-realtime/getting-started/) that shows how to integrate Bitquery's Crypto Price API with TradingView's Advanced Charts. **Production-Ready Code**: - **GitHub Repository**: [Complete implementation examples](https://github.com/bitquery/tradingview-subscription-realtime/tree/main) - **NPM SDK**: [`@bitquery/tradingview-sdk`](https://www.npmjs.com/package/@bitquery/tradingview-sdk) - Ready-to-chart SDK with copy-paste integration **Quick Start**: Simply copy the Advanced Charting Library into your project, add your Bitquery access token, and you're ready to stream real-time crypto price data to TradingView charts. ## Raw vs Aggregated Price Data: Real Examples ### **Raw Trade Data Issues (DEXTradeByTokens API)** Using raw DEX trade data directly can lead to unreliable limit order triggers. Here's what you typically see: **Example: ETH Price Volatility from Raw Trades** ```graphql subscription { EVM { DEXTradeByTokens( where: {Trade: {Currency: {Symbol: {is: "WETH"}}}} ) { Trade { PriceInUSD AmountInUSD } Block { Time } } } } ``` [Run this query ➤](https://ide.bitquery.io/ETH-price-raw-trades) **Typical Raw Data Results:** - **12:00:01** - $2,420.15 (normal trade) - **12:00:02** - $3,890.44 (**+60% spike** from $500 sandwich attack) - **12:00:03** - $2,418.33 (back to normal) - **12:00:04** - $1,205.67 (**-50% crash** from MEV bot) - **12:00:05** - $2,422.89 (normal again) **Result**: Your limit orders at $2,500 would trigger falsely on the $3,890 spike, executing at poor prices. ### **Aggregated Price Data Solution (Crypto Price API)** The same ETH data through aggregated price feeds provides stability: **Example: ETH Price with 5-Second Time Aggregation** ```graphql subscription { Trading { Tokens( where: { Token: {Symbol: {is: "WETH"}, Network: {is: "ethereum"}} Interval: {Time: {Duration: {eq: 5}}} } ) { Price { Ohlc { Open High Low Close } Average { Mean SimpleMoving WeightedSimpleMoving } } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } } } } ``` [Run this query ➤](https://ide.bitquery.io/ETH-aggregated-5s-intervals) **Aggregated Results (Same Time Period):** - **12:00:05** - OHLC: $2,420/$2,425/$2,418/$2,423 (**Stable range**) - **Mean Price**: $2,421.50 (outliers filtered) - **Volume**: $847,500 (sufficient for reliability) **Result**: Your limit order at $2,500 remains inactive, preventing false execution. ## Types of Aggregation Available ### **1. Time-Based Aggregation** **Available Intervals**: `1, 3, 5, 10, 30, 60, 300, 900, 1800, 3600` seconds **Use Cases by Interval:** - **1-3 seconds**: High-frequency trading with minimal smoothing - **5-10 seconds**: Standard automated trading platforms - **30-60 seconds**: Swing trading and portfolio management - **15+ minutes**: Long-term position management **Production Examples**: - [Real-time 1-second price stream ➤](https://ide.bitquery.io/1-second-crypto-price-stream) - [Multi-timeframe aggregation ➤](https://ide.bitquery.io/crypto-price-multiple-intervals) ### **2. Volume-Based Aggregation** :::caution Not currently available Volume-based intervals are **not currently available**. Filtering on `Interval: { TargetVolume: ... }` or `Interval: { VolumeBased: true }` returns **zero rows**, with HTTP 200 and no error — the query looks like it simply found nothing. To approximate the same intent, use a time interval and filter on volume in the `where` clause: ```graphql subscription { Trading { Tokens( where: { Token: {Symbol: {is: "USDC"}} Interval: {Time: {Duration: {eq: 300}}} Volume: {Usd: {gt: 100000}} } ) { Price { Ohlc { Close } } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } } } } ``` ::: **Example Results**: - **$100k volume threshold**: Price updates only when $100k+ traded - **Result**: Ultra-stable pricing, immune to low-volume manipulation - **Update frequency**: Variable (every 30 seconds to 5 minutes depending on activity) ### **3. Multi-Chain Currency Aggregation** **Cross-chain Bitcoin pricing example:** ```graphql subscription { Trading { Currencies( where: {Currency: {Id: {is: "bid:bitcoin"}}} ) { Price { Ohlc { Close } } Volume { Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } } } } ``` [Run cross-chain Bitcoin query ➤](https://ide.bitquery.io/bitcoin-cross-chain-price) **Aggregation Sources**: - Native BTC on Bitcoin network - WBTC on Ethereum - Wrapped BTC on Solana - cbBTC on Base **Result**: True market-wide Bitcoin price, not isolated to single chain. ### **4. Pair-Specific Market Data** :::tip Track the deepest market instead of hardcoding one Pinning a limit order to a **named** pool reintroduces the single-pool risk described above — that pool can thin out or stop being where the token trades. Adding **`Ranking: { Position: { eq: 1 } }`** to a `Pairs` query instead follows the token's **top market** automatically: you get the price from the deepest pool at any moment, and thin pools cannot influence it. This is the complement to blended aggregation, not a contradiction of it. Blended [`Tokens`](/docs/trading/crypto-price-api/tokens) prices dampen spikes by mixing every pool, including thin ones; rank-1 removes the thin pools entirely and quotes the market with the most volume. Use blended prices when you want smoothing across the whole market, and rank-1 when you want the price you could actually execute against. See [Getting the Most Accurate Token Price](/docs/trading/crypto-price-api/pairs#most-accurate-token-price). ::: **Example: SOL/USDC on Raydium** ```graphql subscription { Trading { Pairs( where: { Market: {Protocol: {is: "Raydium"}} Token: {Symbol: {is: "SOL"}} QuoteToken: {Symbol: {is: "USDC"}} } ) { Price { Ohlc { Close } } Volume { Base Quote Usd } Supply { TotalSupply FullyDilutedValuationUsd MarketCap } } } } ``` [Try SOL/USDC pair data ➤](https://ide.bitquery.io/SOL-USDC-pair-trading-data) **Use Case**: Market-making, arbitrage detection, DEX-specific strategies. ## Production Resources & Documentation ### **Implementation Guides** - **[Crypto Price API Documentation](/docs/trading/crypto-price-api/)** - Complete API reference with live examples - **[DEX Trade Filtering Guide](/docs/usecases/how-to-filter-anomaly-prices/)** - How to handle raw data anomalies - **[OHLC Candlestick API](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api/)** - Ready-to-chart data with [live Bitcoin OHLC example ➤](https://ide.bitquery.io/bitcoin-currency-price-stream) - **[DEXTradeByTokens API](/docs/cubes/dextradesbyTokens/)** - Raw trade data for comparison ### **Real-Time Streaming** - **GraphQL Endpoint**: `https://streaming.bitquery.io/graphql` ([Try in IDE ➤](https://ide.bitquery.io/?endpoint=https://streaming.bitquery.io/graphql)) - **Kafka Topic**: `trading.prices` ([Schema & SDKs ➤](https://github.com/bitquery/streaming_protobuf/tree/main/market)) - **WebSocket Subscriptions**: Convert any query to `subscription` for real-time data ([WebSocket Guide ➤](/docs/subscriptions/websockets/)) ## **Proven Production Benefits** Based on real trading platform implementations ([see live comparison ➤](https://ide.bitquery.io/raw-vs-aggregated-price-comparison)): - **81% reduction** in false triggers from price spike dampening - **Eliminated retry logic complexity** through built-in price stability - **Universal pool coverage** removing need for manual liquidity selection - **Improved execution quality** leading to higher user confidence - **Reduced system latency** by eliminating pre-execution quote validation needs ## **Technical Advantages Over Raw Price Feeds** - **Time-based aggregation**: 1-second to 1-hour intervals proven in production - **Volume thresholds**: $1K to $1M+ filtering ensuring significant trading activity - **Multi-pool aggregation**: Comprehensive price discovery across all major DEXs - **Real-time USD pricing**: Built-in conversion eliminating additional API calls - **Bad trade filtering**: Automatic removal of outliers and routing anomalies [Explore all technical features ➤](/docs/trading/crypto-price-api/#key-features-of-these-apis) Ready to implement our crypto price API for limit orders in your trading system? **Get Started with Our Limit Order Price API:** - [Try live examples in our IDE ➤](https://ide.bitquery.io/?query_name=crypto-price-examples) - [Read the complete API documentation ➤](/docs/trading/crypto-price-api/) - [Download SDKs and schemas ➤](https://github.com/bitquery/streaming_protobuf/tree/main/market) - [Join our community ➤](https://t.me/Bloxy_info) Join trading platforms that have already upgraded from unreliable raw data to our enterprise-grade limit order price API for reliable price for limit order execution. --- ## Mempool Transaction Fee Explorer URL: https://docs.bitquery.io/docs/usecases/mempool-transaction-fee/ Build Mempool Transaction Fee Explorer: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Mempool Transaction Fee Explorer In the world of cryptocurrency, understanding transaction fees and their dynamics is crucial for traders, investors, and blockchain enthusiasts alike. Ethereum, one of the leading blockchain platforms, relies on a concept known as the "Mempool" to manage pending transactions and determine their associated fees. The [Mempool API](https://bitquery.io/products/mempool-api) provides developers with a convenient interface to access real-time data from the Ethereum Mempool. By using the Mempool API, developers can get details about transactions, monitor how transaction fee changes, and learn more about what's happening on the Ethereum network. In this blog, we'll explore the concept of the Mempool, delve into the functionalities of the Mempool API, and embark on a practical journey of building a transaction fee analysis dashboard using Python, Streamlit, and the Mempool API. ## Building a Mempool Transaction Fee Analysis Dashboard with Streamlit In this tutorial, we'll focus on a practical use case of utilizing the Mempool API to build a transaction fee analysis dashboard. This dashboard will offer users a comprehensive view of recent Ethereum transactions, including details such as transaction hashes, block numbers, sender and receiver addresses, transaction costs, and associated fees. Moreover, we'll visualize key metrics such as burnt fees and priority fees per gas over time, allowing users to track fee trends and make informed decisions regarding transaction prioritization and fee optimization. By the end of this tutorial, readers will have a solid understanding of how to interact with the Mempool API, fetch real-time transaction data, process it using Python libraries like Pandas and Matplotlib, and present it in an interactive dashboard using Streamlit. ### Step 1: Importing Libraries and Defining Constants ```python ``` In this section, we import necessary libraries: streamlit, requests, pandas, and matplotlib. These libraries will help us build our Streamlit web application and handle data visualization. ### Step 2: Defining Constants and GraphQL Query ```python ACCESS_TOKEN = "Your_v2_Access_Token" MEMPOOL_API_URL = "https://streaming.bitquery.io/graphql" GRAPHQL_QUERY = """ { EVM(mempool: true) { Transactions(limit: {count: 200}) { Block { Time Number } Transaction { Hash Cost To From } Fee { Burnt SenderFee PriorityFeePerGas MinerReward GasRefund EffectiveGasPrice Savings } } } } """ ``` Here, we define constants such as ACCESS_TOKEN for the Mempool API and MEMPOOL_API_URL for its endpoint. We also define the GraphQL query (GRAPHQL_QUERY) that will retrieve transaction data from the Mempool API. ### Step 3: Function to Fetch Data from the Mempool API ```python def fetch_data_from_mempool_api(): headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {ACCESS_TOKEN}' } response = requests.post(MEMPOOL_API_URL, json={"query": GRAPHQL_QUERY}, headers=headers) data = response.json() return data['data']['EVM']['Transactions'] ``` This function fetch_data_from_mempool_api() sends a POST request to the Mempool API endpoint with the GraphQL query and access token. It then returns the transaction data obtained from the response. ### Step 4: Creating the Streamlit App ```python def main(): ``` #### Set page title ``` st.title("Transaction Fee Analysis Dashboard") ``` ## Fetch data from Mempool API ```python try: transactions_data = fetch_data_from_mempool_api() except Exception as e: st.error(f"Error fetching data from Mempool API: {str(e)}") return ``` Here, we define the main function main() where we set the page title for our Streamlit app and attempt to fetch data from the Mempool API. We handle any exceptions that may occur during data retrieval. ### Step 5: Processing and Displaying Data #### Initialize lists to store data ```python block_time = [] block_number = [] transaction_hash = [] transaction_cost = [] transaction_to = [] transaction_from = [] fee_burnt = [] fee_sender = [] fee_priority = [] fee_miner = [] fee_refund = [] fee_effective_gas = [] fee_savings = [] ``` #### Extract data from JSON and store in lists ```python for transaction in transactions_data: block_time.append(transaction['Block']['Time']) block_number.append(transaction['Block']['Number']) transaction_hash.append(transaction['Transaction']['Hash']) transaction_cost.append(transaction['Transaction']['Cost']) transaction_to.append(transaction['Transaction']['To']) transaction_from.append(transaction['Transaction']['From']) fee_burnt.append(transaction['Fee']['Burnt']) fee_sender.append(transaction['Fee']['SenderFee']) fee_priority.append(transaction['Fee']['PriorityFeePerGas']) fee_miner.append(transaction['Fee']['MinerReward']) fee_refund.append(transaction['Fee']['GasRefund']) fee_effective_gas.append(transaction['Fee']['EffectiveGasPrice']) fee_savings.append(transaction['Fee']['Savings']) ``` #### Create DataFrame ```python df = pd.DataFrame({ 'Block Time': block_time, 'Block Number': block_number, 'Transaction Hash': transaction_hash, 'Transaction Cost': transaction_cost, 'Transaction To': transaction_to, 'Transaction From': transaction_from, 'Fee Burnt': fee_burnt, 'Fee Sender': fee_sender, 'Fee Priority': fee_priority, 'Fee Miner': fee_miner, 'Fee Refund': fee_refund, 'Fee Effective Gas': fee_effective_gas, 'Fee Savings': fee_savings }) ``` #### Display DataFrame ```python st.subheader("Latest Transactions and Fee Details") st.write(df) ``` In this section, we process the fetched transaction data by extracting relevant information and storing it in lists. Then, we create a Pandas DataFrame (df) to organize the data and display it using Streamlit. ### Step 6: Visualizing Data #### Initialize lists for fee data ```python timestamps = [] burnt_fees = [] priority_fees = [] ``` #### Extract fee data from transactions ```python for transaction in transactions_data: timestamps.append(pd.to_datetime(transaction['Block']['Time'])) burnt_fees.append(float(transaction['Fee']['Burnt'])) priority_fees.append(float(transaction['Fee']['PriorityFeePerGas'])) ``` #### Create DataFrame ```python df = pd.DataFrame({ 'Timestamp': timestamps, 'Burnt Fee': burnt_fees, 'Priority Fee': priority_fees }) ``` #### Set index to Timestamp for time-based plotting ``` df.set_index('Timestamp', inplace=True) ``` #### Display DataFrame ``` st.subheader("Latest Transactions and Fee Details") st.write(df) ``` #### Plot bar chart for burnt fee over time ```python st.subheader("Burnt Fee over Time") plt.figure(figsize=(10, 6)) plt.scatter(df.index, df['Burnt Fee'], color='blue') plt.xlabel('Timestamp') plt.ylabel('Burnt Fee') plt.xticks(rotation=45) st.pyplot(plt) ``` #### Plot bar chart for priority fee per gas over time ```python st.subheader("Priority Fee per Gas over Time") plt.figure(figsize=(10, 6)) plt.scatter(df.index, df['Priority Fee'], color='red') plt.xlabel('Timestamp') plt.ylabel('Priority Fee per Gas') plt.xticks(rotation=45) st.pyplot(plt) ``` Here, we prepare fee data for visualization by extracting timestamps and fee values from transactions. We create another DataFrame (df) for visualization purposes, set the index to timestamps, and display it using Streamlit. Additionally, we plot bar charts for burnt fee and priority fee per gas over time using Matplotlib. ### Step 7: Running the Streamlit App ``` if **name** == "**main**": main() ``` Finally, we run the Streamlit app by calling the main() function if the script is executed directly. This is the entry point of our application. ## Output When you run the Streamlit app generated by the code, here's what you can expect to see: - Latest Transactions and Fee Details: You will see a table displaying details of the latest transactions fetched from the Mempool API. The table includes information such as block time, block number, transaction hash, transaction cost, sender, receiver, and various fee details. ![Mempool transaction fee analysis, step 1](/img/mempool-transaction-fee/image-001.png) - Burnt Fee over Time: A scatter plot will show the burnt fee (the fee consumed by the network) over time. The x-axis represents the timestamp of transactions, and the y-axis represents the burnt fee value. ![Mempool transaction fee analysis, step 2](/img/mempool-transaction-fee/image-002.png) - Priority Fee per Gas over Time: Another scatter plot will display the priority fee per gas (the fee paid by the user for faster confirmation) over time. Similarly, the x-axis represents the timestamp, and the y-axis represents the priority fee per gas. ![Mempool transaction fee analysis, step 3](/img/mempool-transaction-fee/image-003.png) When interacting with the dashboard, users can analyze transaction data, identify patterns, and make informed decisions regarding transaction prioritization and fee management. Overall, the dashboard offers a user-friendly interface for exploring and understanding Ethereum transaction fees in real-time, facilitating better decision-making in the realm of cryptocurrency transactions. --- ## Migrate from Bitquery API v1 to v2 URL: https://docs.bitquery.io/docs/API-Blog/migrate-v1-v2/ Migrate Bitquery GraphQL apps from API v1 to v2 covering schema changes, streaming datasets, authentication updates, and code examples. # Migrating from API v1 to v2 ## Overview V2 APIs are designed to provide real-time blockchain data without any delay. It combines both real-time and historical data. You can read more on the differences [here](https://docs.bitquery.io/v1/docs/graphql-ide/v1-and-v2) Below, you'll find key changes and instructions on how to adapt your existing v1 queries to the v2 format. ## Authentication in v1 vs v2 One of the major differences between v1 and v2 is the way API is authenticated. In v1, you use a API-KEY to authenticate your requests to `graphql.bitquery.io`. And in v2, you use OAuth token mentioned as `Bearer ory_...yourtoken` and authenticate your requests to `streaming.bitquery.io/graphql`. Read more on how to generate token [here](/docs/authorization/how-to-generate/). ## Changes in Network Specification - **v1:** Specified using a generic identifier within a function, e.g., `ethereum(network: ethereum)`. - **v2:** Now requires a more specific `network` identifier and inclusion of a `dataset`. Example: `EVM(network: eth, dataset: combined)`. **Example Conversion:** - **v1 Query:** ```graphql query MyQuery { ethereum(network: ethereum) { blocks { count } } } ``` - **v2 Query:** ```graphql query { EVM(network: eth, dataset: combined) { Blocks { count } } } ``` ## Schema and Data Access The v2 API maintains a similar schema structure but integrates new data cubes such as `balanceUpdates`, `tokenHolders`, and `DexTradeByTokens`. The ability to click-select in the schema builder is still available in v2, facilitating easier transition and query building. If you're new to v2, check more examples on: [TokenHolder APIs](/docs/blockchain/Ethereum/token-holders/token-holder-api/) [balanceUpdates](/docs/blockchain/Ethereum/balances/balance-api/) [DexTradeByTokens](/docs/blockchain/Ethereum/dextrades/token-trades-apis/) ## Smart Contract Interactions - **v1:** Data is accessed through `smartContractCalls` and `smartContractEvents`. - **v2:** Simplified to `Calls` and `Events`. ## Handling Arguments and Values One of the major differences in v2 is how arguments and their values are handled and accessed. - **v1:** Arguments and values are accessed using filters based on the argument name. ```graphql token0: any(of: argument_value, argument: { is: "token0" }) ``` - **v2:** Arguments are explicitly defined by data type, providing more structured access and clearer query definitions. ```graphql Arguments { Name Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } ``` ## Aggregation : From v1 to v2 Let's take this query in v1, where we get total number of unique currencies traded on Ethereum on a particular day. The `count` and `sum` aggregation is available in both v1 and v2. ```graphql query MyQuery { ethereum(network: ethereum) { dexTrades(date: {is: "2024-01-01"}) { Unique_tokens_bought: count(uniq: buy_currency) Unique_tokens_sold: count(uniq: sell_currency) } } } ``` - **Date Filtering**: Instead of a separate `date` field, v2 uses `Block.Date` for date filtering. - **Field Mapping**: The `buy_currency` and `sell_currency` fields in v1 are mapped to `Trade_Buy_Currency_SmartContract` and `Trade_Sell_Currency_SmartContract` respectively in v2. ```graphql query MyQuery { EVM(network: eth, dataset: combined) { DEXTrades(where: {Block: {Date: {is: "2024-01-01"}}}) { Unique_tokens_bought:count(distinct: Trade_Buy_Currency_SmartContract) Unique_tokens_sold:count(distinct:Trade_Sell_Currency_SmartContract) } } } ``` ## Decimals & Precision In API v1, the decimal precision was low at the GraphQL layer. While the data was saved accurately with all decimals, it would lose precision when processed through GraphQL. To avoid this issue, in API v2, values are now represented as strings, allowing for exact precision up to 18 decimal places without any loss of accuracy. ## Migrating Complex Queries from v1 to v2 Let's take the below query which fetches the latest token details including USD values. ```graphql { ethereum(network: ethereum) { dexTrades( options: {desc: ["block.height", "tradeIndex"], limit: 1, offset: 0} buyCurrency: {is: "0xdac17f958d2ee523a2206206994597c13d831ec7"} ) { block { timestamp { time(format: "%Y-%m-%d %H:%M:%S") } height } tradeIndex protocol exchange { fullName } buyAmount buyCurrency { symbol } buy_amount_usd: buyAmount(in: USD) sellAmount sellCurrency { symbol } sell_amount_usd: sellAmount(in: USD) priceInUSD: expression(get: "buy_amount_usd / buyAmount") } } } ``` Now to convert this to v2, let's first tackle the filters. ``` options: {desc: ["block.height", "tradeIndex"], limit: 1, offset: 0} buyCurrency: {is: "0xdac17f958d2ee523a2206206994597c13d831ec7"} ``` In v2, the same filters would be: ``` limit: {count: 1} orderBy: {descending: Block_Number} where: {Trade: {Buy: {Currency: {SmartContract: {is: "0xdac17f958d2ee523a2206206994597c13d831ec7"}}}}} ``` There is no separate filter called `options` **Notice we use `.` in v1 to access inner fields while we use `_` in v2.** Next, we select the fields in the response ```graphql { Block { Number Time } Transaction { From To Hash } Trade { Buy { Amount AmountInUSD Buyer Currency { Name Symbol SmartContract } Seller Price PriceInUSD } Sell { Amount AmountInUSD Buyer Currency { Name SmartContract Symbol } Seller Price PriceInUSD } } } ``` In v1, we had fields labelled as `buyCurrency`, `sellAmount` and so on but in v2 we have nested schema, where we choose ``` Buy{ Currency {} } ``` The nested schema in v2 requires accessing fields through specific paths, e.g., `Trade.Buy.Currency.SmartContract`. ## Where do I find my Bitquery API v1 key? {#where-do-i-find-my-bitquery-api-v1-key} To find your Bitquery API v1 key, log in to your [Bitquery account](https://account.bitquery.io/), navigate to [Applications](https://account.bitquery.io/user/api_v2/applications), and create a new application if needed. Then, visit the [Access Tokens](https://account.bitquery.io/user/api_v2/access_tokens) page to generate a new access token. This token serves as your API key for authentication. When creating a token, be sure to set an appropriate lifespan to avoid unnecessary interruptions or the need for frequent regeneration. ## V1 vs V2 : Data Points ![Bitquery v1 to v2 API comparison](/img/v1v2.png) --- ## Monitoring Solana at Scale: Managing 100s of Addresses URL: https://docs.bitquery.io/docs/usecases/monitoring-solana-at-scale-managing-hundreds-of-addresses/ Build Monitoring Solana at Scale: Managing 100s of Addresses: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Monitoring Solana at Scale: Managing 100s of Addresses This guide demonstrates how to monitor real-time token transfers and DEX trades for hundreds of blockchain addresses efficiently using WebSockets and Streamlit. Learn to dynamically track and display key transfer and trade details in a real-time dashboard. ## Monitoring 100s of Addresses on Solana Transfers in Real-Time This section walks through how to track real-time Solana token transfers using a WebSocket connection and display sender, receiver, amount, and time in a dynamic Streamlit dashboard. ### 1. Setting Up the Environment First, ensure that you have the necessary dependencies installed: ```bash pip install streamlit pandas gql ``` Then, import the following libraries: ```python from gql import Client, gql from gql.transport.websockets import WebsocketsTransport ``` Set your Bitquery API token: ```python TOKEN = "your_bitquery_api_token" ``` ### 2. Preparing the List of Addresses Define the list of Solana addresses you want to monitor: ```python addresses = [ "7Ppgch9d4XRAygVNJP4bDkc7V6htYXGfghX4zzG9r4cH", "G6xptnrkj4bxg9H9ZyPzmAnNsGghSxZ7oBCL1KNKJUza", # Add more addresses here... ] ``` ### 3. Creating the Transfer Subscription Query The following function dynamically generates a GraphQL query to monitor token transfers for the specified addresses: ```python def create_transfer_subscription_query(): address_list = ', '.join(f'"{addr}"' for addr in addresses) return gql(f""" subscription {{ Solana {{ Transfers( where: {{ Transfer: {{ Sender: {{ Address: {{ in: [ {address_list} ] }} }} }} }} ) {{ Transfer {{ Amount AmountInUSD Receiver {{ Address }} Sender {{ Address }} Currency {{ Symbol }} }} }} }} }} """) ``` ### 4. Monitoring Real-Time Transfers The `monitor_transfers` function sets up a WebSocket connection and subscribes to real-time transfer data. The results are displayed in real-time using a Streamlit dashboard. ```python async def monitor_transfers(): transport = WebsocketsTransport( url=f"wss://streaming.bitquery.io/graphql?token={TOKEN}", headers={"Sec-WebSocket-Protocol": "graphql-transport-ws"} ) await transport.connect() print("Connected to WebSocket for transfer monitoring.") transfers_df = pd.DataFrame() # Set up Streamlit display for transfers st.title("Solana Transfer Monitoring Dashboard") table = st.empty() # Initialize an empty Streamlit table try: query = create_transfer_subscription_query() async for result in transport.subscribe(query): if result.data: print("Transfer Data:", result.data) transfer_data = pd.json_normalize(result.data['Solana']['Transfers']) # Select only the relevant fields display_data = transfer_data[[ 'Transfer.Sender.Address', 'Transfer.Receiver.Address', 'Transfer.Amount', 'Transfer.AmountInUSD', 'Transfer.Currency.Symbol' ]] transfers_df = pd.concat([transfers_df, display_data], ignore_index=True) # Update the Streamlit table with the new data with st.spinner('Updating data...'): table.dataframe(transfers_df, use_container_width=True) except Exception as e: print("Error during transfer monitoring:", e) finally: await transport.close() ``` ### 5. Running the Transfer Monitoring Dashboard To run the real-time monitoring dashboard, use the following command: ```bash streamlit run your_script.py ``` The dashboard will open in your browser, where you will see real-time updates of Solana transfers for the specified addresses. ### Output This is how it will look. ## Scaling the Monitoring - Initial tests were conducted with 50 addresses, yielding fast response times. - Increased the number of addresses to 150, and the response speed remained consistent without any noticeable delay. - Further tested with over 500 addresses, and again, no visible latency was observed. - The response time remained relatively quick even as the number of addresses increased, demonstrating the scalability and efficiency of the system. ## Monitoring Solana DEX Trades for 100s of Addresses This section explains how to monitor real-time DEXTrades on Solana, displaying buy/sell data, trade prices, and other relevant trade information for hundreds of addresses. ### 1. Setting Up the Environment Ensure that you have the necessary libraries installed: ```bash pip install streamlit pandas gql ``` Import the necessary libraries: ```python from gql import Client, gql from gql.transport.websockets import WebsocketsTransport ``` Set your Bitquery API token: ```python TOKEN = "your_bitquery_api_token" ``` ### 2. Preparing the List of Addresses Define the list of Solana addresses you want to monitor for DEX trades: ```python addresses = [ "5qrvgpvr55Eo7c5bBcwopdiQ6TpvceiRm42yjHTbtDvc", "FpCMFDFGYotvufJ7HrFHsWEiiQCGbkLCtwHiDnh7o28Q", # Add more addresses here... ] ``` ### 3. Creating the DEXTrades Subscription Query This function dynamically generates a GraphQL query to monitor DEX trades involving the specified addresses: ```python def create_dex_trades_subscription_query(): address_list = ', '.join(f'"{addr}"' for addr in addresses) return gql(f""" subscription {{ Solana {{ DEXTrades(where: {{Trade: {{Buy: {{Account: {{Address: {{in: [{address_list}]}}}}}}}}}}) {{ Trade {{ Dex {{ ProgramAddress ProtocolName }} Buy {{ Amount Account {{ Address }} Currency {{ Symbol Name }} Price }} Sell {{ Account {{ Address }} Price }} }} }} }} }} """) ``` ### 4. Monitoring Real-Time DEXTrades The following function establishes a WebSocket connection and subscribes to real-time DEXTrades, displaying the results in a Streamlit dashboard: ```python async def monitor_dex_trades(): transport = WebsocketsTransport( url=f"wss://streaming.bitquery.io/graphql?token={TOKEN}", headers={"Sec-WebSocket-Protocol": "graphql-transport-ws"}, ) await transport.connect() print("Connected to WebSocket for DEXTrades monitoring.") dex_trades_df = pd.DataFrame() # Set up Streamlit display for DEXTrades st.title("Solana DEXTrades Monitoring Dashboard") table = st.empty() # Initialize an empty Streamlit table try: query = create_dex_trades_subscription_query() async for result in transport.subscribe(query): if result.data: print("DEXTrade Data:", result.data) dex_trade_data = pd.json_normalize(result.data["Solana"]["DEXTrades"]) # Select only the relevant fields display_data = dex_trade_data[[ 'Trade.Buy.Currency.Name', 'Trade.Buy.Currency.Symbol', 'Trade.Buy.Account.Address', 'Trade.Buy.Price', 'Trade.Sell.Account.Address', 'Trade.Sell.Price', 'Trade.Dex.ProgramAddress', 'Trade.Dex.ProtocolName' ]] dex_trades_df = pd.concat([dex_trades_df, display_data], ignore_index=True) # Update the Streamlit table with the new data with st.spinner("Updating data..."): table.dataframe(dex_trades_df, use_container_width=True) except Exception as e: print("Error during DEXTrades monitoring:", e) finally: await transport.close() ``` ### 5. Running the DEXTrades Monitoring Dashboard To run the real-time DEXTrades monitoring dashboard, use the following command: ```bash streamlit run your_script.py ``` The dashboard will open in your browser, showing real-time updates for DEXTrades involving the specified addresses. --- ### Conclusion This guide demonstrates how to efficiently monitor 100s of Solana addresses in real time using WebSockets and Streamlit. You can extend this code to handle additional blockchain events or customize the displayed data based on your specific needs. --- ## Monitoring the Solana Blockchain in Real Time URL: https://docs.bitquery.io/docs/usecases/monitoring-solana-blockchain-real-time-tutorial/ Build Monitoring the Solana Blockchain in Real Time: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Monitoring the Solana Blockchain in Real Time - Easy Tutorial Monitoring blockchain activities is crucial for developers, investors, and regulatory bodies. [Bitquery](https://bitquery.io) offers the infrastructure to monitor blockchain activities effectively. In this tutorial, we'll use [Bitquery Solana real-time data](https://bitquery.io/blockchains/solana-blockchain-api) and Python to build a real-time Solana DEX monitoring dashboard. We will create a dashboard that monitors **Solana DEX trades** and **Raydium DEX trades**. Here is a step-by-step guide to build this system. The code can be found [here on GitHub](https://gist.github.com/divyasshree-BQ/84bc875bf1c3c3e9088653d5c9b5d7eb). ## Prerequisites Ensure you have Python installed and pip configured correctly. Install the required libraries: ```bash pip install asyncio pandas streamlit gql websockets ``` ## Step 1: Import Necessary Libraries The first step is to import all necessary libraries that the script requires. These include libraries for asynchronous programming, data manipulation, creating the web interface, and handling GraphQL subscriptions over WebSockets. ```python from gql import Client, gql from gql.transport.websockets import WebsocketsTransport ``` ## Step 2: Set Up the WebSocket Connection To generate a token to run the subscription, check [the guide here](/docs/authorization/how-to-generate/). Define the asynchronous function `run_subscription` to establish and manage the WebSocket connection with Bitquery. This function will handle subscribing to the real-time data streams. ```python async def run_subscription(): # Setup WebSocket connection transport = WebsocketsTransport( url="wss://streaming.bitquery.io/graphql?token=", headers={"Sec-WebSocket-Protocol": "graphql-ws"} ) # Establish the connection await transport.connect() print("Connected to WebSocket") ``` ## Step 3: Handle User Input Use Streamlit's sidebar to allow users to choose between different datasets ('General' or 'Raydium'): ```python page = st.sidebar.radio("Select Page", ["General", "Raydium"]) general_df = pd.DataFrame() raydium_df = pd.DataFrame() ``` ## Step 4: Subscribe to GraphQL Queries Based on user selection, subscribe to the respective GraphQL queries to receive real-time data. Process the incoming data and update the dashboard accordingly. ## All DEX Trades: ```python if page == "General": st.subheader("General Table") table = st.table(general_df) while True: async for result in transport.subscribe( gql(""" subscription { Solana { General: DEXTradeByTokens { Block { Time } Trade { Amount Price Currency { Symbol Name } Side { Amount Currency { Symbol Name MetadataAddress }} Dex { ProgramAddress ProtocolFamily ProtocolName } Market { MarketAddress } Order { LimitAmount LimitPrice OrderId } PriceInUSD } } } } """) ): if result.data: new_data = pd.json_normalize(result.data['Solana']['General']) general_df = pd.concat([general_df, new_data], ignore_index=True) with st.spinner('Updating data...'): table.table(general_df) ``` ## Raydium DEX Trades: ```python elif page == "Raydium": st.subheader("Raydium Table") table = st.table(raydium_df) while True: async for result in transport.subscribe( gql(""" subscription { Solana { Raydium: DEXTrades( where: {Trade: {Dex: {ProgramAddress: {is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"}}}} ) { Trade { Dex { ProgramAddress ProtocolName } Buy { Account { Address } Amount Currency { Symbol Name } PriceInUSD } Sell { Account { Address } Amount Currency { Symbol Name } PriceInUSD } } Block { Time Height } Transaction { Signature } } } } """) ): if result.data: new_data = pd.json_normalize(result.data['Solana']['Raydium']) raydium_df = pd.concat([raydium_df, new_data], ignore_index=True) with st.spinner('Updating data...'): table.table(raydium_df) ``` ## Step 5: Clean Up and Close Connection Once the subscription ends or the user closes the dashboard, ensure to properly close the WebSocket connection to free up resources. ```python finally: await transport.close() ``` ## Step 6: Run the Streamlit App Define the main entry point for your Streamlit application: ```python def main(): st.title("Solana DEX General & Raydium Data Dashboard") asyncio.run(run_subscription()) if __name__ == "__main__": main() ``` ## The Final Result --- ## NFT Analytics Dashboard - Tutorial URL: https://docs.bitquery.io/docs/usecases/nft-analytics/ Build NFT Analytics Dashboard - Tutorial: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # NFT Analytics Dashboard - Tutorial ### Marketplace Analysis Bitquery's queries can help NFT marketplace builders analyze the performance of different NFTs on various blockchain networks. By providing real-time data on transaction volume, and other key metrics, Bitquery can help builders optimize their marketplace's offerings and improve trading conditions for users. Everything used here comes from the [NFT API](https://bitquery.io/products/nft-api) — trades, metadata, holders and floor prices across 40+ chains. ## Tutorial This is a tutorial to build a NFT Dashboard using Python code that connects to the Bitquery API and retrieves data for a particular NFT on the Ethereum network. The code then displays the data on a user-friendly interface built using Python and Streamlit. ## Required Libraries The code uses the following libraries: streamlit: A Python library for building web apps and visualizations http.client: A Python library for making HTTP requests json: A Python library for working with JSON data pandas: A Python library for data manipulation and analysis ## Step by Step Code Implementation ### Importing the Required Libraries The first step in the code is to import the required libraries using the import statement: ```python ``` ### Establishing Connection with the Bitquery API Next, the code connects to the Bitquery API using the http.client library and retrieves data on NFT transactions for a specific contract on the Ethereum network using a GraphQL query. The query is passed as a JSON payload to the request() method, along with the necessary headers and API key. ```python conn = http.client.HTTPSConnection("streaming.bitquery.io") payload = json.dumps({ "query": "{\n EVM(dataset: archive) {\n DEXTrades(\n where: {Trade: {Dex: {ProtocolFamily: {is: \"OpenSea\"}}, Buy: {Currency: {SmartContract: {is: \"0x322e2741c792c1f2666d159bcc6d3a816f98d954\"}}}}}\n ) {\n Count_NFTS_bought: sum(of: Trade_Buy_Amount)\n }\n }\n}\n", "variables": "{}" }) headers = { 'Content-Type': 'application/json', "Authorization": "Bearer your_access_token_here", } conn.request("POST", "/graphql", payload, headers) res = conn.getresponse() data = res.read() resp= json.loads( data.decode("utf-8")) count_nfts_bought = resp['data']['EVM']['DEXTrades'][0]['Count_NFTS_bought'] ``` The code retrieves the count of NFTs bought from the response data and stores it in the count_nfts_bought variable. ### Displaying the Metric The code then displays the retrieved data in a Streamlit dashboard using the streamlit library. The dashboard includes a title, a header, a metric, a table, and a line chart. ```python st.title ("NFT Dashboard") st.header("Punk Evil Rabbit NFT") st.metric("Count of Punk Evil Rabbit NFTS Bought",count_nfts_bought) ``` The title() and header() methods are used to display the title and header of the dashboard, respectively. The metric() method is used to display the count of NFTs bought as a metric. ### Adding a Table This code snippet retrieves the latest DEX trades for a specific NFT token from the Ethereum blockchain using the Bitquery DEX Trades API, and displays them in a data table using the streamlit library. ```python payload_table = json.dumps({ "query": "{\n EVM(dataset: archive, network: eth) {\n buyside: DEXTrades(\n limit: {count: 10}\n orderBy: {descending: Block_Time}\n where: {Trade: {Buy: {Currency: {SmartContract: {is: \"0x322e2741c792c1f2666d159bcc6d3a816f98d954\"}}}}}\n ) {\n Block {\n Number\n Time\n }\n Transaction {\n From\n To\n Hash\n }\n Trade {\n Buy {\n Amount\n Buyer\n Currency {\n Name\n Symbol\n SmartContract\n }\n Seller\n Price\n }\n Sell {\n Amount\n Buyer\n Currency {\n Name\n SmartContract\n Symbol\n }\n Seller\n Price\n }\n }\n }\n sellside: DEXTrades(\n limit: {count: 10}\n orderBy: {descending: Block_Time}\n where: {Trade: {Buy: {Currency: {SmartContract: {is: \"0x322e2741c792c1f2666d159bcc6d3a816f98d954\"}}}}}\n ) {\n Block {\n Number\n Time\n }\n Transaction {\n From\n To\n Hash\n }\n Trade {\n Buy {\n Amount\n Buyer\n Currency {\n Name\n Symbol\n SmartContract\n }\n Seller\n Price\n }\n Sell {\n Amount\n Buyer\n Currency {\n Name\n SmartContract\n Symbol\n }\n Seller\n Price\n }\n }\n }\n }\n}\n", "variables": "{}" }) conn.request("POST", "/graphql", payload_table, headers) res1 = conn.getresponse() data1 = res1.read() resp1= json.loads( data1.decode("utf-8")) st.subheader("Latest DEX Trades") data_table= resp1['data']['EVM']['buyside'] df = pd.json_normalize(data_table) st.dataframe(df) ``` ### Adding a Chart The chart section of the code creates a line chart using the streamlit library. The chart displays the number of NFTs bought on a daily basis on the OpenSea protocol on the Ethereum blockchain. ```python ## chart payload3 = json.dumps({ "query": "{\n EVM(dataset: archive) {\n DEXTrades(\n where: {Trade: {Dex: {ProtocolFamily: {is: \"OpenSea\"}}, Buy: {Currency: {SmartContract: {is: \"0x322e2741c792c1f2666d159bcc6d3a816f98d954\"}}}}}\n ) {\n Count_NFTS_bought: sum(of: Trade_Buy_Amount)\n Block {\n Date\n }\n }\n }\n}\n", "variables": "{}" }) conn.request("POST", "/graphql", payload3, headers) res3 = conn.getresponse() data3 = res3.read() chart_data=json.loads(data3)['data']['EVM']['DEXTrades'] df_chart = pd.json_normalize(chart_data) df_chart.columns = ['Count_NFTS_bought', 'Block_Date'] # Convert the 'Count_NFTS_bought' column to integer data type df_chart['Count_NFTS_bought'] = df_chart['Count_NFTS_bought'].astype(int) df_chart['Block_Date'] = pd.to_datetime(df_chart['Block_Date']) st.subheader('Daily Metrics') st.line_chart(df_chart,x='Block_Date',y='Count_NFTS_bought') ``` #### Here's how it looks If you want to build up query from scratch you are welcome or you can use the [premade examples](https://ide.bitquery.io/explore/All%20queries) as well. ## Setting Up Subscriptions Lastly, we also have the the [“subscribe”](https://community.bitquery.io/t/how-to-subscribe-to-real-time-data-stream-using-bitquery-api-and-python-graphql-client/1431?u=divya) feature of the dApp. These functions are important as they allow us to continuously update the dApp for our users in real-time whenever a transaction of digital collectibles occurs in the marketplace. --- ## NFTs Tracking Across Chains URL: https://docs.bitquery.io/docs/examples/cross-chain/cross-chain-api/ NFT balances across chains: one GraphQL query with aliases returns a wallet's NFT collections and counts on BNB Chain, Ethereum, Arbitrum and Optimism. # NFTs Tracking Across Chains Check a wallet's NFT holdings on several chains in one call. GraphQL aliases let one request query several networks at once. The query below returns the NFT collections held by `0xaba7161a7fb69c88e16ed9f455ce62b791ee4d03` on BNB Chain, Ethereum, Arbitrum and Optimism, with the number of NFTs held in each collection. Replace it with the wallet you need. It uses the [`Balances` cube](/docs/cubes/balances-cube/), which returns the current balance per collection directly. `Balances` reports how many NFTs an address holds in a collection, not the individual token IDs. For token IDs, use the [NFT ownership API](/docs/blockchain/Ethereum/nft/nft-ownership-api/). ```graphql query MyQuery { binance: EVM(network: bsc, dataset: combined) { Balances( limit: { count: 10 } orderBy: { descending: Balance_Amount } where: { Balance: { Address: { is: "0xaba7161a7fb69c88e16ed9f455ce62b791ee4d03" } } Currency: { Fungible: false } } ) { Currency { Fungible Symbol SmartContract Name } Balance { Address Amount(selectWhere: { gt: "0" }) } } } eth: EVM(network: eth, dataset: combined) { Balances( limit: { count: 10 } orderBy: { descending: Balance_Amount } where: { Balance: { Address: { is: "0xaba7161a7fb69c88e16ed9f455ce62b791ee4d03" } } Currency: { Fungible: false } } ) { Currency { Fungible Symbol SmartContract Name } Balance { Address Amount(selectWhere: { gt: "0" }) } } } arbitrum: EVM(network: arbitrum, dataset: combined) { Balances( limit: { count: 10 } orderBy: { descending: Balance_Amount } where: { Balance: { Address: { is: "0xaba7161a7fb69c88e16ed9f455ce62b791ee4d03" } } Currency: { Fungible: false } } ) { Currency { Fungible Symbol SmartContract Name } Balance { Address Amount(selectWhere: { gt: "0" }) } } } optimism: EVM(network: optimism, dataset: combined) { Balances( limit: { count: 10 } orderBy: { descending: Balance_Amount } where: { Balance: { Address: { is: "0xaba7161a7fb69c88e16ed9f455ce62b791ee4d03" } } Currency: { Fungible: false } } ) { Currency { Fungible Symbol SmartContract Name } Balance { Address Amount(selectWhere: { gt: "0" }) } } } } ``` --- ## OHLCV Candle Data Complete Guide URL: https://docs.bitquery.io/docs/usecases/ohlcv-complete-guide/ Learn how to build OHLCV candles from Bitquery DEX trade data, including intervals, aggregations, gaps, and chart-ready GraphQL examples. # Complete Guide to Building the Perfect OHLCV Data Using Bitquery APIs This page shows how to get OHLCV (Open, High, Low, Close, Volume) candlestick data, also called K-Line data, across different blockchain networks using Bitquery APIs. We’ll also explore how to filter out bot trades, outliers, and abnormally high or low prices to ensure accurate OHLC calculations. ## Recommended: pre-aggregated OHLC via the Crypto Price API (real-time + last ~30 days) This page builds candles **from raw chain-level trades** (`DEXTradeByTokens`), the right tool for **history older than ~30 days** or **custom intervals**. For anything real-time or within the last ~30 days, use the [**Crypto Price API**](/docs/trading/crypto-price-api/) instead: **true pre-aggregated OHLC down to 1-second intervals**, including the real open price that in-query aggregation cannot produce, with **USD values on every candle and MEV/outlier trades already filtered**, so none of the manual filtering below is needed. The example returns 1-minute OHLC for a token from its top-volume market; swap the token address and network, and change `Duration` for other intervals. Run it [in the IDE](https://ide.bitquery.io/Trading-API-Token-Price-Top-Market-Rank-1). ```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 } Average { Mean } } Volume { Base Usd } Block { Time } } } } ``` The rest of this guide covers the chain-level path: building candles from raw `DEXTradeByTokens` rows for deep history and custom intervals. ## **Intervals in OHLC** Bitquery’s OHLC APIs support multiple time intervals, including minutes, hours, days, weeks, and months. You can specify the desired interval in the response field, as shown in the example below: ```graphql Block { Time(interval: {count: 1, in: minutes}) } ``` ``` Block { Time(interval: {count: 1, in: hours}) } ``` ```graphql Block { Time(interval: {count: 1, in: days}) } ``` ``` Block { Time(interval: {count: 1, in: weeks}) } ``` ``` Block { Time(interval: {count: 1, in: months}) } ``` and so on. ## **OHLC on EVM Chains** To fetch OHLC (Open, High, Low, Close) data for a specific token pair on EVM-compatible chains like Ethereum, you can use Bitquery’s `DEXTradeByTokens` API. Supported networks include: - **Ethereum** → `EVM(network: eth)` - **BNB Chain** → `EVM(network: bsc)` - **Polygon (Matic)** → `EVM(network: matic)` - **Arbitrum** → `EVM(network: arbitrum)` - **Base** → `EVM(network: base)` - **Optimism** → `EVM(network: optimism)` - **Robinhood** → `EVM(network: robinhood)` - **Arc Mainnet** → `EVM(network: arc)` ([Arc DEX trades and OHLCV guide](/docs/blockchain/arc-mainnet/arc-mainnet-trades-api/)) - **Arc Testnet** → `EVM(network: arc_testnet)` (chain-level cubes only; no `Trading` cube, USD fields are 0) For full API documentation, refer to: [Get OHLC Data for a Particular Token Pair](/docs/blockchain/Ethereum/dextrades/token-trades-apis/#get-ohlc-data-for-a-particular-token-pair). ### **Sample Query** The following GraphQL query retrieves OHLCV data for an Ethereum token pair: ```graphql query tradingViewPairs { EVM(network: eth) { DEXTradeByTokens( orderBy: { ascendingByField: "Block_Time" } where: { Trade: { Side: { Amount: { gt: "0" } Currency: { SmartContract: { is: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599" } } } Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } PriceAsymmetry: { lt: 0.5 } } } ) { Block { Time(interval: { count: 5, in: minutes }) } Trade { open: PriceInUSD(minimum: Block_Number) close: PriceInUSD(maximum: Block_Number) max: PriceInUSD(maximum: Trade_PriceInUSD) min: PriceInUSD(minimum: Trade_PriceInUSD) } volume: sum(of: Trade_Side_Amount) } } } ``` ## **OHLC on Non-EVM Chains** Bitquery also supports non-EVM chains, such as **Solana** and **Tron**, enabling you to retrieve OHLC data for these networks. ## **OHLC on Solana** For a detailed guide, visit: [Historical OHLC on Solana](/docs/blockchain/Solana/historical-aggregate-data/#historical-ohlc-on-solana). :::note Archive is also not complete: over a window it shares with `realtime` it returns roughly a fifth to a quarter fewer trades, so candles and volume built from it read low against the same period on `realtime` or the Trading cube. Use it for depth, and prefer the Trading cube's pre-aggregated candles for the last 30 days. On `dataset: combined` / `archive`, `PriceAsymmetry` and USD amount fields cannot be used as filters: they return an error. The query below therefore takes raw `high`/`low` extremes. See [Filter limitations on aggregate datasets](/docs/blockchain/Solana/historical-aggregate-data/#filter-limitations-on-aggregate-datasets). ::: #### **Sample Query** ```graphql { Solana(dataset: archive) { DEXTradeByTokens( orderBy: { descendingByField: "Block_Timefield" } where: { Block: { Time: { since_relative: { days_ago: 90 } } } Trade: { Currency: { MintAddress: { is: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" } } Side: { Currency: { MintAddress: { is: "So11111111111111111111111111111111111111112" } } } } } limit: { count: 10 } ) { Block { Timefield: Time(interval: { in: days, count: 1 }) } volume: sum(of: Trade_Amount) Trade { high: Price(maximum: Trade_Price) low: Price(minimum: Trade_Price) open: Price(minimum: Block_Slot) close: Price(maximum: Block_Slot) } count } } } ``` ## **OHLC on Tron** For details, visit: [OHLC Data on Tron](/docs/blockchain/Tron/tron-dextrades/#get-ohlc-data-of-a-token-on-tron-network). #### **Sample Query** ```graphql query tradingViewPairs { Tron { DEXTradeByTokens( orderBy: { ascendingByField: "Block_Time" } where: { Trade: { Side: { Amount: { gt: "0" } Currency: { SmartContract: { is: "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR" } } } Currency: { SmartContract: { is: "TJ9mxWPmQSJswqMakEehFWcAntg73odiAq" } } PriceAsymmetry: { lt: 0.1 } } } ) { Block { Time(interval: { count: 5, in: minutes }) } Trade { open: PriceInUSD(minimum: Block_Number) close: PriceInUSD(maximum: Block_Number) max: PriceInUSD(maximum: Trade_PriceInUSD) min: PriceInUSD(minimum: Trade_PriceInUSD) } volume: sum(of: Trade_Side_Amount) } } } ``` ## Real-time OHLC In EVM and non-EVM chains, you can also use `subscription` to get a token pair with a high or low value. [Run Stream ➤](https://ide.bitquery.io/seconds-oHLC-realtime-solana-example) Take this query below for example; ```graphql subscription LatestTrades { Solana { DEXTradeByTokens( where: { Transaction: { Result: { Success: true } } Trade: { Currency: { MintAddress: { is: "3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh" } } Side: { Currency: { MintAddress: { is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } } } } ) { min: quantile(of: Trade_PriceInUSD, level: 0.05) max: quantile(of: Trade_PriceInUSD, level: 0.95) volume: sum(of: Trade_Side_AmountInUSD) Trade { Market { MarketAddress } Dex { ProtocolName ProtocolFamily } close: PriceInUSD Side { Type Currency { Symbol MintAddress Name } } } Block { Time(interval: { count: 1, in: seconds }) } } } } ``` This GraphQL **subscription** query is fetching real-time **OHLC (Open, High, Low, Close)** data for Solana trades by continuously monitoring and streaming the latest trades. **Calculating OHLC-Like Metrics** - **`min`** → **5th percentile** price (`quantile(of: Trade_PriceInUSD, level: 0.05)`) - What is the price of the token for lowest 5% of the trades? - **`max`** → **95th percentile** price (`quantile(of: Trade_PriceInUSD, level: 0.95)`) - What is the price of the token for top 5% of the trades? - **`close`** → The latest trade price (`PriceInUSD`). - **`volume`** → Total trade volume in USD (`sum(of: Trade_Side_AmountInUSD)`) over the interval. 4. **Streaming the Data Continuously**: - Because this is a **subscription**, every time a new trade happens on Solana for this token pair, the latest price data is sent. - The latest **closing price (`close`)** is updated dynamically as new trades occur. ### **Why This Is "Real-Time" OHLC?** - The query continuously **monitors** the latest **trades** on Solana. - Each trade updates the **latest close price (`close`)**. - The **high (`max`) and low (`min`)** prices adjust dynamically based on the last 50 trades. - It provides **near real-time OHLC-like data** without waiting for the full candle interval. ### **Limitations** - This is **not true OHLC** because: - It does not aggregate price data strictly by time intervals (e.g., every 5 min, 1h). - The **open price** (first trade of the interval) is missing. - It works based on a **rolling window of the latest trades**, not fixed time slots. ## **Filtering Abnormal Prices** When fetching trade data from Bitquery APIs, you may encounter abnormal prices. These anomalies occur due to two primary reasons: 1. **Legitimate but unusual trades** – The data is correct (can be verified via an explorer like Etherscan), but bot activity may cause extreme price variations. 2. **Incorrect trade data in Bitquery’s database** – If you suspect incorrect data, report the issue by creating a support ticket. For a complete guide, visit: [How to Filter Anomalous Prices](/docs/usecases/how-to-filter-anomaly-prices/). ### **Methods to Filter Anomalous Trades** #### **1. Using Price Asymmetry** - Measures the USD value difference between traded tokens. - To filter extreme trades, use: ```graphql { PriceAsymmetry: {lt: 0.1} } ``` (This removes trades where the price difference exceeds 10%). - Additionally, remove low-value trades: ```graphql { Trade: {AmountInUSD: {lt: "10"}} } ``` #### **2. Using Quantiles** - Quantiles divide data into percentiles, helping detect outliers. - Example: - **75th percentile (`level: 0.75`)** → 75% of values are below this. - **25th percentile (`level: 0.25`)** → 25% of values are below this. - To filter extreme values, keep trades only between the **5th and 95th percentiles**. For more details, check: [Quantile Documentation](/docs/graphql/metrics/quantile/). #### **3. Fetch All Trades and Filter Manually** - Retrieve all trade data from Bitquery. - Apply custom filters, such as: - Calculating the **5th and 95th percentiles** of trade prices. - Keeping only trades within this range. ## **Checking and Reporting Incorrect OHLC Data** If your OHLC data differs significantly from other providers, you should: - Check if **Price Asymmetry** and other filters (as discussed above) are applied. - If there’s still a **huge discrepancy**, report the issue by creating a ticket at: [Bitquery Support](https://support.bitquery.io). ### Example Scenario For example if you check the OHLC for this token `J3TqbUgHurQGNxWtT88UQPcMNVmrL875pToQZdrkpump` again WSOL, Take this query [https://ide.bitquery.io/quantile](https://ide.bitquery.io/quantile) which includes both OHLC using `maximum`, `minimum` and using `quantile` and removes small trades using `AmountinUSD >10` filter. ```graphql { Solana(dataset: archive) { DEXTradeByTokens( orderBy: {descendingByField: "Block_Timefield"} where: { Block: { Time: { since_relative: { days_ago: 90 } } }, Trade: {Currency: {MintAddress: {is: "J3TqbUgHurQGNxWtT88UQPcMNVmrL875pToQZdrkpump"}}, Side: {Currency: {MintAddress: {is: "So11111111111111111111111111111111111111112"}}, Amount: {gt: "0.05"}}}} limit: {count: 10} ) { Block { Timefield: Time(interval: {in: days, count: 1}) } volume: sum(of: Trade_Side_AmountInUSD) min1: quantile(of: Trade_PriceInUSD, level: 0.05) max1: quantile(of: Trade_PriceInUSD, level: 0.95) close1: median(of: Trade_PriceInUSD) open1: median(of: Trade_PriceInUSD) Trade { Currency { Name } high: PriceInUSD(maximum: Trade_Price) low: PriceInUSD(minimum: Trade_Price) open: PriceInUSD(minimum: Block_Slot) close: PriceInUSD(maximum: Block_Slot) Side { Currency { Name } } } count } } } ``` If you compare the results of the two, you see smoothening of spikes. **Example 1 (March 2, 2025)** - **Without quantile filtering:** - `high`: 0.00043009940205914187 (Very high) - `low`: 0.00034041182935594274 - **With quantile filtering:** - `max1`: 0.0004214320331811905 (Lower than raw high → filters extreme spike) - `min1`: 0.0003484918735921383 (Higher than raw low → removes low extremes) _Effect:_ The high (`max1`) and low (`min1`) values are adjusted to remove extreme spikes. **Example 2 (February 27, 2025)** - **Without quantile filtering:** - `close`: 0.0003147996409415908 - `high`: 0.00033683591504094873 - `low`: 0.000254437945561626 (Low outlier) - `open`: 0.00030726648293986935 - **With quantile filtering:** - `close1`: 0.0003042437473777681 - `max1`: 0.00032312784204259514 - `min1`: 0.00028326141997240487 (Higher than raw low, outlier removed) - `open1`: 0.0003042437473777681 _Effect:_ The low (`min1`) is adjusted upwards, likely removing an extreme drop. - Smoothing high price spikes (`max1` is lower than `high`) - Removing sharp downward price drops (`min1` is higher than `low`) ## **Alternative: Calculating OHLC from Trades Without Aggregating in GraphQL Query** ### Limitations of the OHLC API - Solana chain-level OHLC coverage does not span the chain's full history. See [Data coverage & retention](/docs/graphql/data-coverage-retention/) for current windows. - While you can add filters as shown above to filter only valuable trades, it is not fool-proof and might not work with all tokens especially memecoins. If you prefer not to use an aggregated GraphQL query, you can fetch raw trade data and manually compute OHLC values. Complete guide to [using trades to calculate OHLC is available here](/docs/usecases/solana-ohlc-calculator/) ## **Building TradingView Charts** To visualize OHLCV data using **TradingView Advanced Chart Library**, refer to: - [TradingView Advanced Charts Guide](/docs/usecases/tradingview-subscription-realtime/getting-started/) - [TradingView Real-Time Subscription](/docs/usecases/tradingview-subscription-realtime/getting-started/) --- ## Perp DEX API — Onchain Perpetual Futures Data & Streams URL: https://docs.bitquery.io/docs/perpetuals/ Perp DEX API for onchain perpetual futures: orders, trades, positions, PnL, liquidations, funding, mark price and open interest on Solana, via GraphQL and WebSocket. # Perp DEX API — Onchain Perpetual Futures Data & Streams Bitquery indexes **onchain perpetual futures DEXs** at event level and exposes the data as five GraphQL cubes. Every order placement and cancellation, every fill, every position change, every liquidation, every order-book price tick and every open-interest update is queryable over HTTP **and** streamable over WebSocket — the same query text works as a `query` (history) and as a `subscription` (live stream). | | | | ------------- | ------------------------------------------------------------------------------------- | | **Cubes** | `PerpetualOrders`, `PerpetualFills`, `PerpetualPositions`, `PerpetualPrices`, `PerpetualMarketSummaries` | | **Endpoints** | `https://streaming.bitquery.io/graphql` | | **Streaming** | `wss://streaming.bitquery.io/graphql` — see [WebSocket docs](/docs/subscriptions/websockets) | | **Kafka** | `solana.perpetual.proto` protobuf topic — see the [Solana Perpetuals Kafka Stream](/docs/streams/protobuf/chains/Solana-perpetual-protobuf) | | **Auth** | [OAuth token](/docs/authorization/how-to-generate) as `Authorization: Bearer ` | ## The five cubes | Cube | One row per… | What it answers | | -------------------------- | ------------------------------------- | ------------------------------------------------------------------------------- | | `PerpetualOrders` | order lifecycle event | Who placed, cancelled, or got rejected; limit/market/post-only/stop orders; cancel and reject reasons | | `PerpetualFills` | trade execution | Executions with price, size, fee, taker side, maker counterparty, and the position that resulted | | `PerpetualPositions` | position state change | Entry price, size before/after, realized PnL, funding settlements, liquidations | | `PerpetualPrices` | order-book price tick | Best bid, best ask, mark price, last trade — tick by tick | | `PerpetualMarketSummaries` | market state update | Open interest, spot index vs mark price, cumulative maker/taker fees | Together they cover the full trading loop: an order enters the book (`PerpetualOrders`), matches (`PerpetualFills`), moves a position (`PerpetualPositions`), and the market's price and open interest move with it (`PerpetualPrices`, `PerpetualMarketSummaries`). ## Why this data is different - **Cross-asset markets.** Onchain perp DEXs now list far more than crypto pairs: the currently indexed venue trades crypto majors and memecoins alongside **US equities, commodities like gold, silver and oil, and pre-IPO names** — all as perpetual futures, all settled onchain, all in one API. - **Order-book depth of detail.** This is not OHLC candles. You see individual post-only quotes, stop-loss placements, cancel reasons, and which fills were matched by the AMM backstop versus another trader's resting order. - **Liquidations as first-class events.** Liquidation fills and liquidated positions carry the liquidator address, liquidated size and quote value — enough to build a live liquidation feed or long-term liquidation analytics. - **PnL without reconstruction.** Position rows carry `RealizedPnl`, entry price and size transitions, so trader leaderboards don't require you to replay fills yourself. ## Supported DEXs Coverage is organized by chain, then by protocol: | Chain | Protocol | Docs | | ------ | -------------------------------------------------------------- | ----------------------------------------------------------- | | Solana | **Phoenix Perpetuals** (`phoenix_eternal`, by Ellipsis Labs) | [Phoenix Perpetuals API](/docs/perpetuals/solana/phoenix-perpetuals-api) | More venues will appear here as they are enabled. To check what is indexed at any moment, group any cube by `Exchange`: ```graphql query { Solana { PerpetualFills(limit: { count: 20 }, orderBy: { descendingByField: "count" }) { count Fill { Exchange { Family Name Program Version } } } } } ``` ## Query or stream — your choice Every cube is available in both forms. A historical query: ```graphql query { Solana { PerpetualFills(limit: { count: 10 }, orderBy: { descending: Block_Time }) { Block { Time } Fill { Asset { Symbol } Side ExecutionPrice Amount { Filled Quote } } } } } ``` …becomes a live stream by changing one word and dropping the pagination arguments: ```graphql subscription { Solana { PerpetualFills { Block { Time } Fill { Asset { Symbol } Side ExecutionPrice Amount { Filled Quote } } } } } ``` ## What people build with it - **Liquidation alert bots** — stream `PerpetualPositions` filtered to `Liquidation: true` - **Trader analytics and leaderboards** — aggregate `RealizedPnl` per `Trader` - **Open-interest and fee dashboards** — snapshot `PerpetualMarketSummaries` per market - **Live tickers and charting** — stream `PerpetualPrices` best bid/ask and mark - **Market-maker monitoring** — follow order lifecycle and AMM-vs-book fill share Start with the [Phoenix Perpetuals API](/docs/perpetuals/solana/phoenix-perpetuals-api) page — it documents every cube with working queries and streams. Then jump to the [Perps Trader Cookbook](/docs/perpetuals/solana/perps-trader-cookbook) for workflow-shaped recipes: copy-trading a wallet, trader win-rate report cards, top unrealized positions, whale fills, OHLC candles, open-interest and order-flow series. --- ## Phoenix Perpetuals API — Solana Perp DEX Data & Streams URL: https://docs.bitquery.io/docs/perpetuals/solana/phoenix-perpetuals-api/ Phoenix Perpetuals API on Solana: query and stream orders, fills, positions, realized PnL, liquidations, funding, best bid/ask, mark price and open interest. # Phoenix Perpetuals API — Solana Perp DEX Data & Streams [Phoenix Perpetuals](https://www.ellipsislabs.xyz/) is the fully onchain perpetual futures exchange built by Ellipsis Labs, the team behind the Phoenix spot order book on Solana. Bitquery indexes it at event level into five cubes, each available as a GraphQL `query` and as a WebSocket `subscription`. ## Video Tutorials | | | | ------------------- | ------------------------------------------------------ | | **Exchange family** | `Phoenix` | | **Exchange name** | `phoenix_eternal` | | **Program** | `EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih` | | **Quote currency** | `PhUsd` — mint `PhUsd11YkbjSaWjFncfAAmatntsjx3MgDR9B6g1ks3A`, 6 decimals | | **Markets** | Crypto majors and memecoins, US equities, commodities, pre-IPO names | | **Endpoints** | `https://streaming.bitquery.io/graphql`; streams via `wss://streaming.bitquery.io/graphql` | | **Kafka** | Same data as protobuf on the [`solana.perpetual.proto` topic](/docs/streams/protobuf/chains/Solana-perpetual-protobuf) | ## Reading the data model Every cube shares the same `Asset` block — the perpetual market being traded: - **`Asset.Id`** — the venue's numeric market id (e.g. BTC is `"1"`). Stable key; filter on it or on `Symbol`. - **`Asset.Symbol`** — the underlying: `BTC`, `SOL`, `AAPL`, `TSLA`, `GOLD`, `WTIOIL`, … - **`Asset.LotSize` / `Asset.TickSize`** — minimum size and price increments. - **`Asset.QuoteCurrency`** — always `PhUsd` on Phoenix; all prices and quote amounts are in it. - **Sizes are signed** where direction matters: negative size = short / sell side. - **`Trader` vs `Signer`** — `Trader` is the account whose position or order it is; `Signer` signed the transaction (they differ for liquidations, AMM flow, and delegated flows). - **`TraderIsAmm` / `CounterpartyIsAmm`** — Phoenix runs an AMM backstop alongside the order book. Rows flag whether each side is the AMM. To see which markets exist right now, group fills (or any cube) by asset: ```graphql query { Solana { PerpetualFills(limit: { count: 100 }, orderBy: { descendingByField: "count" }) { count Fill { Asset { Id Symbol LotSize TickSize QuoteCurrency { Symbol } } } } } } ``` ## Live prices — `PerpetualPrices` One row per order-book price tick: best bid, best ask, mark price, and the last trade price when the tick was caused by a trade. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/sol_perps_bid_ask). ```graphql query { Solana { PerpetualPrices(limit: { count: 10 }, orderBy: { descending: Block_Time }) { Block { Time } Transaction { Signature Signer } Price { Asset { Symbol QuoteCurrency { Symbol } } BestAsk BestBid LastTrade Mark SequenceNumber } } } } ``` Notes: - `LastTrade` is `0` on ticks not caused by a trade (quote updates, cancels). Filter `Price: { LastTrade: { gt: 0 } }` for trade prints only. - `SequenceNumber` is the venue's monotonic sequence — use it to order ticks within a slot. Stream the BBO for one market live: You can run this stream [in the Bitquery IDE](https://ide.bitquery.io/solana-perpetuals-mark-price-stream). ```graphql subscription { Solana { PerpetualPrices(where: { Price: { Asset: { Symbol: { is: "BTC" } } } }) { Block { Time } Price { Asset { Symbol } BestBid BestAsk Mark } } } } ``` ## Open interest & fees — `PerpetualMarketSummaries` Market-level state updates: mark price, the spot index it tracks, open interest, and fee counters. You can run this query [in the Bitquery IDE](https://ide.bitquery.io/sol_perps_market_info). ```graphql query { Solana { PerpetualMarketSummaries( where: { MarketSummary: { Asset: { Symbol: { is: "JTO" } } } } limit: { count: 10 } orderBy: { descending: Block_Time } ) { Block { Time } Transaction { Signature } MarketSummary { Asset { Id Symbol QuoteCurrency { Symbol } } Mark SpotIndex OpenInterest MakerFees TakerFees } } } } ``` You can filter by the numeric market id instead — `where: { MarketSummary: { Asset: { Id: { eq: "22" } } } }` selects the same JTO market. Notes: - `OpenInterest` is in base units of the asset (e.g. BTC for the BTC market). - **`MakerFees` and `TakerFees` are cumulative counters** since market inception, in `PhUsd`. To get fees generated over an interval, take the difference between the latest value and the value at the start of the interval — don't read a single row as a per-block fee. - `Mark` vs `SpotIndex` gives you the perp premium/discount at any moment. For a dashboard, grab the **latest snapshot of every market in one query** with `limitBy` on the asset id: You can run this query [in the Bitquery IDE](https://ide.bitquery.io/phoenix-perps-all-markets). ```graphql query { Solana { PerpetualMarketSummaries( limitBy: { by: MarketSummary_Asset_Id, count: 1 } limit: { count: 100 } orderBy: { descending: Block_Time } ) { Block { Time } MarketSummary { Asset { Id Symbol } Mark SpotIndex OpenInterest } } } } ``` Stream open-interest changes across all markets: ```graphql subscription { Solana { PerpetualMarketSummaries { Block { Time } MarketSummary { Asset { Symbol } Mark SpotIndex OpenInterest } } } } ``` ## Order lifecycle — `PerpetualOrders` You can run an order-lifecycle query [in the Bitquery IDE](https://ide.bitquery.io/sol_perps_orders). One row per order event. `Order.Type` is the **event**, and the nested `Order.Order.Type` is the **order kind**: | Field | Values seen | | -------------------- | ------------------------------------------------------------------------------------------------------------ | | `Order.Type` (event) | `OrderRequested`, `OrderPlaced`, `OrderCancelled`, `OrderRejected`, `StopLossPlaced`, `TakeProfitPlaced`, `TriggerPlaced`, `TriggerExecuted`, `TriggerCancelled`, `ConditionalExecuted`, `ConditionalCancelled` | | `Order.Order.Type` (kind) | `limit`, `market`, `post-only`, `stop-loss`, `take-profit` (empty on events where kind isn't re-stated, e.g. cancels) | | `Order.Order.CancelReason` | `UserRequested`, `Expired`, `ReduceOnlyInvalidated`, `SelfTradeCancelProvide` | | `Order.Order.RejectReason` | `TiFInvalid`, `PostOnlyCross` | A typical placement produces `OrderRequested` followed by `OrderPlaced` (which carries the assigned `Order.Order.Id`) in the same transaction. More lifecycle details, all observable in the data: - **Conditional & trigger events** (`Conditional*`, `Trigger*`) describe the stop/take-profit machinery: a `StopLossPlaced` row carries the `Price.Trigger` level and a `Order.ConditionalId` that later `TriggerExecuted` / `ConditionalCancelled` rows reference. These bookkeeping rows have an empty `Side`. - **Time-in-force**: `Order.Order.ValidUntilSlot` is a slot-based expiry for resting quotes (`0` = no expiry). Orders that hit it are cancelled with `CancelReason: "Expired"`. - **`Order.Order.ClientId`** is the trader's own hex order identifier, when supplied — useful for reconciling your execution system against the chain. ```graphql query { Solana { PerpetualOrders(limit: { count: 10 }, orderBy: { descending: Block_Time }) { Block { Time } Transaction { Signature } Order { Asset { Symbol } Type Side Trader Signer Price { Limit Trigger Mark } Amount { Size Remaining Quote } Order { Id Type ReduceOnly CancelReason RejectReason } } } } } ``` Stream every stop-loss and take-profit placement as it happens: ```graphql subscription { Solana { PerpetualOrders( where: { Order: { Type: { in: ["StopLossPlaced", "TakeProfitPlaced"] } } } ) { Block { Time } Order { Asset { Symbol } Type Side Trader Price { Trigger Mark } Amount { Size } } } } } ``` ## Trades — `PerpetualFills` You can run a live fills stream [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-live-trades-stream), or [fills for one wallet](https://ide.bitquery.io/sol_perps_filled_orders_by_signer). One row per execution. `Side` is the taker's side (`bid` = taker bought, `ask` = taker sold); `Amount.Size` is signed by direction while `Amount.Filled` is the unsigned fill quantity and `Amount.Quote` the quote value. ```graphql query { Solana { PerpetualFills(limit: { count: 10 }, orderBy: { descending: Block_Time }) { Block { Time } Transaction { Signature } Fill { Asset { Symbol QuoteCurrency { Symbol Decimals } } Side ExecutionPrice MarkPrice Amount { Filled Size Quote Fee Remaining } Trader TraderIsAmm Counterparty CounterpartyIsAmm MakerOrderId SplineId Collateral Position { EntryPrice Size } Liquidation Liquidator } } } } ``` Notes: - **AMM fills**: when `CounterpartyIsAmm` is `true`, the fill matched the AMM backstop — `MakerOrderId` is empty and `SplineId` identifies the AMM curve segment. Book fills carry the maker's `MakerOrderId` instead. The AMM currently absorbs the large majority of taker flow, so segment by this flag before drawing conclusions about book liquidity. - `Amount.Fee` is the fee charged on the fill in `PhUsd`; it is `0` on most fills and never negative in observed data. - `Position { EntryPrice, Size }` is the trader's position **after** this fill — you can follow a position's evolution from fills alone. - `Collateral` is the trader's collateral balance snapshot in `PhUsd`. - `Liquidation: true` marks forced fills, with the `Liquidator` address populated. All fills of one trader (excluding liquidations): ```graphql query { Solana { PerpetualFills( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Fill: { Liquidation: false Signer: { is: "7Kjwrohbf49adi5Gg4WM1M9h68UZSBFvLVRdw7PoeX5E" } } } ) { Block { Time } Transaction { Signature } Fill { Asset { Symbol } Side ExecutionPrice Amount { Filled Quote Fee } Position { EntryPrice Size } } } } } ``` ## Positions, PnL & liquidations — `PerpetualPositions` Runnable IDE examples: [top open positions](https://ide.bitquery.io/solana-perps-top-positions), [liquidations](https://ide.bitquery.io/solana-perps-liquidations), [realized PnL leaderboard](https://ide.bitquery.io/solana-perps-trader-pnl). One row per position state change: size transitions, realized PnL, funding settlements, and liquidations. ```graphql query { Solana { PerpetualPositions(limit: { count: 10 }, orderBy: { descending: Block_Time }) { Block { Time } Transaction { Signature Signer } Position { Asset { Symbol QuoteCurrency { Symbol } } Type Trader TraderIsAmm Position { EntryPrice Size SizeBefore } MarkPrice RealizedPnl Funding Closed Liquidation Liquidator LiquidatedQuote LiquidatedSize } } } } ``` Notes: - `Position.Type` is `PnL` for normal position accounting rows and `Liquidation` for the dedicated liquidation rows. - `SizeBefore → Size` is the transition; `Closed: true` marks a full close. - `RealizedPnl` (in `PhUsd`) is booked on closes and reductions; `Funding` is non-zero on funding settlement rows. - **Funding settlements are their own rows**: `Funding ≠ 0`, position size unchanged (`SizeBefore` = `Size`), `RealizedPnl: 0` and `MarkPrice: 0`. The sign is from the trader's perspective — positive means the position received funding, negative means it paid. Filter `Position: { Funding: { ne: 0 } }` for a funding history. - **A liquidation emits multiple rows in one transaction**: the trader's forced close (`Type: "PnL"`, `Closed: true`, negative `RealizedPnl`) plus a `Type: "Liquidation"` row carrying `LiquidatedSize` and `LiquidatedQuote`, with `Liquidator` set on each — and the liquidator's own position rows alongside. Count *events*, not rows, when measuring liquidation activity. Profitable closed trades — every close that realized more than 100 `PhUsd`: ```graphql query { Solana { PerpetualPositions( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Position: { RealizedPnl: { gt: 100 }, Closed: true } } ) { Block { Time } Transaction { Signature Signer } Position { Asset { Symbol } Trader Position { EntryPrice Size SizeBefore } MarkPrice RealizedPnl Closed } } } } ``` A realized-PnL leaderboard falls out of one aggregation — total booked PnL per trader across closed positions, AMM excluded: ```graphql query { Solana { PerpetualPositions( limit: { count: 10 } orderBy: { descendingByField: "pnl" } where: { Position: { TraderIsAmm: false, Closed: true } } ) { Position { Trader } pnl: sum(of: Position_RealizedPnl) closes: count } } } ``` Live liquidation feed: ```graphql subscription { Solana { PerpetualPositions(where: { Position: { Liquidation: true } }) { Block { Time } Transaction { Signature } Position { Asset { Symbol } Type Trader Liquidator LiquidatedSize LiquidatedQuote RealizedPnl MarkPrice } } } } ``` ## Ideas to build Worked, runnable versions of the recipes below — copy-trade feeds, trader report cards, unrealized-PnL rankings, OHLC candles, OI/basis series, order-flow pressure — live in the [Perps Trader Cookbook](/docs/perpetuals/solana/perps-trader-cookbook). - **Liquidation alerts** — the subscription above, pushed to Telegram/Discord. - **PnL leaderboard** — aggregate `RealizedPnl` by `Trader` over `PerpetualPositions`, excluding `TraderIsAmm: true`. - **OI & premium dashboard** — periodic snapshots of `PerpetualMarketSummaries` (`OpenInterest`, `Mark` vs `SpotIndex`, fee-counter diffs). - **Equity & commodity perps tracker** — filter any cube to `AAPL`, `TSLA`, `GOLD`, `WTIOIL` markets: stock and commodity price action, settled onchain, streaming in real time. - **Execution analytics** — compare `ExecutionPrice` to `MarkPrice` on fills; split volume by AMM vs order-book counterparty. --- ## Polymarket AI & Tech Markets API URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-ai-tech-api/ Polymarket AI and tech markets: newly created markets, live odds, OHLC odds movement, top markets by volume, resolved markets and whale-bet alerts. # Polymarket AI & Tech Markets API Get full access to **Polymarket AI and tech prediction markets** through one API. That covers OpenAI, GPT, Anthropic, AGI, and big-tech bets, with live odds (implied probability), trades, volume, market creation and resolution, and trader activity. Bitquery runs **its own blockchain nodes** and **indexes, decodes, and parses** raw Polygon transactions into clean, structured prediction-market data. You don't have to run nodes, decode contract logs, or stitch together odds yourself. The same data is available three ways: - **Historical queries:** backfill odds, volume, and resolutions over any time range. - **Real-time GraphQL subscriptions (WebSocket):** stream new trades and odds the moment they hit the chain. - **Kafka streams:** low-latency, high-throughput feeds for production pipelines. Every query below can be run live by changing `query` to `subscription`. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. See [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/). ::: --- ## How AI & Tech Markets Are Identified AI and tech markets are matched by a keyword in `Question.Title` using `includesCaseInsensitive`. The same filter works on **PredictionManagements**, **PredictionTrades**, and **PredictionSettlements**, placed under `Prediction.Question.Title`. | Topic | Example `Question.Title` keyword | | ---------------- | -------------------------------- | | OpenAI | `"OpenAI"` | | ChatGPT / GPT | `"GPT"` | | Anthropic | `"Anthropic"` | | Claude | `"Claude"` | | Google Gemini | `"Gemini"` | | xAI / Grok | `"Grok"` | | AGI | `"AGI"` | | Big Tech | `"Apple"`, `"Nvidia"`, `"Tesla"` | > **Tip:** To match the whole AI category, use the standalone word with surrounding spaces, `" AI "`. This avoids the substring trap where a bare `"AI"` also catches unrelated words such as "Spain" or "fair". For a specific company or model, use a precise term like `"OpenAI"` or `"GPT"`. A single market is uniquely identified by its `Question.MarketId`. --- ## Latest AI Markets Created Returns the 10 most recent **Created** events for Polymarket markets whose title includes the standalone word **" AI "** (note the surrounding spaces). Swap the keyword for any term from the table above. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-AI-markets-created-on-Polymarket) ```graphql query LatestAIMarketsCreated { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: " AI " } } } } } ) { Block { Time } Management { Description EventType Prediction { Condition { Id QuestionId Outcomes { Index Label } } Outcome { Index Label } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { Hash } } } } ``` --- ## Live Odds (Implied Probability) for an AI Market Returns the **latest trade price per outcome** for one market by `MarketId`. This is the **live implied probability** for that question. On Polymarket an outcome's `Price` ranges from 0 to 1 and equals its implied probability (e.g. `0.62` = 62%). Replace `""` with a market ID from the creation query above. Change `query` to `subscription` for a live odds feed. [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-AI-market-live-odds) ```graphql query AIMarketLiveOdds { EVM(network: matic) { PredictionTrades( limitBy: { by: Trade_Prediction_Outcome_Label, count: 1 } orderBy: { descending: Block_Time } where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { MarketId: { is: "" } } } } } ) { Trade { OutcomeTrade { Price PriceInUSD } Prediction { Outcome { Index Label } OutcomeToken { Name AssetId } Question { Title MarketId ResolutionSource Image } } } } } } ``` --- ## Odds (Line) Movement: OHLC for an Outcome Returns **OHLC** (Open, High, Low, Close) in USD for one outcome of an AI market, bucketed by interval (here 5 minutes). It shows how the implied probability moved over time, and powers charts and backtests. Replace `""` and `""` (e.g. `"Yes"` or `"No"`). [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-AI-odds-movement-OHLC) ```graphql query AIOddsMovementOHLC { EVM(network: matic) { PredictionTrades( limit: { count: 100 } orderBy: { descendingByField: "Block_Interval" } where: { Trade: { Prediction: { Question: { MarketId: { is: "" } } Outcome: { Label: { is: "" } } } } } ) { Block { Interval: Time(interval: { count: 5, in: minutes }) } Trade { OutcomeTrade { Open: PriceInUSD(minimum: Block_Time) High: PriceInUSD(maximum: Trade_OutcomeTrade_PriceInUSD) Low: PriceInUSD(minimum: Trade_OutcomeTrade_PriceInUSD) Close: PriceInUSD(maximum: Block_Time) } Prediction { OutcomeToken { Name AssetId } Outcome { Id Label } } } } } } ``` --- ## Top AI Markets by Volume (Last 24 Hours) Returns AI markets (title includes the standalone word **" AI "**) ranked by USD trading volume in the last 24 hours, with buyer and seller counts. Adjust `time_ago`, `limit`, and the title keyword as needed. [Run in Bitquery IDE](https://ide.bitquery.io/Top-AI-markets-by-volume-Polymarket) ```graphql query TopAIMarketsByVolume($time_ago: Int!, $limit: Int!) { EVM(network: matic) { PredictionTrades( where: { Block: { Time: { since_relative: { hours_ago: $time_ago } } } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: " AI " } } } } } limit: { count: $limit } orderBy: { descendingByField: "sumBuyAndSell" } ) { Trade { Prediction { Question { Id Image Title MarketId CreatedAt } } } buyUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { IsOutcomeBuy: true } } } ) sellUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { IsOutcomeBuy: false } } } ) sumBuyAndSell: calculate(expression: "$buyUSD + $sellUSD") trades: count buyers: count(distinct: Trade_OutcomeTrade_Buyer) sellers: count(distinct: Trade_OutcomeTrade_Seller) } } } ``` Variables: ```json { "time_ago": 24, "limit": 100 } ``` --- ## Latest Resolved AI Markets Returns the 10 most recent **Resolved** AI markets, including the winning outcome. Use this to grade results and settle positions. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-resolved-AI-markets-Polymarket) ```graphql query LatestResolvedAIMarkets { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Resolved" } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: " AI " } } } } } ) { Block { Time } Management { Description EventType Prediction { Outcome { Index Label } Question { Title MarketId ResolutionSource Image CreatedAt } } } Transaction { Hash } } } } ``` --- ## Real-Time Whale-Bet Alerts (Subscription) Streams live AI-market trades above a USD threshold (here `$5,000`). This is ideal for whale-alert bots and detecting large, conviction bets. Filter with a `Question.Title` keyword, or swap it for a single-market `MarketId`. Change `subscription` to `query` for historical results. [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-AI-whale-trades-stream) ```graphql subscription { EVM(network: matic) { PredictionTrades( where: { Trade: { OutcomeTrade: { CollateralAmountInUSD: { gt: "5000" } } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: " AI " } } } } } ) { Block { Time } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmountInUSD Price PriceInUSD IsOutcomeBuy } Prediction { Question { Title MarketId ResolutionSource Image } Outcome { Index Label } } } Transaction { From Hash } } } } ``` --- ## Real-Time: GraphQL Subscriptions and Kafka ### GraphQL Subscriptions Any query on this page can be run as a subscription. Keep the same `where` filters and fields, and change the keyword `query` to `subscription`. You receive new events as they occur on Polygon over a WebSocket connection. ### Kafka Streams For low-latency consumption, prediction market data (including AI and tech markets) is available via Kafka: - **`matic.predictions.proto`:** Raw prediction market events (creations, resolutions, trades) - **`matic.broadcasted.predictions.proto`:** Mempool prediction market data Kafka requires separate credentials. See [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/). For access, [contact support](https://t.me/bloxy_info) or email support@bitquery.io. --- ## Related APIs | Need | API | | ---- | --- | | **Live odds, trades, volume by sport** | [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/) | | **Commodity markets (gold, oil)** | [Polymarket Commodity API](/docs/examples/polymarket-api/polymarket-commodity-api/) | | **Bitcoin up or down odds** | [Polymarket Bitcoin API](/docs/examples/polymarket-api/bitcoin-polymarket-api/) | | **Insider & fresh-wallet detection** | [Polymarket Insider Detection API](/docs/examples/polymarket-api/polymarket-insider-detection-api/) | | **Trader realized PnL & win rate** | [Realized PnL & Win Rate for Polymarket Trader](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/) | | **Settlements & redemptions** | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | **Real-time: Kafka streams** | [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) | --- ## Support - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket API - Advanced Analytics URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-advanced-analytics-api/ Polymarket analytics: USDC TVL, daily volume, hourly buy vs sell order flow, whale trade subscriptions, open interest by date and top markets by 24h volume. # Polymarket API - Advanced Analytics This guide shows **GraphQL examples** for deeper **Polymarket** metrics on **Polygon** (`network: matic`): **TVL** of Polymarket, **daily trade aggregates**, **buy vs sell pressure** for a market, **large-trade streaming**, **split/merge settlement** totals, and **top markets by volume**. All examples use `dataset: realtime`, which covers about the **last 7 days** of data. Use it together with the [Polymarket API](/docs/examples/polymarket-api/), [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/), and [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/). :::note API Key Required To run these queries outside the Bitquery IDE, you need an API access token. See [How to generate Bitquery API token](/docs/authorization/how-to-generate/). ::: :::tip Contract addresses Since the April 2026 migration, Polymarket trades on the **CTF Exchange** `0xE111180000d2663C0091e4f400237545B87B996B` and **Neg Risk CTF Exchange** `0xe2222d279d744050d28e00520010520000310F59`, and settles in **pUSD** `0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB` (a USDC-backed wrapper around USDC.e). Confirm addresses against the [Polymarket contracts page](https://docs.polymarket.com/resources/contracts) before production use; deployments can change over time. ::: :::note Dataset: `realtime` and retention Polymarket prediction-market data on Polygon (`PredictionTrades`, `PredictionSettlements`, and related examples on this page) must use `dataset: realtime`. This dataset holds roughly the **last 7 days**, so use time filters that fall inside that window. For longer history, see [Polymarket historical data exports](/docs/cloud/polymarket/). ::: --- ## Overview | Topic | API | What you get | | ------------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------- | | **TVL Polymarket** | `TransactionBalances` | Latest **USDC.e** and **pUSD** balances for listed custody addresses | | **Daily volume & maker/taker split** | `PredictionTrades` | **Shares** (`Amount`), **USDC** collateral, split by **Buyer** vs CTF-style addresses | | **Order flow (hourly)** | `PredictionTrades` | **Buy** vs **sell** pressure using `IsOutcomeBuy`, optional **market title** filter | | **Whale trades** | `PredictionTrades` (subscription) | Trades above a **USD** threshold | | **open Interest (one day)** | `PredictionSettlements` | **Split** / **merge** USDC and **net** (liquidity-style proxy, not CLOB OI) | | **Top markets by volume** | `PredictionTrades` | Markets ranked by **buy + sell** USD over **24 hours** | --- --- ## USDC TVL: balances for Conditional Tokens and neg-risk collateral Summarize collateral held by **Conditional Tokens** and **neg-risk wrapped collateral** contracts. Traders now hold and settle in **pUSD** (`0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`), but the collateral adapters unwrap it on the way in, so custody inside these contracts is still **USDC.e** (`0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174`). The query reads both tokens; expect USDC.e to carry almost all of the balance. Extend the `Address` list if you track additional custodians. [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-TVL) ```graphql query PolymarketCollateralTVL { EVM(dataset: realtime, network: matic) { TransactionBalances( where: { TokenBalance: { Address: { in: [ "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045" "0x3A3BD7bb9528E159577F7C2e685CC81A765002E2" ] } Currency: { SmartContract: { in: [ "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB" "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" ] } Fungible: true } } } ) { TokenBalance { Address PostBalance(maximum: Block_Time) PostBalanceInUSD(maximum: Block_Time) Currency { Symbol SmartContract Decimals } } } } } ``` --- ## Daily volume: notional shares, USDC, and buyer-address split For a **single calendar day** (UTC), aggregate: - **volume_notional_shares**: sum of outcome `Amount` (share-like notional) - **volume_usdc**: sum of `CollateralAmountInUSD` - **taker_volume_usdc_address_rule**: USDC where `Buyer` is in `$PolymarketContractAddresses` - **maker_volume_usdc_address_rule**: USDC where `Buyer` is **not** in that list Use `dataset: realtime` (see the **Dataset: realtime and retention** note at the top of this page). [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-Notional-Volume-Taker-VolumeMaker-Volume) ```graphql query PolymarketVolume( $date: String! $PolymarketContractAddresses: [String!]! ) { EVM(dataset: realtime, network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } Block: { Date: { is: $date } } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Date } volume_notional_shares: sum(of: Trade_OutcomeTrade_Amount) volume_usdc: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) taker_volume_usdc_address_rule: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { Buyer: { in: $PolymarketContractAddresses } } } } ) maker_volume_usdc_address_rule: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { Buyer: { notIn: $PolymarketContractAddresses } } } } ) } } } ``` **Variables (example):** ```json { "date": "2026-09-17", "PolymarketContractAddresses": [ "0xe111180000d2663c0091e4f400237545b87b996b", "0xe2222d279d744050d28e00520010520000310f59" ] } ``` --- ## Order flow: buy vs sell pressure by hour Bucket trades by **hour** and split **collateral USD** using `IsOutcomeBuy` (see [Prediction Trades API: trade direction](/docs/examples/prediction-market/prediction-trades-api/)). ### One market (filter by exact question title) [Run in Bitquery IDE](https://ide.bitquery.io/buy-sell-pressure-of-aspecific-market) ```graphql query PolymarketOrderFlowPressureOneMarket( $hours_ago: Int! $marketTitle: String! ) { EVM(dataset: realtime, network: matic) { PredictionTrades( orderBy: { ascending: Block_Time } where: { TransactionStatus: { Success: true } Block: { Time: { since_relative: { hours_ago: $hours_ago } } } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { is: $marketTitle } } } } } ) { Block { Time(interval: { count: 1, in: hours }) } Trade { Prediction { Question { MarketId Title } } } buy_pressure_usd: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { IsOutcomeBuy: true } } } ) sell_pressure_usd: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { IsOutcomeBuy: false } } } ) } } } ``` **Variables (example):** ```json { "hours_ago": 24, "marketTitle": "US x Iran ceasefire by April 30?" } ``` --- ## Whale trades: subscription above a USD threshold Stream **successful** Polymarket trades whose **collateral** exceeds **$10,000** USD. Adjust the threshold string as needed. [Run in Bitquery IDE](https://ide.bitquery.io/polymarket-whale-trades-alert_1) ```graphql subscription PolymarketWhaleTradesAlert { EVM(dataset: realtime, network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } OutcomeTrade: { CollateralAmountInUSD: { gt: "10000" } } } } ) { Block { Time } Trade { OutcomeTrade { Buyer Seller CollateralAmount CollateralAmountInUSD Price PriceInUSD IsOutcomeBuy } Prediction { Question { Title MarketId } Outcome { Label } } } Transaction { Hash } } } } ``` --- ## Open Interest on Polymarket on a specific date For a **calendar day**, sum **Split** and **Merge** **collateral USD** and the **net** (`split − merge`). This describes **Open Interest** of that day. [Run in Bitquery IDE](https://ide.bitquery.io/Open-Interest-on-a-day) ```graphql query PolymarketSettlementFlowOneDay($day: String!) { EVM(dataset: realtime, network: matic) { PredictionSettlements( where: { Block: { Date: { is: $day } } Settlement: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { split_usd: sum( of: Settlement_Amounts_CollateralAmountInUSD if: { Settlement: { EventType: { is: "Split" } } } ) merge_usd: sum( of: Settlement_Amounts_CollateralAmountInUSD if: { Settlement: { EventType: { is: "Merge" } } } ) net_split_merge_usd: calculate(expression: "$split_usd - $merge_usd") } } } ``` **Variables (example):** ```json { "day": "2026-03-22" } ``` --- ## Top markets by volume: last 24 hours Rank **Polymarket** markets by **buy + sell** collateral USD, with **buy/sell** breakdown, **trade count**, **distinct buyers/sellers**, and optional **resolution** join. Uses `limitBy: Trade_Prediction_Question_Id` so each row is **one market**. [Run in Bitquery IDE](https://ide.bitquery.io/top-100-markets-by-volumein-last24-hrs_1) ```graphql query topMarketsByVolume($limit: Int!) { EVM(network: matic) { PredictionTrades( where: { Block: { Time: { since_relative: { hours_ago: 24 } } } } limitBy: { count: 1, by: Trade_Prediction_Question_Id } limit: { count: $limit } orderBy: { descendingByField: "sumBuyAndSell" } ) { Trade { Prediction { Question { Id Image Title CreatedAt } OutcomeToken { assetId0: AssetId( if: { Trade: { Prediction: { Outcome: { Index: { eq: 0 } } } } } ) assetId1: AssetId( if: { Trade: { Prediction: { Outcome: { Index: { eq: 1 } } } } } ) } Outcome { label0: Label( if: { Trade: { Prediction: { Outcome: { Index: { eq: 0 } } } } } ) label1: Label( if: { Trade: { Prediction: { Outcome: { Index: { eq: 1 } } } } } ) } } OutcomeTrade { price0: Price( maximum: Block_Time if: { Trade: { Prediction: { Outcome: { Index: { eq: 0 } } } } } ) price1: Price( maximum: Block_Time if: { Trade: { Prediction: { Outcome: { Index: { eq: 1 } } } } } ) } } buyUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { IsOutcomeBuy: true } } } ) sellUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { OutcomeTrade: { IsOutcomeBuy: false } } } ) sumBuyAndSell: calculate(expression: "$buyUSD + $sellUSD") trades: count buyers: count(distinct: Trade_OutcomeTrade_Buyer) sellers: count(distinct: Trade_OutcomeTrade_Seller) resolved: joinPredictionManagements( join: left Management_Prediction_Question_Id: Trade_Prediction_Question_Id ) { Block { Time( maximum: Block_Time if: { Management: { EventType: { is: "Resolved" } } } ) } } } } } ``` **Variables (example):** ```json { "limit": 100 } ``` --- ## Related APIs | Need | Doc | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | Trades, prices, filters | [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) | | Splits, merges, redemptions | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | Condition ID, slug, token | [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/) | | Overview | [Polymarket API](/docs/examples/polymarket-api/) | | Wallet-level activity | [Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/) | | Token balances (USDC) | [Token Balance API](/docs/blockchain/Ethereum/balances/transaction-balance-tracker/token-balance-api/) | --- ## Support - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket API vs Bitquery Polymarket API (2026) URL: https://docs.bitquery.io/docs/API-Blog/polymarket-api-vs-bitquery-polymarket-api/ Official Polymarket APIs (Gamma, CLOB, Data API v2) compared with Bitquery's on-chain Polymarket API: what each does, limits, and when to use both. # Polymarket API vs Bitquery Polymarket API If you are building on Polymarket, whether a dashboard, a trading bot, a research tool, or a wallet leaderboard, you have two very different data stacks to choose from. Polymarket publishes a set of **official REST and WebSocket APIs** that serve the [polymarket.com](https://polymarket.com) application itself. Bitquery offers an **on-chain-derived GraphQL API** for the same Polymarket markets on Polygon, plus subscriptions and Kafka streams. Both give you Polymarket data. They are built for different jobs. This article walks through what each one does well, what it does not do, and when to combine them. ## TL;DR The official Polymarket APIs are the right tool when you need to **place orders**, read the **live order book**, or fetch Polymarket's own metadata (events, tags, user profiles, positions, bridge status). Those are off-chain order-book concerns. They are useful for execution and application views, but they stop where the trader's real questions begin. Bitquery's Polymarket API is the right tool for **traders and researchers**: on-chain-grounded trades, realized P&L, wallet histories, whale filtering, top-trader leaderboards, settlement flows, and cross-market analytics. An off-chain order book can tell you the current bid and your own fills; it cannot tell you **who the best traders on Polymarket are, how much every wallet has won or lost, which markets are seeing whale accumulation, or how flows move between outcomes across the protocol**. That is what on-chain trade data, exposed as GraphQL, subscriptions, and Kafka streams, is for. In most serious production stacks the two complement each other: CLOB for execution and event/market metadata, Bitquery for trader analytics, P&L, streaming, history, and anything that needs GROUP BY. ## What changed in 2026 Three changes on Polymarket's side are worth knowing before you pick a stack: - **New exchange contracts and pUSD.** In April 2026 Polymarket moved trading to new CTF Exchange contracts (`0xE111180000d2663C0091e4f400237545B87B996B` and the Neg Risk exchange `0xe2222d279d744050d28e00520010520000310F59`) and switched collateral from USDC.e to **pUSD**, a USDC-backed wrapper. Order structs and SDK package names changed with it. Bitquery queries filtered by `ProtocolName: "polymarket"` keep working; only hardcoded addresses need updating. - **Data API v2.** `data-api.polymarket.com/v2` adds cursor pagination and a shared response envelope for wallet activity, market data and trader analytics. That makes it a better per-account and per-market API than v1, but you still page through REST, so protocol-wide aggregates remain a client-side job. - **In-house indexing.** In September 2026 Polymarket said it now indexes Polygon itself (built on rindexer) with Goldsky as backup, which makes its own app and APIs faster to reflect on-chain events. For builders this mostly improves the official APIs' freshness; it does not add GraphQL aggregation, Kafka delivery, joins with other Polygon data, or warehouse exports. None of this moves the line between the two stacks. The official APIs got faster and easier to page through for execution and account views, and cross-market analytics and streaming still need an on-chain index. ## Polymarket's official API surface Polymarket splits its public surface into three REST APIs plus two WebSocket streams. The **Gamma API** at gamma-api.polymarket.com is the market-discovery layer. It indexes every event, market, tag, sports category, and series on the platform, and exposes endpoints such as /events, /markets, /public-search, /tags, /series, and /sports, along with lookups by market ID or slug. It requires no authentication, supports filtering by active/closed and tag, sorting by 24h volume, liquidity, start/end date, and returns Polymarket's canonical structure: events are top-level questions, markets are tradable binary outcomes nested inside them. Rate limits are generous for read traffic, at roughly 4,000 requests per 10 seconds overall, with per-endpoint caps such as /events at 500/10s and /markets at 300/10s. Gamma does not serve price history, on-chain balances, or order placement. The **CLOB API** at clob.polymarket.com is the trading layer. It handles order submission, order-book reads, price history (/prices-history), trade history, and user order management. Authenticated endpoints use API-key credentials derived from wallet signatures (Ethereum/Polygon private key signing with HMAC-SHA256 request signing), while order-book and price endpoints are public. Order submission is rate-limited in the order of 10 requests per second, and WebSocket connections are capped at around 5 concurrent per IP. The CLOB is also where you subscribe to the real-time market and user channels at wss://ws-subscriptions-clob.polymarket.com/ws/market and /ws/user. The **Data API** at data-api.polymarket.com (now with a **v2** at `/v2` using cursor pagination) is the user-data layer. Endpoints like GET /positions, GET /activity, and GET /trades fetch a wallet's current positions (size, average price, cash PnL, % PnL), trade history, and activity feed. Profile fields (name, pseudonym, bio, profile image) are joined inline. Polymarket also runs a public-profile endpoint on Gamma (`GET /public-profile?address=`) and a Bridge API for deposits/withdrawals. Finally, the **Real-Time Data Socket** at wss://ws-live-data.polymarket.com broadcasts activity and trades. In practice it is most useful for fills and order-book deltas; filtering by market_slug or event_slug has known gaps, and Polymarket does not currently support unsubscribing from channels mid-session. A few characteristics fall out of this design. The official APIs reflect the [polymarket.com](https://polymarket.com) application view: canonical market metadata, your own positions, and real-time fills, at high rate limits. They are tightly scoped to single-entity reads. They do not natively support aggregate queries like "top 100 wallets by Polymarket volume in the last 5 days" or "largest whale trades across every Bitcoin market", and they do not expose on-chain-level fields like transaction hash, block time, log signature, condition ID events, or split/merge settlements in a single query. Historical data depth is whatever the CLOB has persisted, which is fine for order history but less convenient for ad-hoc aggregates. ## Bitquery's Polymarket API Bitquery takes a different approach. It indexes the Polymarket contracts on Polygon directly, normalizes the events into a GraphQL schema, and exposes the same operations as REST queries, GraphQL subscriptions (WebSocket), or Kafka streams. Every Polymarket query lives under EVM(network: matic) with dataset: realtime, and filters on Marketplace.ProtocolName: "polymarket". The pages under `docs/examples/polymarket-api/` in this repo correspond to these guides: - [Polymarket API overview](/docs/examples/polymarket-api/): the entry point, covering the core PredictionTrades query, live subscriptions, whale trades, and top buyers and sellers. - [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/): filter markets by market_slug, condition_id, or token_id through PredictionManagements. - [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/): recent trade counts, collateral totals, and distinct-market counts for any wallet, plus guidance on which fields belong to Polymarket's Profile, Gamma, or Bridge APIs. - [Polymarket Advanced Analytics API](/docs/examples/polymarket-api/polymarket-advanced-analytics-api/): USDC TVL in core contracts, daily volume, maker/taker splits, order flow by market, whale subscriptions, settlement flows, and top markets by volume. - [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/): cricket, sports, and esports markets filtered by ResolutionSource, description, or outcome label. - [Polymarket Commodity API](/docs/examples/polymarket-api/polymarket-commodity-api/): oil, gold, and commodity-linked prediction markets. - [Bitcoin Up or Down Polymarket API](/docs/examples/polymarket-api/bitcoin-polymarket-api/): BTC direction markets, live odds, and top winners from settlements. - [Polymarket Wallet Realized PnL](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/): PnL derived from on-chain fills. Underneath, Bitquery's primary operations are PredictionTrades (buys and sells with buyer, seller, amount, price, USD-denominated collateral, and full market metadata), PredictionManagements (creation, resolution, and other lifecycle events), and PredictionSettlements (splits, merges, redemptions). Each can be run as a query for history or as a subscription for live streaming; swapping the single keyword is the only change required. For low-latency pipelines, Bitquery also publishes Kafka topics matic.predictions.proto and matic.broadcasted.predictions.proto (mempool), which require separate credentials. Because everything is GraphQL, aggregations, limitBy, orderBy: descendingByField, and computed expressions (e.g. `calculate(expression: "$buyUSD + $sellUSD")`) are built into the query language. That is what makes queries like "top 100 Polymarket markets by volume over a window" or "all whale trades above $10k across Polymarket in real time" one query instead of a client-side batch job over the CLOB. The one caveat is retention on the live endpoint: dataset: realtime holds roughly the **last 7 days**. For longer windows, a **full historical Polymarket dataset (Parquet on S3, Snowflake, BigQuery) is available via [Bitquery Cloud](/docs/cloud/polymarket/) on request**, with no need to self-persist the stream. ## Side-by-side comparison | Capability | Polymarket official APIs | Bitquery Polymarket API | | --- | --- | --- | | **Place orders** | Yes, via CLOB /order with signed requests | No (read-only) | | **Live order book (bids/asks/depth)** | Yes, via CLOB REST + WS market channel | No; trades and settlements, not the L2 book | | **Event, market, tag, series metadata** | Yes, via Gamma API, canonical | Partial: market ID, question, outcomes, condition ID, resolution source (on-chain derived) | | **User positions / PnL** | Yes, via Data API /positions | Derivable from trades + settlements; [realized PnL example](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/) | | **User activity feed** | Yes, via Data API /activity | Yes, via [Wallet API](/docs/examples/polymarket-api/polymarket-wallet-api/) | | **Price history per token** | Yes, via CLOB /prices-history | Yes, via PredictionTrades with time filter | | **On-chain fields** (tx hash, block time, log signatures, condition ID events) | Limited | Yes, native on every row | | **Aggregations** (top N markets by volume, top wallets, maker/taker split) | Client-side | Native GraphQL: sum, count(distinct:), limitBy, orderBy: descendingByField | | **Whale trade filtering across all markets** | Manual | One subscription; see the [whale trades example](/docs/examples/polymarket-api/) | | **Real-time streaming** | WebSocket (ws-subscriptions-clob, ws-live-data), cannot unsubscribe, some filter gaps | GraphQL subscriptions plus Kafka matic.predictions.proto | | **Settlement (split / merge / redeem) analytics** | Indirect | Native; see [Advanced Analytics](/docs/examples/polymarket-api/polymarket-advanced-analytics-api/) | | **Cross-market vertical APIs** (sports, commodity, BTC up/down) | Filter manually on Gamma | Dedicated guides: [Sports](/docs/examples/polymarket-api/polymarket-sports-api/), [Commodity](/docs/examples/polymarket-api/polymarket-commodity-api/), [Bitcoin Up or Down](/docs/examples/polymarket-api/bitcoin-polymarket-api/) | | **TVL / collateral (pUSD, USDC.e) custody balances** | Not exposed | Yes, via TransactionBalances on Conditional Tokens + neg-risk collateral | | **Historical depth** | Full CLOB history | realtime dataset ~last 7 days; full history as [Parquet exports](/docs/cloud/polymarket/) for S3, Snowflake, BigQuery | | **Auth** | API-key + HMAC-SHA256 (CLOB); none (Gamma) | Bitquery API token; Kafka requires separate creds | | **Rate limits** | 4,000/10s (Gamma), ~10 orders/sec (CLOB) | No data or rate limits on streams; Kafka for enterprise streaming and scaling to 1,000+ simultaneous users | ## When to use which Reach for the **official APIs** when you are executing orders, showing a user their own positions and PnL as Polymarket displays them, reading live order-book depth for market-making or pricing, pulling the canonical event tree (tags, series, sports categories, cover images, resolution links), or wiring into Polymarket's deposit/withdrawal bridge. These are the authoritative source for "what does [polymarket.com](https://polymarket.com) show right now for this account." Reach for **Bitquery** when you need GraphQL aggregations, server-side filtering, and on-chain fields in a single request. Typical use cases include: - Leaderboards of top buyers and sellers by USD volume over a rolling window. - Whale monitors streaming every trade above a USD threshold across all markets from a single subscription. - Volume rankings for the top 100 Polymarket markets by buy-plus-sell USD in the last 24 hours. - Category dashboards for BTC up/down, sports, or commodity markets with live odds. - Settlement flow analytics using split/merge/redeem totals as a liquidity and open-interest proxy. - Low-latency pipelines consuming matic.predictions.proto from Kafka for mempool-level insight. - Cross-wallet clustering for forensic tracing of Polymarket activity. More advanced (alpha-focused) use cases: - **Alpha signals from order flow**: net buy-pressure imbalance per market as a directional signal, with USD-weighted buy vs sell sums over rolling windows. - **Smart-money tracking**: identify historically profitable wallets via realized PnL and copy their live trades through whale subscriptions filtered on Buyer/Seller. - **Mean-reversion on odds dislocations**: detect markets where outcome price moves sharply against thin liquidity by joining trade size to pre-trade mid-price. - **Momentum from whale accumulation**: flag condition IDs where top-N wallets turn net-long above a threshold within the last hour. - **News-vs-odds divergence**: cross-reference ResolutionSource updates against live odds subscriptions to surface mispriced markets around breaking events. - **Event-driven alpha**: pre-load markets by slug or tag (elections, earnings, fights, matches) and subscribe to whale trades as the event unfolds. - **Cross-market arbitrage**: correlate implied probabilities between related Polymarket markets (e.g. winner vs margin-of-victory) and flag inconsistent pricing. - **Settlement/redemption signals**: watch PredictionSettlements for large merges or redeems as a leading indicator that informed traders are closing positions. - **Mempool front-running detection**: use matic.broadcasted.predictions.proto to spot pending whale trades before they confirm, useful for both MEV research and defensive execution. - **Cohort-based backtests**: segment wallets by realized-PnL percentile over a window, replay their trades against historical odds, and test whether following the top cohort produces alpha. - **Liquidity-migration detection**: track USDC TVL changes across Conditional Tokens and neg-risk collateral to flag when capital is rotating between market clusters. - **Sentiment indices**: roll up weighted outcome probabilities across a category (macro, geopolitics, crypto) into a single index consumable by trading models. - **Risk and exposure monitoring**: for funds or market makers, aggregate position exposure across every condition ID a wallet has touched in real time. A reasonable production architecture looks like this: - **Gamma** for canonical market catalogs. - **CLOB** for execution and order-book depth. - **Data API** for user-facing account screens. - **Bitquery** for analytics, alerting, long-running streams, and any query that needs GROUP BY. ## A practical starting set If you are new to Bitquery's Polymarket coverage, four queries exercise most of the surface area: 1. The basic recent-trades query from the [Polymarket API overview](/docs/examples/polymarket-api/), which confirms your API token and shows the shape of a PredictionTrade. 2. The volume-ranking query from the same doc, which demonstrates limitBy, orderBy: descendingByField, and the computed sumBuyAndSell expression. 3. A live whale-trade subscription: swap the query keyword for subscription and filter on `CollateralAmountInUSD: { gt: "10000" }` to push events as they happen. 4. The daily volume and maker/taker split from the [Advanced Analytics page](/docs/examples/polymarket-api/polymarket-advanced-analytics-api/), which shows how far GraphQL takes you before any client-side aggregation is required. All of these are runnable from the Bitquery IDE (linked inline from each docs page) before you ever issue an API token. ## See it live If you want a visual reference for what Bitquery's Polymarket data looks like rendered as a live dashboard, showing top markets, whale trades, odds, and volumes, the [DexRabbit Polymarket Predictions dashboard](https://dexrabbit.bitquery.io/polymarket-predictions) runs directly on these APIs. Each panel ships with a "Get API" button that exposes the exact GraphQL query behind the chart, which you can copy into your own stack. ## Which one to start with The official Polymarket APIs tell you **what Polymarket the application knows about your account and the current book**. Bitquery tells you **what actually happened on-chain across every Polymarket market, in a shape you can aggregate, stream, and replay**. Picking the right one, or more often using both, comes down to whether your next query starts with "place this order" or "across all markets…". ## Further reading Canonical Bitquery Polymarket references: - [Polymarket API, Trade, Prices & Market Data](/docs/examples/polymarket-api/) - [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/) for CTF Exchange, condition_id, and token_id lookups - [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/) - [Polymarket Advanced Analytics API](/docs/examples/polymarket-api/polymarket-advanced-analytics-api/) Vertical guides: - [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/) - [Polymarket Commodity API](/docs/examples/polymarket-api/polymarket-commodity-api/) - [Bitcoin Up or Down Polymarket API](/docs/examples/polymarket-api/bitcoin-polymarket-api/) - [Polymarket Wallet Realized PnL](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/) Infrastructure and live reference: - [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) - [Polymarket historical data exports](/docs/cloud/polymarket/) - [bitquery/polymarket-api on GitHub](https://github.com/bitquery/polymarket-api) - [DexRabbit Polymarket Predictions dashboard](https://dexrabbit.bitquery.io/polymarket-predictions) --- ## Polymarket API: Trades, Prices, Wallet PnL and Live Streams URL: https://docs.bitquery.io/docs/examples/polymarket-api/ Query Polymarket on-chain data with Bitquery GraphQL: trades, outcome prices, wallet PnL, market lifecycle and oracle resolutions, with WebSocket and Kafka streams. # Polymarket API - Prediction Market Data The Bitquery Polymarket API provides prediction market data on Polygon via GraphQL. Use `dataset: realtime` on `EVM` queries for `PredictionTrades`, `PredictionSettlements`, and related prediction-market APIs. This dataset retains roughly the **last 7 days**. Use it to query trades, settlements, market metadata, and volume; filter by condition_id, outcome token, or trade size; and access data via REST, WebSocket subscriptions, or Kafka streams. Filter by Polymarket using `ProtocolName: "polymarket"` or `Marketplace.ProtocolName` in your queries. If you are evaluating data providers, the [Polymarket API product page](https://bitquery.io/products/polymarket-api) summarizes market, trade and position coverage with plans and real-time delivery options. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: :::note Dataset Polymarket prediction-market data on Polygon requires `dataset: realtime` (~**7 days** of rolling history). Add `dataset: realtime` to the `EVM(...)` argument in your GraphQL examples when using prediction trades and settlements. For anything older, use the [Polymarket historical data exports](/docs/cloud/polymarket/). ::: --- ## Bitquery Polymarket API vs the official Polymarket API Polymarket's own APIs (Gamma, CLOB, Data API) are the right choice for **placing orders**, reading the **order book**, and looking up one market or one account at a time. Bitquery indexes Polymarket **on-chain**, straight from the exchange and Conditional Tokens contracts on Polygon, so it fits the jobs the official API is not built for: | Job | Official Polymarket API | Bitquery Polymarket API | | --- | --- | --- | | Place and manage orders, order book depth | Yes (CLOB) | No, read-only data | | Aggregate across **all markets** in one request (top markets, top traders, volume by day) | Paginate and compute yourself | One GraphQL query with `sum`, `count`, `limitBy` | | **Whale and insider detection** across every market | Manual | One subscription or query, see [insider detection](/docs/examples/polymarket-api/polymarket-insider-detection-api/) | | **Wallet PnL** and cross-market activity | Per-account endpoints | [Wallet API](/docs/examples/polymarket-api/polymarket-wallet-api/) and [realized PnL](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/) | | Join with other Polygon data (funding transfers, balances, DEX trades) | No | Same GraphQL endpoint and schema | | Delivery | REST and WebSocket | GraphQL, WebSocket subscriptions, **Kafka**, and [Parquet historical exports](/docs/cloud/polymarket/) | The two work together. Trade through the official CLOB API, and run analytics, alerts and backfills on Bitquery. A longer comparison is in [Polymarket API vs Bitquery Polymarket API](/docs/API-Blog/polymarket-api-vs-bitquery-polymarket-api/). :::info Polymarket contracts and collateral (2026 migration) Polymarket moved trading to new exchange contracts in April 2026 and now settles in **pUSD** (`0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`), a USDC-backed ERC-20 that wraps USDC.e. Current Polygon addresses: - **CTF Exchange:** `0xE111180000d2663C0091e4f400237545B87B996B` - **Neg Risk CTF Exchange:** `0xe2222d279d744050d28e00520010520000310F59` - **Conditional Tokens (CTF):** `0x4D97DCd97eC945f40cF65F87097ACe5EA0476045` (unchanged) Inside the Conditional Tokens contract, custody is still USDC.e; pUSD is what traders hold and what `CollateralToken` returns on trades. Queries that filter by `ProtocolName: "polymarket"` need no change. If you hardcode exchange or collateral addresses, use the ones above; the previous exchanges (`0x4bFb41d5…8982E`, `0xC5d563A3…0f80a`) no longer emit `OrderFilled`. ::: ## Polymarket API guides - [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/): activity, volume and market counts by wallet - [Polymarket Wallet Realized PnL](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/): profit and loss per trader - [Polymarket Advanced Analytics API](/docs/examples/polymarket-api/polymarket-advanced-analytics-api/): TVL, daily volume, order flow, whale streams - [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/): look up markets by slug, condition ID or outcome token - [Polymarket Insider Detection API](/docs/examples/polymarket-api/polymarket-insider-detection-api/): fresh wallets, funding sources, pre-resolution bets - [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/), [Commodity API](/docs/examples/polymarket-api/polymarket-commodity-api/), [Bitcoin API](/docs/examples/polymarket-api/bitcoin-polymarket-api/) and [AI & Tech API](/docs/examples/polymarket-api/polymarket-ai-tech-api/): category-specific markets - [Polymarket historical data (Parquet, S3, Snowflake, BigQuery)](/docs/cloud/polymarket/): full-history backfills - [Polymarket Telegram alerts bot](/docs/usecases/polymarket-tg-alerts-bot/): tutorial - [bitquery/polymarket-api on GitHub](https://github.com/bitquery/polymarket-api): SDKs and runnable examples - [Bitquery MCP server](/docs/mcp/mcp-server/): ask Polymarket questions from Claude, ChatGPT or Cursor, no GraphQL needed ## Can I ask Polymarket questions in plain English instead of writing GraphQL? Yes. The [Bitquery MCP server](https://bitquery.io/products/bitquery-mcp-server?ref=docs-polymarket-api) puts the same on-chain Polymarket data behind tools that Claude, ChatGPT, Cursor, VS Code and Codex can call. You type a question, the agent picks the tool, and the rows come back in the chat. It reads data only. It cannot place or cancel orders. | Ask | Tool the agent calls | | --- | --- | | What are the most traded Polymarket markets in the last 24 hours? | `polymarket_top_markets` | | Find the open markets about the Fed rate decision. | `polymarket_find_markets` | | What are the current odds on this market? | `polymarket_market_odds` | | Show the latest trades in this market. | `polymarket_market_trades` | | Who traded the most volume in this market? | `polymarket_top_traders` | | What has this wallet traded, and what does it still hold? | `polymarket_trader_activity`, `polymarket_trader_positions` | | Was this market's resolution disputed? | `polymarket_market_disputes` | `polymarket_top_traders` ranks wallets by volume traded. A wallet at the top may be a market maker that is flat or losing, so do not read that list as a profit ranking. To connect from Claude Code: ```bash claude mcp add --transport http bitquery https://mcp.bitquery.io ``` Other clients take the same URL. Steps for each are in the [MCP server install guide](/docs/mcp/mcp-server/), and plans are on the [MCP server page](https://bitquery.io/products/bitquery-mcp-server?ref=docs-polymarket-api#pricing). ## What Polymarket data can I get with Bitquery? Bitquery provides **trades and prices** (buy/sell activity, volume, outcome prices), **positions and redemptions** (splits, merges after resolution), **market lifecycle** (creation, resolution, oracle outcomes), **market metadata** (condition ID, slug, token filters via CTF Exchange), **wallet activity** (volume and market counts by address), and **real-time streaming** (Kafka for trades and settlements). All data is on Polygon (`network: matic`) with `dataset: realtime` (about the **last 7 days**). Use the links below to find the right API. ### How do I get Polymarket trades and prices? Use the **[Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api)** to query buy/sell activity, track prices, and monitor volume across Polymarket. [→ Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api) ### How do I track positions and redemptions? Use the **[Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api)** to query splits, merges, and redemptions after market resolution. [→ Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api) ### How do I get market lifecycle data? Use the **[Prediction Market API](/docs/examples/prediction-market/prediction-market-api)** to query market creation, resolution, and complete lifecycle events. [→ Prediction Market API](/docs/examples/prediction-market/prediction-market-api) ### How do I query Polymarket CTF exchange data (condition ID, slug, token)? Use the **[Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api)** (CTF Exchange) to query market data by slug, condition ID, outcome token, or event. Combine with [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api) and [Prediction Market API](/docs/examples/prediction-market/prediction-market-api) for full lifecycle. [→ Polymarket Markets API (CTF Exchange)](/docs/examples/polymarket-api/polymarket-markets-api) ### How do I get Polymarket wallet and user activity? Use the **[Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api)** to query recent activity, volume, and market counts by wallet address. [→ Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api) ### How do I build TVL, open-interest-style metrics, maker/taker splits, and order-flow analytics? Use the **[Polymarket Advanced Analytics API](/docs/examples/polymarket-api/polymarket-advanced-analytics-api)** for GraphQL examples: USDC balances in core contracts, daily volume and maker/taker splits, order-flow by market, whale-trade subscriptions, settlement flows, and top markets by volume. [→ Polymarket Advanced Analytics API](/docs/examples/polymarket-api/polymarket-advanced-analytics-api) ### How do I stream Polymarket data in real time? Use **Kafka Streams** for low-latency Polymarket data. Subscribe to trades, settlements, and market events as they happen. Available Kafka topics: - `matic.predictions.proto`: Raw prediction market events - `matic.broadcasted.predictions.proto`: Mempool prediction market data Note: Kafka streaming requires separate credentials. [Contact support](https://t.me/bloxy_info) or email support@bitquery.io for access. [→ Kafka Streams Documentation](/docs/streams/kafka-streaming-concepts) --- ## How do I get real-time Polymarket trades using Bitquery? Use Bitquery's `PredictionTrades` GraphQL query filtered by `ProtocolName: "polymarket"` on Polygon (`network: matic`). For live streaming, use a subscription or Kafka. [Run in Bitquery IDE](https://ide.bitquery.io/prediction_trades) ```graphql query { EVM(network: matic) { PredictionTrades( limit: { count: 10 } orderBy: { descending: Transaction_Time } where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } OutcomeTrade: { IsOutcomeBuy: true } } } ) { Transaction { Hash Time } Trade { Prediction { Question { Title MarketId } Outcome { Label } CollateralToken { Symbol } } OutcomeTrade { Buyer Seller Amount CollateralAmount } } } } } ``` --- ## Query Examples ### How do I subscribe to live Polymarket trades? Use a GraphQL subscription on `PredictionTrades` filtered by `Marketplace.ProtocolName: "polymarket"` to stream trades as they happen on Polygon: ```graphql subscription PredictionTradesStream { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount Price IsOutcomeBuy } Prediction { Question { Title MarketId } Outcome { Label } CollateralToken { Symbol } } } Transaction { Hash } } } } ``` ### How do I filter Polymarket trades by condition_id or asset ID? Use `PredictionManagements` with `OutcomeToken.AssetId` or `Condition.Id` in the where clause. For condition_id, market_slug, and combined filters, see the [Polymarket Markets API (CTF Exchange)](/docs/examples/polymarket-api/polymarket-markets-api/). [Run query](https://ide.bitquery.io/Filter-markets-by-asset-ID-Polymarket) ```graphql query MarketsByAssetId($assetIds: [String!]) { EVM(network: matic) { PredictionManagements( limit: { count: 50 } orderBy: { descending: Block_Time } where: { Management: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } OutcomeToken: { AssetId: { in: $assetIds } } } } } ) { Block { Time Number } Transaction { Hash Time } Management { EventType Prediction { Condition { Id QuestionId Outcomes { Id Index Label } } Question { MarketId Title Image ResolutionSource CreatedAt } CollateralToken { Symbol Name } Outcome { Id Label } OutcomeToken { AssetId SmartContract } } } } } } ``` **Variables (example):** ```json { "assetIds": [ "19443761038809394075988687891855393102730862479306560066876868792031660494383" ] } ``` ### How do I get Polymarket markets ranked by volume? Use Bitquery's `PredictionTrades` with `limitBy: Trade_Prediction_Question_Id`, `orderBy: descendingByField: "sumBuyAndSell"`, and `sum(of: Trade_OutcomeTrade_CollateralAmountInUSD)` grouped by buy/sell to rank markets by total volume over a time window. [Run query](https://ide.bitquery.io/Top-Polymarket-Markets-by-Volume) ```graphql query questionsByVolume($time_ago: DateTime) { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: {Success: true} Block: {Time: {since: $time_ago}} Trade: {Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}}} } limit: {count: 100} orderBy: {descendingByField: "sumBuyAndSell"} limitBy: {by: Trade_Prediction_Question_Id} ) { Trade { Prediction { Question { Id Image Title CreatedAt } } } buyUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: {Trade: {OutcomeTrade: {IsOutcomeBuy: true}}} ) sellUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: {Trade: {OutcomeTrade: {IsOutcomeBuy: false}}} ) sumBuyAndSell: calculate(expression: "$buyUSD + $sellUSD") } } } ``` **Variables (example):** ```json { "time_ago": "2026-02-24T07:22:21Z" } ``` ## How do I get real-time odds for all active Polymarket markets? Use a GraphQL subscription on `PredictionTrades` with `limitBy: { count: 1, by: [Trade_Prediction_Question_Id, Trade_Prediction_Outcome_Label] }` filtered by `ProtocolName: "polymarket"` to stream the latest odds for every active market. Returns live price and outcome probabilities. [Run in Bitquery IDE](https://ide.bitquery.io/How-do-I-get-real-time-odds-for-all-active-Polymarket-markets) ```graphql subscription { EVM(network: matic) { PredictionTrades( limitBy: {count: 1, by: [Trade_Prediction_Question_Id, Trade_Prediction_Outcome_Label]} where: {Trade: {Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}}}} ) { Trade { OutcomeTrade { Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol AssetId } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Label } } } } } } ``` ## How do I track high-value or whale trades on Polymarket? Use a GraphQL subscription on `PredictionTrades` filtered by `CollateralAmountInUSD: { gt: "10000" }` and `ProtocolName: "polymarket"` to monitor trades exceeding $10,000 USD in real time. Ideal for detecting whale activity and large market movements. [Run in Bitquery IDE](https://ide.bitquery.io/How-do-I-track-high-value-or-whale-trades-on-Polymarket) ```graphql subscription { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: {Success: true} Trade: { Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}} OutcomeTrade: {CollateralAmountInUSD: {gt: "10000"}} } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## What are the largest Polymarket trades in the last 7 days? Use `PredictionTrades` with `orderBy: { descending: Trade_OutcomeTrade_CollateralAmountInUSD }` and filter by `ProtocolName: "polymarket"` to get the top 10 largest trades by USD volume. Add `Block.Time: { since: $time_ago }` for a custom time window. [Run in Bitquery IDE](https://ide.bitquery.io/What-are-the-largest-Polymarket-trades-in-the-last-7-days) ```graphql query { EVM(network: matic) { PredictionTrades( limit: {count: 10} orderBy: {descending: Trade_OutcomeTrade_CollateralAmountInUSD} where: { TransactionStatus: {Success: true} Trade: { Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}} OutcomeTrade: {CollateralAmountInUSD: {gt: "10000"}} } } ) { Block{ Time Date } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## How do I get top buyers and sellers on Polymarket by volume? Use `PredictionTrades` with `limitBy` and `sum(of: Trade_OutcomeTrade_CollateralAmountInUSD)` grouped by Buyer (or Seller) to rank the top 100 wallets by volume over the last 5 days. Useful for leaderboards, whale tracking, and trader analytics. [Run in Bitquery IDE](https://ide.bitquery.io/How-do-I-get-top-buyers-and-sellers-on-Polymarket-by-volume) ```graphql query { EVM(network: matic) { buyers: PredictionTrades( limit: {count: 100} orderBy: {descendingByField: "volume_usd"} where: { Block: {Time: {since_relative: {days_ago: 5}}} TransactionStatus: {Success: true} Trade: {Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}}} } ) { volume_usd: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) Trade { OutcomeTrade { Buyer } } } sellers: PredictionTrades( limit: {count: 100} orderBy: {descendingByField: "volume_usd"} where: { Block: {Time: {since_relative: {days_ago: 5}}} TransactionStatus: {Success: true} Trade: {Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}}} } ) { volume_usd: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) Trade { OutcomeTrade { Seller } } } } } ``` ## How do I count trades for a specific Polymarket trader? Use `PredictionTrades` with `any` filter on `Buyer` or `Seller` to return the total trade count for a wallet. Add `ProtocolName: "polymarket"` to restrict to Polymarket only. Replace the address with your target wallet. [Run in Bitquery IDE](https://ide.bitquery.io/How-do-I-count-trades-for-a-specific-Polymarket-trader) ```graphql query { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: {Success: true} any: [ {Trade: {Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}}, OutcomeTrade: {Buyer: {is: "0xd48165a42bb4eeb5971e5e830c068eef0890af35"}}}} {Trade: {Prediction: {Marketplace: {ProtocolName: {is: "polymarket"}}}, OutcomeTrade: {Seller: {is: "0xd48165a42bb4eeb5971e5e830c068eef0890af35"}}}} ] } ) { count } } } ``` --- ## How much Polymarket volume goes through each exchange contract? Group one day of trades by exchange contract and collateral token. This shows the split between the **CTF Exchange** and the **Neg Risk CTF Exchange**, and confirms the collateral token (pUSD) your integration should expect. [Run in Bitquery IDE](https://ide.bitquery.io/polymarket-volume-by-exchange-contract-and-collateral) ```graphql query PolymarketVolumeByExchange($date: String!) { EVM(dataset: realtime, network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } Block: { Date: { is: $date } } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } limit: { count: 10 } orderBy: { descendingByField: "volume_usd" } ) { Trade { Prediction { Marketplace { SmartContract ProtocolVersion } CollateralToken { Symbol SmartContract } } } volume_usd: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) trades: count } } } ``` **Variables (example):** ```json { "date": "2026-09-17" } ``` ## Support For questions and technical support: - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket Bitcoin Up or Down API & Websocket URL: https://docs.bitquery.io/docs/examples/polymarket-api/bitcoin-polymarket-api/ Polymarket Bitcoin Up or Down markets: stream trades via subscriptions and Kafka, get price odds, top traders, top winners, whale trades and wallets. # Polymarket Bitcoin Up or Down Prediction Odds API Query and stream **Polymarket Bitcoin up or down** prediction market trades and **Bitcoin price odds** via Bitquery’s GraphQL API. These markets ask whether Bitcoin will be **up** or **down** at a specific time (e.g. daily or weekly settlement). **What you can do with this API:** Build real-time dashboards, aggregate odds across markets, track top traders by volume, identify top winners from settlements, analyze market liquidity, monitor whale activity, backtest strategies, and power alerts or bots. ## How Bitcoin Up or Down markets are identified Trades are filtered by **Question.Title** containing **"Bitcoin Up or Down"**. | Filter | Use case | Where to apply | | ------------------ | ------------------------------- | ------------------------------------------- | | **Question.Title** | Bitcoin Up or Down markets only | `Trade.Prediction.Question.Title.includes` | | **ProtocolName** | Polymarket only (optional) | `Trade.Prediction.Marketplace.ProtocolName` | --- ## Real-time: Subscriptions and Kafka ### GraphQL subscriptions The **subscription** below streams live Bitcoin Up or Down trades as they occur on Polygon. Change `subscription` to `query` and add `limit` / `orderBy` for historical results. ### Kafka streams For **low-latency** consumption, prediction market data (including Bitcoin Up/Down) is available via **Kafka**: - `matic.predictions.proto`: Raw prediction market events (trades, creations, resolutions) - `matic.broadcasted.predictions.proto`: Mempool prediction market data Kafka requires **separate credentials**. See [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/). For access, [contact support](https://t.me/bloxy_info) or email support@bitquery.io. --- ## How do I stream Bitcoin Up or Down trades in real time? Subscribe to live Polymarket trades for markets whose question title includes **"Bitcoin Up or Down"**. Includes block time, call/log signatures, full outcome trade details (buyer, seller, amount, price, USD values), and prediction metadata (question, outcomes, collateral token, marketplace). [Run in Bitquery IDE](https://ide.bitquery.io/Bitcoin-Up-or-Down-Trades-Stream) ```graphql subscription { EVM(network: matic) { PredictionTrades( where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includes: "Bitcoin Up or Down" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## How do I get Bitcoin price odds for all active Polymarket up/down markets? This WebSocket subscription streams real-time odds (prices) for all active "Bitcoin Up or Down" Polymarket markets. Use a GraphQL subscription on `PredictionTrades` with `limitBy: { count: 1, by: [Trade_Prediction_Question_Id, Trade_Prediction_Outcome_Label] }` to stream the latest odds (Up/Down outcome prices) for every active Bitcoin Up or Down market. Each market returns one row per outcome with `Price`, `PriceInUSD`, and market metadata. [Run in Bitquery IDE](https://ide.bitquery.io/Odds-of-all-Bitcoin-up-and-down-markets) ```graphql subscription { EVM(network: matic) { PredictionTrades( orderBy: { descending: Block_Time } limitBy: { count: 1 by: [Trade_Prediction_Question_Id, Trade_Prediction_Outcome_Label] } where: { Trade: { Prediction: { Outcome: { Label: { in: ["Up", "Down"] } } Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includes: "Bitcoin Up or Down" } } } } } ) { Trade { OutcomeTrade { Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol AssetId } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Label } } } } } } ``` ## How do I get the latest Bitcoin price odds for a specific up/down market? To track a single specific Bitcoin Up or Down market, use its unique question ID (condition ID) with the following query. This lets you fetch real-time or latest odds for just that market: Use `PredictionTrades` with `limitBy: { count: 1, by: Trade_Prediction_Outcome_Label }` and filter by `Question.Id` (condition ID) to get the latest odds for a single Bitcoin Up or Down market. Returns one row per outcome (Up/Down) with `Price` and `PriceInUSD`. Use a `query` for one-time fetch or change to `subscription` for real-time updates. Replace `Question.Id` with any market's condition ID to query odds for other Polymarket markets. [Run in Bitquery IDE](https://ide.bitquery.io/Odds-of-a-specific-Bitcoin-up-and-down-market) ```graphql { EVM(network: matic) { PredictionTrades( orderBy: { descending: Block_Time } limitBy: { count: 1, by: Trade_Prediction_Outcome_Label } where: { Trade: { Prediction: { Outcome: { Label: { in: ["Up", "Down"] } } Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Id: { is: "0xd8c16674c7242c146cd9662906af3a442ba702d08f079885287ebc194ab0c271" } Title: { includes: "Bitcoin Up or Down" } } } } } ) { Trade { OutcomeTrade { Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol AssetId } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Label } } } } } } ``` For real-time odds via WebSocket, use the same query with `subscription` instead of `query`: ```graphql subscription { EVM(network: matic) { PredictionTrades( orderBy: { descending: Block_Time } limitBy: { count: 1, by: Trade_Prediction_Outcome_Label } where: { Trade: { Prediction: { Outcome: { Label: { in: ["Up", "Down"] } } Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Id: { is: "0xd8c16674c7242c146cd9662906af3a442ba702d08f079885287ebc194ab0c271" } Title: { includes: "Bitcoin Up or Down" } } } } } ) { Trade { OutcomeTrade { Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol AssetId } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Label } } } } } } ``` Replace `Question.Id` with any market's condition ID and adjust the `Title` filter to get odds for other Polymarket prediction markets. ## How do I get top traders of Bitcoin Up or Down markets by volume? This **query** returns the top 10 **buyers** and top 10 **sellers** by traded volume in Bitcoin Up or Down markets on Polymarket over the last 24 hours. Results are aggregated by trader address and ordered by `buy_amount` (buyers) or `sell_amount` (sellers). [Run in Bitquery IDE](https://ide.bitquery.io/Top-BuyersSellers-of-Bitcoin-up-down-market) ```graphql { EVM(network: matic) { Top_buyers: PredictionTrades( where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includes: "Bitcoin Up or Down" } } } OutcomeTrade: { IsOutcomeBuy: true } } Block: { Time: { since_relative: { hours_ago: 24 } } } } limit: { count: 10 } orderBy: { descendingByField: "buy_amount" } ) { Trade { OutcomeTrade { Buyer } } buy_amount: sum(of: Trade_OutcomeTrade_Amount) } Top_sellers: PredictionTrades( where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includes: "Bitcoin Up or Down" } } } OutcomeTrade: { IsOutcomeBuy: false } } Block: { Time: { since_relative: { hours_ago: 24 } } } } limit: { count: 10 } orderBy: { descendingByField: "sell_amount" } ) { Trade { OutcomeTrade { Buyer } } sell_amount: sum(of: Trade_OutcomeTrade_Amount) } } } ``` ## How do I get top winners of Bitcoin Up or Down markets by redemption volume? Use `PredictionSettlements` filtered by `EventType: "Redemption"` and `Question.Title` including "Bitcoin Up or Down" to return the top 10 holders by redeemed amount over the last hour. Useful for tracking which traders won the most on settled Bitcoin Up or Down markets. [Run in Bitquery IDE](https://ide.bitquery.io/Top-Winners-of-Bitcoin-up-down-market) ```graphql query MyQuery { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descendingByField: "redeemed_amount" } where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Settlement: { EventType: { is: "Redemption" } Prediction: { Question: { Title: { includes: "Bitcoin Up or Down" } } } } } ) { Settlement { Holder Prediction { Question { Title } } } redeemed_amount: sum(of: Settlement_Amounts_Amount) } } } ``` ## Monitoring High-Value Trades on Polymarket Bitcoin Markets Use the following WebSocket subscription to monitor live trades greater than $5,000 USD on Polymarket Bitcoin Up or Down markets. This is ideal for detecting whale activity and large market movements in real time. ```graphql subscription { EVM(network: matic) { PredictionTrades( where: {TransactionStatus: {Success: true}, Trade: {OutcomeTrade: {CollateralAmountInUSD: {gt: "5000"}}, Prediction: {Question: {Title: {includes: "Bitcoin Up or Down"}}}}} ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## How do I monitor specific wallets on Bitcoin Up or Down markets in real time? Stream every Bitcoin Up or Down trade where one of a given list of wallets is the **Buyer** or the **Seller**. Pass the wallets you want to watch in the `$wallets` variable. The `any` predicate matches a trade if the wallet appears on either side. Useful for wallet-level alerting bots, copy-trading signals, and PnL tracking dashboards. [Run in Bitquery IDE](https://ide.bitquery.io/montioring-specific-wallets-in-realtime-for-Bitcoin-Up-or-Down-markets) ```graphql subscription MyQuery($wallets: [String!]) { EVM(network: matic) { PredictionTrades( where: { any: [ { Trade: { OutcomeTrade: { Buyer: { in: $wallets } } } } { Trade: { OutcomeTrade: { Seller: { in: $wallets } } } } ] Trade: { Prediction: { Question: { Title: { includesCaseInsensitive: "Bitcoin Up or Down" } } Marketplace: { ProtocolName: { is: "polymarket" } } } } TransactionStatus: { Success: true } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` **Variables:** ```json { "wallets": [ "0x87a961f161681cc1e9b3af2b6542b95ef3c4bd70", "0x0bab932893a7efc76d8e0951366ba933ba9fd3be" ] } ``` ### Same query for other Up or Down markets The same wallet-monitoring pattern works for every Polymarket Up or Down market. Only the `Question.Title` filter changes. Open any of the pre-built IDE queries below to stream trades for the chain you care about: - [Solana Up or Down, monitor specific wallets](https://ide.bitquery.io/montioring-specific-wallets-in-realtime-for-Solana-Up-or-Down-markets_1) - [Ethereum Up or Down, monitor specific wallets](https://ide.bitquery.io/monitoring-specific-wallets-trades-in-realtime-for-Ethereum-up-or-down-market) - [XRP Up or Down, monitor specific wallets](https://ide.bitquery.io/monitoring-specific-wallets-trades-in-realtime-for-XRP-up-or-down-market) Each query exposes the same `$wallets` variable, so you can drop the same wallet list into all four and run them in parallel to cover every chain at once. ## Related APIs | Need | API | | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **All Polymarket trades & prices** | [Polymarket API](/docs/examples/polymarket-api/) / [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) | | **Filter by slug, condition, token** | [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/) | | **Settlements & redemptions** | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | **Market creation & resolution** | [Prediction Market API](/docs/examples/prediction-market/prediction-market-api/) | | **User & wallet activity** | [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/) | | **Real-time: Kafka streams** | [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) | --- ## Support - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket Commodity API URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-commodity-api/ Polymarket commodity markets for Gold and Crude Oil: new and resolved markets, outcome prices, OHLC, volumes, top redeemers, subscriptions and Kafka. # Polymarket Commodity API: Gold, Crude Oil & Commodity Markets Query **commodity-related prediction markets** on Polymarket: **Gold (GC)** (e.g. “Gold Up or Down”), **Crude Oil**, and other commodity price-direction markets. Use **PredictionManagements** for market creation and resolution events, **PredictionTrades** for prices and volume, and **PredictionSettlements** for redemptions and top redeemers. All data is on Polygon (`network: matic`). :::tip See it in action: DEXrabbit Polymarket showcase Check out our **Polymarket data showcase** on [DEXrabbit](https://dexrabbit.bitquery.io/polymarket-predictions) powered by Bitquery APIs. Explore live commodity markets: - **[Gold (GC) market example](https://dexrabbit.bitquery.io/polymarket-predictions/0x6a9b34f5f4b44a7d3dced5ac84b3300aa9ae18e163a9bb4c98b805b57bbc1abb)**: View current odds, trade statistics, volume, and top traders by PnL - **[Crude Oil market example](https://dexrabbit.bitquery.io/polymarket-predictions/0x9a1e4e09a4bb9321f9b3f4f04d4242f1c11046348f88d236eeb0ae038b336096)**: See price charts, trading activity, and market analytics Click the **"Get API"** buttons on any market page to get ready-to-use GraphQL queries for trade statistics, top traders by PnL, volume breakdowns, and more. ::: :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. See [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/). ::: --- ## How commodity markets are identified Markets are filtered by **Question.Title** using case-insensitive keywords. Use the same filters in **PredictionManagements**, **PredictionTrades**, or **PredictionSettlements** as needed. | Filter | Use case | Where to apply | |--------|----------|----------------| | **Question.Title** | Gold (GC) markets | `includesCaseInsensitive: "Gold (GC)"` | | **Question.Title** | Crude Oil markets | `includesCaseInsensitive: "crude oil"` | | **MarketId** | Specific market (prices, OHLC, volume) | `Question.MarketId` (replace with your market ID) | | **ProtocolName** | Polymarket only | `Marketplace.ProtocolName: "polymarket"` | **Network:** Polygon (`network: matic`). For full lifecycle and trade APIs, see the [Polymarket API](/docs/examples/polymarket-api/) overview and [Prediction Market API](/docs/examples/prediction-market/prediction-market-api). --- ## Latest Gold (GC) markets created Returns the 10 most recent **Created** events for Polymarket markets whose question title includes **"Gold (GC)"**, with full condition, outcomes, question, and collateral token details. [Run in Bitquery IDE](https://ide.bitquery.io/latest-created-gold-markets-on-polymarket_5) ```graphql query LatestMarketCreations { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } Prediction: { Question: { Title: { includesCaseInsensitive: "Gold (GC)" } } Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest Crude Oil markets created Returns the 10 most recent **Created** events for Polymarket markets whose question title includes **"crude oil"**, with full prediction metadata. [Run in Bitquery IDE](https://ide.bitquery.io/latest-created-crude-oil-markets-on-polymarket) ```graphql query LatestMarketCreations { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } Prediction: { Question: { Title: { includesCaseInsensitive: "crude oil" } } Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest resolved Gold (GC) markets Returns the 10 most recent **Resolved** events for Polymarket Gold (GC) markets, including the winning outcome and full prediction details. [Run in Bitquery IDE](https://ide.bitquery.io/latest-resolved-gold-markets_1) ```graphql query LatestMarketResolutions { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Resolved" } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: "Gold (GC)" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest resolved Crude Oil markets Returns the 10 most recent **Resolved** events for Polymarket Crude Oil markets. [Run in Bitquery IDE](https://ide.bitquery.io/latest-resolved-crudeoil-markets) ```graphql query LatestMarketResolutions { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Resolved" } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: "crude oil" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest price of outcomes of a Crude Oil market Returns the **latest trade price** (and price in USD) per outcome for a **single market** by `MarketId`. Replace `"1570893"` with the target Crude Oil market ID from [Polymarket](https://polymarket.com) or from the creation/resolution queries above. [Run in Bitquery IDE](https://ide.bitquery.io/latest-price-of-outcomes-of-a-crude-oil-market) ```graphql query { EVM(network: matic) { PredictionTrades( limitBy: { by: Trade_Prediction_OutcomeToken_AssetId, count: 1 } where: { TransactionStatus: { Success: true } Trade: { Prediction: { Question: { MarketId: { is: "1570893" } } } } } ) { Trade { OutcomeTrade { Price(maximum: Block_Time) PriceInUSD(maximum: Block_Time) } Prediction { OutcomeToken { Name AssetId } Outcome { Id Label } } } } } } ``` --- ## OHLC of an outcome of a Gold market Returns **OHLC** (Open, High, Low, Close) in USD for one outcome of a Gold market, bucketed by time (e.g. 1-minute intervals). Replace `MarketId` `"1606192"` and outcome `"Down"` with the desired market and outcome label (e.g. `"Up"` or `"Down"`). [Run in Bitquery IDE](https://ide.bitquery.io/OHLC-of-a-outcome-of-a-gold-market) ```graphql query { EVM(network: matic) { PredictionTrades( limit: { count: 10 } orderBy: { descending: Block_Time } where: { TransactionStatus: { Success: true } Trade: { Prediction: { Question: { MarketId: { is: "1606192" } } Outcome: { Label: { is: "Down" } } } } } ) { Block { Interval: Time(interval: { count: 1, in: minutes }) } Trade { OutcomeTrade { Open: PriceInUSD(minimum: Block_Time) High: PriceInUSD(maximum: Trade_OutcomeTrade_PriceInUSD) Low: PriceInUSD(minimum: Trade_OutcomeTrade_PriceInUSD) Close: PriceInUSD(maximum: Block_Time) } Prediction { OutcomeToken { Name AssetId } Outcome { Id Label } } } } } } ``` --- ## Outcome volumes of a Gold market (last 24 hours) Returns **total volume** and **volume by outcome** (e.g. Up vs Down) for a single Gold market in the last 24 hours. Replace `MarketId` `"1650002"` with your target market ID. [Run in Bitquery IDE](https://ide.bitquery.io/outcome-volumes-of-a-gold-market-in-last-24-hours_1) ```graphql query MarketVolumeByOutcome { EVM(network: matic) { PredictionTrades( where: { Block: { Time: { since_relative: { hours_ago: 24 } } } TransactionStatus: { Success: true } Trade: { Prediction: { Question: { MarketId: { is: "1650002" } } } } } ) { Trade { Prediction { Question { Title ResolutionSource Image MarketId Id CreatedAt } } } up_volume: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { Prediction: { Outcome: { Label: { is: "Up" } } } } } ) down_volume: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { Prediction: { Outcome: { Label: { is: "Down" } } } } } ) total_volume: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) } } } ``` --- ## Top 10 redeemers of a Gold market Returns the **top 10 holders** by **redeemed amount** (in USD) for a specific Gold market after resolution. Uses **PredictionSettlements** with `EventType: "Redemption"`. Replace the question title with your market’s exact **Question.Title** (e.g. from the creation/resolution queries). [Run in Bitquery IDE](https://ide.bitquery.io/top-10-traders-of-a-gold-market) ```graphql query TopRedeemersGoldMarket { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descendingByField: "redeemed_amount" } where: { Settlement: { EventType: { is: "Redemption" } Prediction: { Question: { Title: { is: "Gold (GC) Up or Down on March 18?" } } } } } ) { Settlement { Holder } redeemed_amount: sum(of: Settlement_Amounts_CollateralAmountInUSD) } } } ``` --- ## Real-time: Subscriptions and Kafka ### GraphQL subscriptions Any **query** on this page can be run as a **subscription**: use the same `where` filters and fields, and change the keyword `query` to `subscription`. You will receive new events (creations, resolutions, or trades) as they occur on Polygon over a WebSocket connection. ### Kafka streams For **low-latency** consumption, prediction market data (including commodity markets) is available via **Kafka**: - `matic.predictions.proto`: Raw prediction market events (creations, resolutions, trades) - `matic.broadcasted.predictions.proto`: Mempool prediction market data Kafka requires **separate credentials**. See [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/). For access, [contact support](https://t.me/bloxy_info) or email support@bitquery.io. --- ## Polymarket Insider & Fresh-Wallet Detection API URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-insider-detection-api/ Polymarket insider detection: catch large trades, check for fresh wallets, trace funding sources, find wallet clusters and score timing against outcomes. # Polymarket Insider & Fresh-Wallet Detection API Detect suspicious trading on **Polymarket** using on-chain data from Polygon. This page shows how to flag potential insiders by combining **fresh-wallet checks**, **USDC funding-source tracing**, **wallet clustering**, **large bets**, and **pre-resolution timing**. Bitquery runs **its own blockchain nodes** and **indexes, decodes, and parses** raw Polygon transactions. That gives you signals the off-chain Polymarket Gamma and Data APIs cannot provide, such as how old a wallet is, where its money came from, and which other wallets share the same funder. These on-chain signals are the foundation of every serious insider-detection and copy-trading tool. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. See [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/). ::: --- ## How Insider Detection Works on Polymarket A suspected insider leaves a specific footprint on-chain: 1. A **brand-new wallet** with little or no prior activity. 2. Funded by a **traceable USDC source**, often shared with other suspicious wallets. 3. A **large bet** in a low-volume or niche market. 4. Entered **shortly before resolution**, on the winning side. 5. **Redeems** the winnings and goes dormant. No single signal is proof. You combine them into a score and flag wallets that cross a threshold. The queries below produce each signal. ### Signal to Data Map | Signal | What you measure | Bitquery source | | ------ | ---------------- | --------------- | | **Fresh wallet** | Wallet age (first on-chain activity) vs first bet | `EVM.Transfers` (`dataset: combined`) | | **Funding source** | First USDC transfer into the wallet | `EVM.Transfers` (USDC.e) | | **Wallet cluster (Sybil)** | Other wallets funded by the same sender | `EVM.Transfers` grouped by receiver | | **Large bet in niche market** | Bet USD vs market 24h volume | `PredictionTrades` | | **Pre-resolution timing** | Hours between trade and resolution, winning side | `PredictionTrades` + `joinPredictionManagements` | | **Won and dormant** | Redemption amount, then no activity | `PredictionSettlements` + `Transfers` | :::tip Why `dataset: combined` The wallet-history queries below use `dataset: combined` so they search the **full chain history**, not just the realtime window. This matters for wallet age and funding traces, where you need the earliest activity ever recorded. ::: --- ## Step 1: Catch Large Trades in Real Time Start by streaming large Polymarket trades. Each event gives you a **buyer address** to investigate. Change `subscription` to `query` for historical results. [Run in Bitquery IDE](https://ide.bitquery.io/large-trades--on-polymarket) ```graphql subscription { EVM(network: matic) { PredictionTrades( where: { Trade: { OutcomeTrade: { CollateralAmountInUSD: { gt: "5000" } } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time } Trade { OutcomeTrade { Buyer Seller CollateralAmountInUSD Price IsOutcomeBuy } Prediction { Question { MarketId Title } Outcome { Index Label } } } Transaction { Hash } } } } ``` --- ## Step 2: Is the Buyer a Fresh Wallet? Look up the buyer's **earliest on-chain activity**. If the wallet's first transfer is close to the time of its first big bet, it is a fresh wallet and scores high. Replace the address with the buyer from Step 1. [Run in Bitquery IDE](https://ide.bitquery.io/freshwallet-check-for-polymarket) ```graphql query FreshWalletCheck { EVM(network: matic, dataset: combined) { Transfers( where: { Transfer: { Sender: { is: "0xe8a2057abd53d285f7bea590b8f1dff1f04454c2" } } } ) { earliest: Block { Time(minimum: Block_Time) } } } } ``` A recent `earliest` time means a young wallet. You can also add `count` to the selection to get the wallet's lifetime transfer count, another freshness signal. --- ## Step 3: Trace the Funding Source Find where the wallet's money came from. The **first inbound USDC transfer** is usually the original funder, often a centralized exchange withdrawal or a parent wallet. This query uses bridged USDC.e on Polygon (`0x2791bca1f2de4661ed88a30c99a7a9449aa84174`), which is still how wallets are funded; Polymarket wraps it into **pUSD** (`0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB`) for trading, so add that contract if you also want pUSD moves between wallets. [Run in Bitquery IDE](https://ide.bitquery.io/FundingSource-for-poylmarket) ```graphql query FundingSource { EVM(network: matic, dataset: combined) { Transfers( orderBy: { ascending: Block_Time } limit: { count: 5 } where: { Transfer: { Currency: { SmartContract: { is: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" } } Receiver: { is: "0xe8a2057abd53d285f7bea590b8f1dff1f04454c2" } } } ) { Block { Time } Transfer { Sender Amount } Transaction { Hash } } } } ``` --- ## Step 4: Find the Wallet Cluster (Sybil Detection) Take the funder from Step 3 and list **every other wallet it funded**. Wallets sharing a funder are likely controlled by the same operator. A large cluster placing correlated bets is a strong signal. [Run in Bitquery IDE](https://ide.bitquery.io/SiblingWallets-for-polymarket) ```graphql query SiblingWallets { EVM(network: matic, dataset: combined) { Transfers( limitBy: { by: Transfer_Receiver, count: 1 } where: { Transfer: { Currency: { SmartContract: { is: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174" } } Sender: { is: "0xe8a2057abd53d285f7bea590b8f1dff1f04454c2" } } } ) { Transfer { Receiver } received: sum(of: Transfer_Amount) } } } ``` --- ## Step 5: Timing and Outcome The final signals come from the trade itself. Check how soon before resolution the wallet entered, and whether it was on the winning side. - **Pre-resolution timing:** join the wallet's trades to the market's `Resolved` event and compute the lead time. Use the `joinPredictionManagements` pattern from the [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/). - **Won and dormant:** confirm the payout with a `Redemption` query on `PredictionSettlements`, then re-run Step 2 to check the wallet went quiet afterward. - **Realized profit and win rate:** see [Realized PnL & Win Rate for Polymarket Trader](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/). --- ## Scoring Model Combine the signals into a weighted score and flag wallets above a threshold. Tune the weights to your tolerance for false positives. ``` score = 0.30 * fresh_wallet + 0.25 * shared_funder_cluster + 0.20 * bet_pct_of_market_volume + 0.15 * entered_within_24h_of_resolution + 0.10 * won_and_went_dormant flag if score >= threshold ``` This mirrors how tools like CrowdIntel and PolyTrack work. Bitquery provides the on-chain primitives they otherwise have to reverse-engineer. --- ## Limitations Read this before acting on a flag. - These are **heuristics, not proof**. A fresh wallet or a shared funder is not evidence of wrongdoing. Exchange withdrawal addresses fund many unrelated users and create false clusters. - The "**entered before the news broke**" idea needs an off-chain news timestamp. On-chain you can only proxy it with the **resolution time** or a **sharp odds move** (see the OHLC query on the Sports API page). - Always verify the current **collateral token** and any neg-risk or Conditional Token Framework specifics in the IDE before relying on results. --- ## Related APIs | Need | API | | ---- | --- | | **Live odds, trades, volume by sport** | [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/) | | **Trader realized PnL & win rate** | [Realized PnL & Win Rate for Polymarket Trader](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/) | | **User & wallet activity** | [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/) | | **Trades, prices, whales** | [Polymarket API](/docs/examples/polymarket-api/) | | **Settlements & redemptions** | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | **Real-time: Kafka streams** | [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) | --- ## Support - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket Markets API URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-markets-api/ Polymarket markets lookup: find markets by condition_id, token_id (AssetId) or market_slug, and combine filters with a time range in GraphQL queries. # Markets API Find markets on Polymarket by **market slug**, **event slug**, **condition ID**, or **token ID**. Use these parameters to narrow results to specific markets or events when building apps that combine market metadata with [trades](/docs/examples/prediction-market/prediction-trades-api/) and [settlements](/docs/examples/prediction-market/prediction-settlements-api/). **Network:** Polygon (`network: matic`). For full lifecycle and trade data, see the [Polymarket API](/docs/examples/polymarket-api/) overview and the [Prediction Market API](/docs/examples/prediction-market/prediction-market-api/). --- ## Filter parameters All parameters are optional. You can combine multiple filters; results match markets that satisfy the criteria you provide. | Parameter | Type | Description | | ---------------- | -------- | ------------------------------------------------------------------- | | **market_slug** | string[] | Filter markets by market slug(s). Can provide multiple values. | | | **condition_id** | string[] | Filter markets by condition ID(s). Can provide multiple values. | | **token_id** | string[] | Filter markets by outcome token ID(s). Can provide multiple values. | ## Find markets by condition_id Use when you have on-chain condition IDs (e.g. from Main Polymarket Contract events). Condition IDs are hex strings; you can pass one or more. Get market lifecycle events (created/resolved) for one or more condition IDs. Condition IDs are hex strings (with or without `0x`). [Run Query](https://ide.bitquery.io/Filter-markets-by-condition-ID-Polymarket) ```graphql query MarketsByConditionId($conditionIds: [String!]) { EVM(network: matic) { PredictionManagements( limit: { count: 50 } orderBy: { descending: Block_Time } where: { Management: { Prediction: { Condition: { Id: { in: $conditionIds } } Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time Number } Transaction { Hash Time } Management { EventType Prediction { Condition { Id QuestionId Outcomes { Id Index Label } } Question { MarketId Title Image ResolutionSource CreatedAt } CollateralToken { Symbol Name } Outcome { Id Label } OutcomeToken { AssetId SmartContract } } } } } } ``` **Variables (example):** ```json { "conditionIds": [ "0x4567b275e6b667a6217f5cb4f06a797d3a1eaf1d0281fb5bc8c75e2046ae7e57" ] } ``` ## Find markets by token_id (AssetId) Use when you have outcome token IDs (e.g. from [Prediction Trades](/docs/examples/prediction-market/prediction-trades-api/) `OutcomeToken.AssetId`). Pass one or more AssetIds. [Run query](https://ide.bitquery.io/Filter-markets-by-asset-ID-Polymarket) ```graphql query MarketsByAssetId($assetIds: [String!]) { EVM(network: matic) { PredictionManagements( limit: { count: 50 } orderBy: { descending: Block_Time } where: { Management: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } OutcomeToken: { AssetId: { in: $assetIds } } } } } ) { Block { Time Number } Transaction { Hash Time } Management { EventType Prediction { Condition { Id QuestionId Outcomes { Id Index Label } } Question { MarketId Title Image ResolutionSource CreatedAt } CollateralToken { Symbol Name } Outcome { Id Label } OutcomeToken { AssetId SmartContract } } } } } } ``` **Variables (example):** ```json { "assetIds": [ "19443761038809394075988687891855393102730862479306560066876868792031660494383" ] } ``` ## Find markets by market_slug Filter by market slug or keyword using **Question.Title** with case-insensitive match. Use the URL slug (e.g. `bitcoin-up-or-down-july-25-8pm-et`) or a keyword (e.g. `XRP`). [Run query](https://ide.bitquery.io/Find-Markets-by-market_slug-on-Polymarket) ```graphql query MarketsByMarketSlug($marketSlug: String!) { EVM(network: matic) { PredictionManagements( limit: { count: 50 } orderBy: { descending: Block_Time } where: { Management: { Prediction: { Question: { Title: { includesCaseInsensitive: $marketSlug } } Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time Number } Transaction { Hash Time } Management { EventType Prediction { Condition { Id QuestionId Outcomes { Id Index Label } } Question { MarketId Title Image ResolutionSource CreatedAt } CollateralToken { Symbol Name } Outcome { Id Label } OutcomeToken { AssetId SmartContract } } } } } } ``` **Variables (example):** ```json { "marketSlug": "bitcoin-up-or-down-july-25-8pm-et" } ``` ### Combined filters (condition_id + time range) Combine **condition_id** with other filters such as a time window or event type in the same `where` clause. You can combine **condition_id** with other filters (e.g. time range, event type) in the same `where` clause. ```graphql query MarketsByConditionIdRecent($conditionIds: [String!]) { EVM(network: matic) { PredictionManagements( limit: { count: 20 } orderBy: { descending: Block_Time } where: { Block: { Time: { since_relative: { days_ago: 7 } } } Management: { Prediction: { Condition: { Id: { in: $conditionIds } } Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time } Management { EventType Prediction { Condition { Id } Question { MarketId Title } Outcome { Label } } } } } } ``` **Variables (example):** ```json { "conditionIds": [ "0x4567b275e6b667a6217f5cb4f06a797d3a1eaf1d0281fb5bc8c75e2046ae7e57" ] } ``` --- ## Related APIs | Need | API | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | **Trades & prices** | [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) | | **Settlements & redemptions** | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | **Market creation & resolution** | [Prediction Market API](/docs/examples/prediction-market/prediction-market-api/) / [Prediction Managements API](/docs/examples/prediction-market/prediction-managements-api/) | | **Polymarket overview** | [Polymarket API](/docs/examples/polymarket-api/) | | **User & wallet activity** | [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/) | | **On-chain condition & tokens** | Main Polymarket Contract (on-chain) | --- ## Polymarket Sports API URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-sports-api/ Polymarket sports markets: World Cup, cricket and esports markets, live odds, odds movement, volume by outcome, resolved games and top traders. # Polymarket Sports API - Live Odds, Cricket, NBA, NFL & Esports Markets Get full access to **Polymarket sports markets** through one API. That covers NBA, NFL, soccer, cricket, UFC, esports and more, with live odds (implied probability), trades, volume, market creation and resolution, and trader-level activity. Bitquery runs **its own blockchain nodes** and **indexes, decodes, and parses** raw Polygon transactions into clean, structured prediction-market data. You don't have to run nodes, decode contract logs, or stitch together odds yourself. The same data is available three ways: - **Historical queries:** backfill odds, volume, and resolutions over any time range. - **Real-time GraphQL subscriptions (WebSocket):** stream new trades and odds the moment they hit the chain. - **Kafka streams:** low-latency, high-throughput feeds for production pipelines. Every query below can be run live by changing `query` to `subscription`. See [Real-Time: GraphQL Subscriptions and Kafka](#real-time-graphql-subscriptions-and-kafka). :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. See [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/). ::: --- ## How Sports Markets Are Identified | Filter | Use case | Where to apply | | -------------------- | --------------------------------------------------------------------------- | ------------------------------------------------- | | **ResolutionSource** | Markets resolved by a specific source (e.g. `espncricinfo.com` for cricket) | `Management.Prediction.Question.ResolutionSource` | | **Description** | General sports markets (keyword in management description) | `Management.Description` | | **Outcome label** | Trades on outcomes whose label contains a term (e.g. "Esports") | `Trade.Prediction.Outcome.Label` | ### Filter by League or Team To target a specific league, tournament, or team, match a keyword in `Question.Title` with `includesCaseInsensitive` (case-insensitive). The same filter works on **PredictionManagements**, **PredictionTrades**, and **PredictionSettlements**. Just place it under `Prediction.Question.Title`. | League / Sport | Example `Question.Title` keyword | | ------------------ | ------------------------------------- | | FIFA / World Cup | `"World Cup"` | | Soccer (EPL) | `"Premier League"` | | Soccer (La Liga) | `"La Liga"` | | Soccer (Serie A) | `"Serie A"` | | Soccer (UCL) | `"Champions League"` | | NBA | `"NBA"` | | NFL | `"NFL"` | | NHL | `"NHL"` | | MLB | `"MLB"` | | UFC / MMA | `"UFC"` | | Tennis | `"Australian Open"`, `"Wimbledon"`, … | | Cricket | `"cricket"` | | Esports (Valorant) | `"Valorant"`, or `"Esports"` (label) | | A specific team | `"Lakers"`, `"Arsenal"`, … | > **Tip:** A single game is uniquely identified by its `Question.MarketId`. Use the creation queries above to discover market IDs, then plug them into the live-odds, OHLC, volume, and trader queries below. --- ### Real-Time: GraphQL Subscriptions and Kafka #### GraphQL Subscriptions Any **query** on this page can be run in **real time** as a **subscription**: keep the same `where` filters and requested fields, and change the keyword `query` to `subscription`. You receive new events (market creations or trades) as they occur on Polygon via a WebSocket connection. #### Kafka Streams For **low-latency** and high-throughput consumption, prediction market data (including sports) is also available via **Kafka**. The same lifecycle events and trades are delivered as Protocol Buffers on Polygon topics: - **`matic.predictions.proto`:** Raw prediction market events (creations, resolutions, trades) - **`matic.broadcasted.predictions.proto`:** Mempool prediction market data Kafka requires **separate credentials** (IDE tokens do not work). See the full guide and topic list: - **[Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/):** Connect, subscribe, parse messages, and configure consumers. For credentials, [contact support](https://t.me/bloxy_info) or email support@bitquery.io. ## Latest FIFA World Cup Markets Created Markets resolved via **FIFA** (`ResolutionSource` includes `fifa.com`). Returns the 10 most recent **Created** events with full condition, outcomes, question, and collateral token details. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-Fifa-World-Cup-Markets-Created) ```graphql query LatestFifaMarketsCreated { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } Prediction: { Question: { ResolutionSource: { includes: "fifa.com" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` ## Top FIFA World Cup Markets by Volume Markets resolved via **FIFA** (`ResolutionSource` includes `fifa.com`). Returns the top 100 cricket related polymarkets sorted by trading volume in the past 24 hours. [Run in Bitquery IDE](https://ide.bitquery.io/top-FIFA-World-Cup-markets-by-volume) ```graphql query TopFIFAMarketsByVolume($time_ago: Int!, $limit: Int!) { EVM(network: matic) { PredictionTrades( where: {Block: {Time: {since_relative: {hours_ago: $time_ago}}}, Trade: {Prediction: {Question: {ResolutionSource: {includes: "fifa.com"}}}}} limit: {count: $limit} orderBy: {descendingByField: "sumBuyAndSell"} ) { Trade { Prediction { Question { Id Image Title CreatedAt } OutcomeToken { assetId0: AssetId(if: {Trade: {Prediction: {Outcome: {Index: {eq: 0}}}}}) assetId1: AssetId(if: {Trade: {Prediction: {Outcome: {Index: {eq: 1}}}}}) } Outcome { label0: Label(if: {Trade: {Prediction: {Outcome: {Index: {eq: 0}}}}}) label1: Label(if: {Trade: {Prediction: {Outcome: {Index: {eq: 1}}}}}) } } OutcomeTrade { price0: Price( maximum: Block_Time if: {Trade: {Prediction: {Outcome: {Index: {eq: 0}}}}} ) price1: Price( maximum: Block_Time if: {Trade: {Prediction: {Outcome: {Index: {eq: 1}}}}} ) } } buyUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: {Trade: {OutcomeTrade: {IsOutcomeBuy: true}}} ) sellUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: {Trade: {OutcomeTrade: {IsOutcomeBuy: false}}} ) sumBuyAndSell: calculate(expression: "$buyUSD + $sellUSD") trades: count buyers: count(distinct: Trade_OutcomeTrade_Buyer) sellers: count(distinct: Trade_OutcomeTrade_Seller) resolved: joinPredictionManagements( join: left Management_Prediction_Question_Id: Trade_Prediction_Question_Id ) { Block { Time(maximum: Block_Time, if: {Management: {EventType: {is: "Resolved"}}}) } } } } } ``` Variables: ```json { "time_ago": 24, "limit": 100 } ``` ## Top FIFA World Cup Markets by Liquidity Returns the top 100 FIFA World Cup related polymarkets sorted by liquidity position in the past 24 hours. Here `position` is the metric used for sorting, hence it could be regarded as the liquidity position of the particular market. [Run in Bitquery IDE](https://ide.bitquery.io/Top-FIFA-World-Cup-Markets-by-Liquidity) ```graphql query TopFIFAMarketByLiquidity($time_ago: Int!, $limit: Int!) { EVM(network: matic) { PredictionSettlements( where: {Block: {Time: {since_relative: {hours_ago: $time_ago}}}, Settlement: {Prediction: {Question: {ResolutionSource: {includes: "fifa.com"}}}}} limit: {count: $limit} orderBy: {descendingByField: "position"} limitBy: {by: Settlement_Prediction_Question_Id} ) { Settlement { Prediction { Question { Image MarketId Title Id CreatedAt ResolutionSource } } } split: sum( of: Settlement_Amounts_CollateralAmountInUSD if: {Settlement: {EventType: {is: "Split"}}} ) merge: sum( of: Settlement_Amounts_CollateralAmountInUSD if: {Settlement: {EventType: {is: "Merge"}}} ) position: calculate(expression: "$split - $merge") count(if: {Settlement: {EventType: {is: "Redemption"}}}, selectWhere: {eq: "0"}) } } } ``` Variables: ```json { "time_ago": 24, "limit": 100 } ``` ## Latest Cricket Markets Created Markets resolved via **ESPN Cricinfo** (`ResolutionSource` includes `espncricinfo.com`). Returns the 10 most recent **Created** events with full condition, outcomes, question, and collateral token details. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-Cricket-Markets-Created) ```graphql query LatestCricketMarketsCreated { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } Prediction: { Question: { ResolutionSource: { includes: "espncricinfo.com" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` ## Top Cricket Markets by Volume Markets resolved via **ESPN Cricinfo** (`ResolutionSource` includes `espncricinfo.com`). Returns the top 100 cricket related polymarkets sorted by trading volume in the past 24 hours. [Run in Bitquery IDE](https://ide.bitquery.io/top-cricket-markets-by-volume) ```graphql query TopCricketMarketsByVolume($time_ago: Int!, $limit: Int!) { EVM(network: matic) { PredictionTrades( where: {Block: {Time: {since_relative: {hours_ago: $time_ago}}}, Trade: {Prediction: {Question: {ResolutionSource: {includes: "espncricinfo.com"}}}}} limit: {count: $limit} orderBy: {descendingByField: "sumBuyAndSell"} ) { Trade { Prediction { Question { Id Image Title CreatedAt } OutcomeToken { assetId0: AssetId(if: {Trade: {Prediction: {Outcome: {Index: {eq: 0}}}}}) assetId1: AssetId(if: {Trade: {Prediction: {Outcome: {Index: {eq: 1}}}}}) } Outcome { label0: Label(if: {Trade: {Prediction: {Outcome: {Index: {eq: 0}}}}}) label1: Label(if: {Trade: {Prediction: {Outcome: {Index: {eq: 1}}}}}) } } OutcomeTrade { price0: Price( maximum: Block_Time if: {Trade: {Prediction: {Outcome: {Index: {eq: 0}}}}} ) price1: Price( maximum: Block_Time if: {Trade: {Prediction: {Outcome: {Index: {eq: 1}}}}} ) } } buyUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: {Trade: {OutcomeTrade: {IsOutcomeBuy: true}}} ) sellUSD: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: {Trade: {OutcomeTrade: {IsOutcomeBuy: false}}} ) sumBuyAndSell: calculate(expression: "$buyUSD + $sellUSD") trades: count buyers: count(distinct: Trade_OutcomeTrade_Buyer) sellers: count(distinct: Trade_OutcomeTrade_Seller) resolved: joinPredictionManagements( join: left Management_Prediction_Question_Id: Trade_Prediction_Question_Id ) { Block { Time(maximum: Block_Time, if: {Management: {EventType: {is: "Resolved"}}}) } } } } } ``` Variables: ```json { "time_ago": 24, "limit": 100 } ``` You can checkout the above data in more intuitive form on [DexRabbit](https://dexrabbit.bitquery.io/polymarket-predictions/sports/cricket?tab=volume). ![Cricket Markets by Liquidity](/img/dexrabbit/cricket-volume.png) ## Top Cricket Markets by Liquidity Returns the top 100 cricket related polymarkets sorted by liquidity position in the past 24 hours. [Run in Bitquery IDE](https://ide.bitquery.io/Top-cricket-Markets-by-Liquidity) ```graphql query questionByLiquidity($time_ago: Int!, $limit: Int!) { EVM(network: matic) { PredictionSettlements( where: {Block: {Time: {since_relative: {hours_ago: $time_ago}}}, Settlement: {Prediction: {Question: {ResolutionSource: {includes: "espncricinfo.com"}}}}} limit: {count: $limit} orderBy: {descendingByField: "position"} limitBy: {by: Settlement_Prediction_Question_Id} ) { Settlement { Prediction { Question { Image MarketId Title Id CreatedAt ResolutionSource } } } split: sum( of: Settlement_Amounts_CollateralAmountInUSD if: {Settlement: {EventType: {is: "Split"}}} ) merge: sum( of: Settlement_Amounts_CollateralAmountInUSD if: {Settlement: {EventType: {is: "Merge"}}} ) position: calculate(expression: "$split - $merge") count(if: {Settlement: {EventType: {is: "Redemption"}}}, selectWhere: {eq: "0"}) } } } ``` Variables: ```json { "time_ago": 24, "limit": 100 } ``` You can checkout the above data in more intuitive form on [DexRabbit](https://dexrabbit.bitquery.io/polymarket-predictions/sports/cricket?tab=liquidity). ![Cricket Markets by Liquidity](/img/dexrabbit/cricket-liquidity.png) ## Latest Sports Markets Created Markets whose **management description** includes the word **"sports"**. Use this for broad sports coverage beyond a single resolution source. Returns the 10 most recent **Created** events. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-Sports-Markets-Created) ```graphql query LatestSportsMarketsCreated { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } Description: { includes: "sports" } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` ## Latest Esports Prediction Trades Recent **trades** where the outcome **label** includes **"Esports"**. Returns up to 50 trades ordered by block time, with buyer, seller, amounts, price, and full prediction/question metadata. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-Esports-Prediction-Trades) ```graphql query LatestEsportsPredictionTrades { EVM(network: matic) { PredictionTrades( limit: { count: 50 } orderBy: { descending: Block_Time } where: { Trade: { Prediction: { Outcome: { Label: { includes: "Esports" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` --- ## Live Odds (Implied Probability) for a Single Game Returns the **latest trade price per outcome** for one market by `MarketId`. This is the **live win probability / odds** for the game. On Polymarket an outcome's `Price` ranges from **0 to 1 and equals its implied probability** (e.g. `0.62` = **62%**). `limitBy` with `orderBy: { descending: Block_Time }` returns one most-recent row per outcome (e.g. Team A vs Team B). Replace `""` with a market ID from the creation queries above. Change `query` to `subscription` for a live odds feed. [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-live-game-odds) ```graphql query LiveGameOdds { EVM(network: matic) { PredictionTrades( limitBy: { by: Trade_Prediction_Outcome_Label, count: 1 } orderBy: { descending: Block_Time } where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { MarketId: { is: "" } } } } } ) { Trade { OutcomeTrade { Price PriceInUSD } Prediction { Outcome { Index Label } OutcomeToken { Name AssetId } Question { Title MarketId ResolutionSource Image } } } } } } ``` --- ## Odds (Line) Movement: OHLC for an Outcome Returns **OHLC** (Open, High, Low, Close) in USD for one outcome of a game, bucketed by interval (here 5 minutes). It shows how the **win probability moved over time**, and powers line-movement charts and strategy backtests. Replace `""` and `""` (e.g. a team name, `"Yes"`, or `"Up"`). [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-sports-odds-movement-OHLC) ```graphql query OddsMovementOHLC { EVM(network: matic) { PredictionTrades( limit: { count: 100 } orderBy: { descendingByField: "Block_Interval" } where: { Trade: { Prediction: { Question: { MarketId: { is: "" } } Outcome: { Label: { is: "" } } } } } ) { Block { Interval: Time(interval: { count: 5, in: minutes }) } Trade { OutcomeTrade { Open: PriceInUSD(minimum: Block_Time) High: PriceInUSD(maximum: Trade_OutcomeTrade_PriceInUSD) Low: PriceInUSD(minimum: Trade_OutcomeTrade_PriceInUSD) Close: PriceInUSD(maximum: Block_Time) } Prediction { OutcomeToken { Name AssetId } Outcome { Id Label } } } } } } ``` --- ## 24-Hour Odds Change for an Outcome Returns the **opening and closing odds** (plus high/low) over the **last 24 hours** for one outcome. It is the single-window version of the OHLC query, useful for "biggest movers" leaderboards (run it per market and sort by `Close − Open`). Replace `""` and `""`. [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-sports-24h-odds-change) ```graphql query Odds24hChange { EVM(network: matic) { PredictionTrades( where: { Block: { Time: { since_relative: { hours_ago: 24 } } } Trade: { Prediction: { Question: { MarketId: { is: "" } } Outcome: { Label: { is: "" } } } } } ) { Trade { OutcomeTrade { Open: PriceInUSD(minimum: Block_Time) Close: PriceInUSD(maximum: Block_Time) High: PriceInUSD(maximum: Trade_OutcomeTrade_PriceInUSD) Low: PriceInUSD(minimum: Trade_OutcomeTrade_PriceInUSD) } Prediction { Outcome { Label } Question { Title MarketId } } } } } } ``` --- ## Volume Split by Outcome (Money on Each Side) Returns **total volume** and **volume per outcome** (e.g. Team A vs Team B) for a single game in the last 24 hours. This is an on-chain **sentiment / sharp-money** signal. Outcomes are split by `Index` (0 and 1) so it works regardless of how the labels are named. Replace `""`. [Run in Bitquery IDE](https://ide.bitquery.io/Polymarket-game-volume-by-outcome) ```graphql query GameVolumeByOutcome { EVM(network: matic) { PredictionTrades( where: { Block: { Time: { since_relative: { hours_ago: 24 } } } Trade: { Prediction: { Question: { MarketId: { is: "" } } } } } ) { Trade { Prediction { Question { Title MarketId Image ResolutionSource } Outcome { label0: Label( if: { Trade: { Prediction: { Outcome: { Index: { eq: 0 } } } } } ) label1: Label( if: { Trade: { Prediction: { Outcome: { Index: { eq: 1 } } } } } ) } } } outcome0_volume: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { Prediction: { Outcome: { Index: { eq: 0 } } } } } ) outcome1_volume: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { Prediction: { Outcome: { Index: { eq: 1 } } } } } ) total_volume: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) trades: count } } } ``` --- ## Latest Resolved Sports Markets (Winning Outcome) Returns the 10 most recent **Resolved** sports markets (management description includes `"sports"`), including the resolved/winning **Outcome** and full question metadata. Use this to grade results and settle bets. For a single league, swap the `Description` filter for a `Prediction.Question.Title` keyword (see the league table above). [Run in Bitquery IDE](https://ide.bitquery.io/Latest-resolved-sports-markets) ```graphql query LatestResolvedSportsMarkets { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Resolved" } Description: { includes: "sports" } } } ) { Block { Time } Management { Description EventType Prediction { Condition { Id QuestionId Outcomes { Index Label } } Outcome { Index Label } Question { Title MarketId ResolutionSource Image CreatedAt } } } Transaction { Hash } } } } ``` --- ## Top Traders on a Game (Sharp Money) Returns the top 10 **buyers** and top 10 **sellers** by USD volume on a single market, showing who is putting the most money on each side. Replace `""`. (Drop the `MarketId` filter and add a `Question.Title` league keyword to rank traders across a whole league.) [Run in Bitquery IDE](https://ide.bitquery.io/Top-traders-on-a-sports-market) ```graphql query TopTradersOnGame { EVM(network: matic) { Top_buyers: PredictionTrades( where: { Trade: { Prediction: { Question: { MarketId: { is: "" } } } OutcomeTrade: { IsOutcomeBuy: true } } } limit: { count: 10 } orderBy: { descendingByField: "buy_volume" } ) { Trade { OutcomeTrade { Buyer } } buy_volume: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) trades: count } Top_sellers: PredictionTrades( where: { Trade: { Prediction: { Question: { MarketId: { is: "" } } } OutcomeTrade: { IsOutcomeBuy: false } } } limit: { count: 10 } orderBy: { descendingByField: "sell_volume" } ) { Trade { OutcomeTrade { Seller } } sell_volume: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) trades: count } } } ``` --- ## Top Winners of a Game (by Redemption) After a game resolves, this returns the top 10 holders by **redeemed amount** (USD). These are the biggest winners. Uses `PredictionSettlements` with `EventType: "Redemption"`. Replace `""`. [Run in Bitquery IDE](https://ide.bitquery.io/Top-winners-of-a-sports-market) ```graphql query TopWinnersOnGame { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descendingByField: "redeemed_amount" } where: { Settlement: { EventType: { is: "Redemption" } Prediction: { Question: { MarketId: { is: "" } } } } } ) { Settlement { Holder Prediction { Question { Title MarketId } } } redeemed_amount: sum(of: Settlement_Amounts_CollateralAmountInUSD) } } } ``` --- ## Real-Time Whale-Bet Alerts (Subscription) Streams live sports trades **above a USD threshold** (here `$5,000`). This is ideal for whale-alert bots and detecting large line-moving bets. Filter the sport with a `Question.Title` keyword (league or team), or swap it for a single-game `MarketId`. Change `subscription` to `query` for historical results. :::tip No-code option: PolyBit Telegram bot Don't want to run your own stream? The **[PolyBit Polymarket bot](https://t.me/PolyBit_Polymarket_Bot)** is a **free Telegram bot built by the Bitquery team** that monitors whale trades and other metrics across all markets. Open a specific market directly with a deep link: ``` https://t.me/PolyBit_Polymarket_Bot?start=market_ ``` Replace `` with the market's ID, for example [`market_2453464`](https://t.me/PolyBit_Polymarket_Bot?start=market_2453464). ::: ```graphql subscription { EVM(network: matic) { PredictionTrades( where: { Trade: { OutcomeTrade: { CollateralAmountInUSD: { gt: "5000" } } Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: "" } } } } } ) { Block { Time } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD Price PriceInUSD IsOutcomeBuy } Prediction { Question { Title MarketId ResolutionSource Image } Outcome { Index Label } } } Transaction { From Hash } } } } ``` --- ## Monitor Specific Wallets on Sports Markets (Subscription) Streams every sports trade where one of a watched wallet list is the **Buyer** or **Seller**. Powers copy-trading signals, wallet alerts, and PnL dashboards. Pass the addresses in the `$wallets` variable and set the `Question.Title` filter to the league/team you care about. [Run in Bitquery IDE](https://ide.bitquery.io/Monitor-wallets-on-Polymarket-sports-markets) ```graphql subscription MonitorWallets($wallets: [String!]) { EVM(network: matic) { PredictionTrades( where: { any: [ { Trade: { OutcomeTrade: { Buyer: { in: $wallets } } } } { Trade: { OutcomeTrade: { Seller: { in: $wallets } } } } ] Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: "" } } } } } ) { Block { Time } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmountInUSD Price PriceInUSD IsOutcomeBuy } Prediction { Question { Title MarketId ResolutionSource } Outcome { Index Label } } } Transaction { From Hash } } } } ``` **Variables:** ```json { "wallets": [ "0x87a961f161681cc1e9b3af2b6542b95ef3c4bd70", "0x0bab932893a7efc76d8e0951366ba933ba9fd3be" ] } ``` --- ## Polymarket-Only Filter To restrict results to **Polymarket** only, add this to the relevant `where` clause: **PredictionManagements:** ```graphql Management: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } # ... other filters } ``` **PredictionTrades:** ```graphql Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } # ... other filters } ``` --- ## Related APIs | Need | API | | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | **Market lifecycle (creation, resolution)** | [Prediction Managements API](/docs/examples/prediction-market/prediction-managements-api/) | | **Trades, volume, prices** | [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) | | **Filter by slug, condition ID, token** | [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/) | | **Polymarket overview** | [Polymarket API](/docs/examples/polymarket-api/) | | **Settlements & redemptions** | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | **User & wallet activity** | [Polymarket Wallet & User Activity API](/docs/examples/polymarket-api/polymarket-wallet-api/) | | **Trader realized PnL & win rate** | [Realized PnL & Win Rate for Polymarket Trader](/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/) | | **Real-time: GraphQL subscriptions** | [GraphQL subscriptions & WebSockets](/docs/subscriptions/websockets/) | | **Real-time: Kafka streams** | [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) | --- ## Support - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket Tg Alerts Bot URL: https://docs.bitquery.io/docs/usecases/polymarket-tg-alerts-bot/ Build Polymarket Tg Alerts Bot: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # How to Build a Polymarket Whale Alerts Telegram Bot with the Bitquery API Build a production-ready **Polymarket Telegram bot** that streams realtime trades and lets users set custom alerts on trade size, share price, trader wallet, or specific market. It is all powered by the **[Bitquery Prediction Market API](/docs/examples/prediction-market/prediction-market-api/)**. By the end of this guide you'll have a multi-user Telegram bot that subscribes to every Polymarket trade on Polygon over a single GraphQL WebSocket, filters trades against per-user alert rules, and pushes Telegram notifications with links to Polymarket, PolygonScan, and the trader's profile. ## Video Walkthrough Add PolyBit on Telegram > **[Source code](https://github.com/Akshat-cs/PolyBit-Polymarket-Alerts-Telegram-Bot)** > **Stack:** Python 3.11+ · `python-telegram-bot v21` · `gql` (Bitquery GraphQL) · `httpx` > **Time to complete tutorial:** ~30 minutes > **Storage:** Flat JSON files (no database) ## What you'll learn - How to subscribe to **realtime Polymarket trades** with a Bitquery GraphQL subscription - How to query **top markets**, **search markets**, and **current outcome prices** over Bitquery's HTTP endpoint - How to match streamed trades against user-defined alert filters with a per-alert cooldown - How to ship a multi-user Telegram bot with persistent state (no DB required) - How to deploy the bot with persistent storage so user alerts survive redeploys ## Why use Bitquery for Polymarket data Polymarket runs on Polygon and uses Gnosis CTF contracts under the hood. To build any data-driven product on top of it you need to either run your own Polygon archive node, index trades from the Conditional Tokens Framework (CTF), and resolve market metadata yourself, or reach for a hosted indexer. The Bitquery Prediction Market API solves all three concerns through a single GraphQL endpoint: - **Realtime + historical in one place.** The same schema is exposed over WebSocket subscriptions and HTTP queries, you don't stitch a streaming SDK to a separate query SDK. - **Decoded trades, not logs.** `PredictionTrades` already exposes `Buyer`, `Seller`, `CollateralAmountInUSD`, `Price`, `IsOutcomeBuy`, `Outcome.Label`: no custom CTF decoding needed. - **Market metadata included.** `Question.Title`, `Question.Image`, `Question.MarketId`, `ConditionId`, `OutcomeToken.AssetId` come back on the same trade row, so a single subscription is enough to render rich notifications. - **Aggregations server-side.** `volume_usd`, `trade_count`, `unique_buyers` are computed inside the query, so "top markets last 1h" is a single round-trip, no client-side reduce. If you're building anything that needs Polymarket whale alerts, leaderboards, market analytics, dashboards, or notifications, this is the fastest path from "I have an API key" to "I'm shipping product." ## Prerequisites Before you start you'll need: 1. **Python 3.11+** installed (`python3 --version`). 2. **A Bitquery API token.** Sign up at [account.bitquery.io](https://account.bitquery.io) and generate an API v2 access token at [account.bitquery.io/user/api_v2/access_tokens](https://account.bitquery.io/user/api_v2/access_tokens). The free tier is plenty to follow this guide. 3. **A Telegram account** plus a bot token. Open Telegram, message [@BotFather](https://t.me/BotFather), send `/newbot`, follow the prompts, and copy the `123456:ABC…` token it returns. 4. **(Optional)** A Render or VPS account if you want to deploy the bot publicly. We cover Render at the end. Keep both tokens private because they grant API access on your behalf. ## Architecture overview PolyBit runs three concurrent tasks on a single asyncio event loop: ``` ┌─────────────────────────────────────────────┐ Bitquery WS ──► │ BitqueryStreamer ──► match_trade() │ (every │ ▲ │ Polymarket │ │ (Alert filters) │ trade) │ ┌───────┴───────┐ │ │ │ AlertStore │ │ │ │ UserStore │ │ Telegram <──── ─┤ TelegramSender │ (JSON files) │ │ (notifications) │ ▲ └───────┬───────┘ │ │ │ │ │ Telegram ────► ─┤ python-telegram-bot ◄───┘ │ (commands, │ Application (handlers) │ callbacks) └─────────────────────────────────────────────┘ ``` - `BitqueryStreamer` opens a single WebSocket subscription to `wss://streaming.bitquery.io/graphql` and dispatches each `TradeEvent` to registered handlers. - `match_trade()` runs the trade against every active alert and returns matches. - `TelegramSender` drains an outbound queue with per-chat throttling so Telegram's rate limits never bite. - **`AlertStore` / `UserStore`** persist users and alerts to JSON files atomically (tmp file + `os.replace`) under an `asyncio.Lock`. That's the whole system. Let's build it. ## Step 1: Project setup Clone the reference repo and install dependencies: ```bash git clone https://github.com/Akshat-cs/PolyBit-Polymarket-Alerts-Telegram-Bot.git cd PolyBit-Polymarket-Alerts-Telegram-Bot python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` `requirements.txt` is intentionally small: ``` gql[websockets]>=3.5.0 websockets>=12.0 python-dotenv>=1.0.0 httpx>=0.27.0 python-telegram-bot[ext]>=21.0 ``` Copy the env template and fill in your tokens: ```bash cp .env.example .env ``` ```bash # .env BITQUERY_TOKEN=ory_at_xxxxxxxx... TELEGRAM_BOT_TOKEN=123456:ABC... POLYBIT_LOG_LEVEL=INFO # POLYBIT_DATA_DIR=/var/data # only on a deployed instance ``` ## Step 2: Stream realtime Polymarket trades from Bitquery The core of any Polymarket alerts product is a single GraphQL subscription. We listen for every successful Polymarket trade on Polygon and request exactly the fields we need to filter, render, and link out from a notification: ```graphql subscription PolymarketTradesStream { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Block { Time } Transaction { Hash From } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD Price PriceInUSD IsOutcomeBuy } Prediction { ConditionId Question { Id Title MarketId Image CreatedAt } Outcome { Label Index } OutcomeToken { AssetId } } } } } } ``` Two important field-level details for prediction markets: - `CollateralAmountInUSD` is the trade size in USDC equivalent. This is what you compare against a "min trade USD" alert filter, not the raw outcome-token `Amount`. - `IsOutcomeBuy` is the trade direction: - `true`: Seller (maker) gives USDC, Buyer (taker) gives outcome tokens. The position is being closed. - `false`: Buyer gives USDC, Seller gives outcome tokens. New exposure is being opened. PolyBit normalizes "buyer" to "whoever received outcome tokens" and surfaces both addresses in notifications. ### Connecting over WebSocket Bitquery's WebSocket endpoint expects the API token as a query parameter and the `graphql-ws` subprotocol header: ```python # polybit/bitquery.py (excerpt) from gql import gql from gql.transport.websockets import WebsocketsTransport from urllib.parse import quote BITQUERY_WS_URL = "wss://streaming.bitquery.io/graphql" def ws_url(token: str) -> str: return f"{BITQUERY_WS_URL}?token={quote(token, safe='')}" class BitqueryStreamer: def __init__(self, token: str) -> None: self._token = token self._handlers = [] def add_handler(self, fn) -> None: self._handlers.append(fn) async def run(self) -> None: transport = WebsocketsTransport( url=ws_url(self._token), headers={"Sec-WebSocket-Protocol": "graphql-ws"}, ) await transport.connect() async for result in transport.subscribe(gql(TRADES_SUBSCRIPTION)): if result.errors: continue for row in (result.data or {}).get("EVM", {}).get("PredictionTrades") or []: event = TradeEvent.from_raw(row) for handler in self._handlers: await handler(event) ``` The full implementation in `polybit/bitquery.py` adds exponential-backoff reconnection and graceful shutdown, but this is the entire happy path. One subscription, one handler chain, all Polymarket trades. ## Step 3: Query top markets over HTTP For browse/search features you don't want to filter the firehose client-side. You want server-side aggregations. Bitquery's `PredictionTrades` exposes `sum`, `count`, and `count(distinct: …)` directly inside the GraphQL query, so "top markets last 1h by volume" is one request: ```graphql query TopMarketsByVolume($hours: Int!, $limit: Int!) { EVM(network: matic) { PredictionTrades( limit: { count: $limit } orderBy: { descendingByField: "volume_usd" } where: { TransactionStatus: { Success: true } Block: { Time: { since_relative: { hours_ago: $hours } } } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } } ) { Trade { Prediction { ConditionId Question { Id Title MarketId Image } } } volume_usd: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) trade_count: count unique_buyers: count(distinct: Trade_OutcomeTrade_Buyer) } } } ``` Swap `orderBy` for `unique_buyers` or `trade_count` to get the other top-market views. That gives three leaderboards from the same query template. The HTTP client is a thin `httpx` wrapper that adds `Authorization: Bearer ` and POSTs to the same hostname: ```python # polybit/bitquery.py (excerpt) BITQUERY_HTTP_URL = "https://streaming.bitquery.io/graphql" class BitqueryHTTP: async def __aenter__(self): self._client = httpx.AsyncClient( base_url=BITQUERY_HTTP_URL, headers={"Authorization": f"Bearer {self._token}"}, timeout=30.0, ) return self async def _exec(self, query: str, variables: dict) -> dict: resp = await self._client.post( "", json={"query": query, "variables": variables}, ) resp.raise_for_status() body = resp.json() if "errors" in body: raise RuntimeError(body["errors"]) return body["data"] ``` ## Step 4: Fetch current prices and search markets Two more queries cover the rest of the browse experience: **Search markets by keyword**, same `PredictionTrades` aggregation with a title substring filter: ```graphql query SearchMarkets($q: String!, $limit: Int!, $hours: Int!) { EVM(network: matic) { PredictionTrades( limit: { count: $limit } orderBy: { descendingByField: "volume_usd" } where: { TransactionStatus: { Success: true } Block: { Time: { since_relative: { hours_ago: $hours } } } Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } Question: { Title: { includesCaseInsensitive: $q } } } } } ) { ...same shape as TopMarketsByVolume } } } ``` **Current outcome prices for a single market**, uses `limitBy` to take the latest trade per outcome token: ```graphql query CurrentPricesForMarket($marketId: String!) { EVM(network: matic) { PredictionTrades( limitBy: { by: Trade_Prediction_OutcomeToken_AssetId, count: 1 } orderBy: { descending: Block_Time } where: { TransactionStatus: { Success: true } Trade: { Prediction: { Question: { MarketId: { is: $marketId } } } } } ) { Block { Time } Trade { OutcomeTrade { Price(maximum: Block_Time) PriceInUSD(maximum: Block_Time) } Prediction { ConditionId Question { Id Title Image MarketId } Outcome { Label Index } OutcomeToken { AssetId } } } } } } ``` `limitBy: { by: …, count: 1 }` returns one row per `AssetId`, and `maximum: Block_Time` picks the most recent price per outcome, the same trick you'd use to render a market's current "Yes / No" prices. ## Step 5: Match streamed trades against user alerts PolyBit's alert model is a single dataclass with seven optional filters. Any field that's `None` is treated as a wildcard: ```python # polybit/store.py (excerpt) @dataclass class Alert: id: str chat_id: str market_key: str | None = None # Polymarket MarketId market_title: str | None = None outcome: str | None = None # e.g. "Yes" / "Up" min_trade_amount_usd: float | None = None max_trade_amount_usd: float | None = None min_price_usd: float | None = None max_price_usd: float | None = None trader: str | None = None # 0x... wallet last_triggered_at: float | None = None paused: bool = False ``` The matcher is straightforward: every set filter must pass, with a per-alert cooldown so a hot market can't spam the user. ```python # polybit/matcher.py (excerpt) def match_trade(event, alerts, *, cooldown_seconds=60): now = time.time() matches = [] for a in alerts: if a.paused or not a.has_any_filter(): continue if a.last_triggered_at and now - a.last_triggered_at < cooldown_seconds: continue if a.market_key and event.market_id != a.market_key: continue if a.outcome and (event.outcome_label or "").lower() != a.outcome.lower(): continue if a.min_trade_amount_usd is not None and (event.collateral_usd or 0) < a.min_trade_amount_usd: continue if a.max_trade_amount_usd is not None and (event.collateral_usd or 0) > a.max_trade_amount_usd: continue if a.min_price_usd is not None and (event.price or 0) < a.min_price_usd: continue if a.max_price_usd is not None and (event.price or 0) > a.max_price_usd: continue if a.trader and a.trader.lower() not in (event.buyer or "", event.seller or ""): continue a.last_triggered_at = now matches.append(Match(alert=a, event=event)) return matches ``` Every active alert is just an in-memory dataclass, so running this for ~hundreds of alerts per trade is trivially fast. ## Step 6: Send notifications via Telegram Wire the streamer's handler to the Telegram sender. The handler builds a notification message per match and enqueues it; the sender drains the queue with per-chat throttling so we never trip Telegram's rate limits: ```python # polybit/main.py (excerpt) async def on_trade(event): matches = match_trade(event, list(alerts.active_alerts())) if not matches: return for m in matches: text, kb, preview = fmt.fmt_trade_notification(m, canonical_url=…) await sender.enqueue(OutboundMessage( chat_id=m.alert.chat_id, text=text, reply_markup=kb, preview_url=preview, # Polymarket S3 question image )) streamer.add_handler(on_trade) ``` Notifications include inline links to: - `https://polymarket.com/event/`: resolved via Polymarket's Gamma API (`https://gamma-api.polymarket.com/markets`) using `ConditionId` from the trade row. Falls back to a slugified title. - `https://polygonscan.com/tx/`: straight from `Transaction.Hash`. - `https://polymarket.com/profile/`: for the trader who triggered the alert (when a trader filter is set). The image preview is the question's S3 image URL (`Question.Image`) directly, with no additional rendering step. ## Step 7: Persist users and alerts to JSON For multi-user state you don't need a database. Two flat JSON files, atomic writes, and an `asyncio.Lock` will hold ~hundreds of users and alerts in well under 100 KB total: ```python # polybit/store.py (excerpt) def _atomic_write_json(path, payload): tmp = path.with_suffix(path.suffix + ".tmp") tmp.write_text(json.dumps(payload, indent=2), encoding="utf-8") os.replace(tmp, path) # atomic on POSIX + Windows class AlertStore: def __init__(self, path): self._path = path self._lock = asyncio.Lock() self._alerts: dict[str, Alert] = {} async def add(self, alert): async with self._lock: self._alerts[alert.id] = alert self._save_unlocked() ``` `UserStore` follows the same pattern. Schema: ```json // users.json { "users": [{ "chat_id": "5057295168", "username": "alice", "joined_at": "2026-05-08T09:23:45+00:00" }] } // alerts.json { "alerts": [{ "id": "0c5c9...", "chat_id": "5057295168", "market_key": "1985666", "market_title": "Will Trump visit China by May 15?", "min_trade_amount_usd": 5000, "trader": "0xae80195bd3a761fe6b39bec9dfab9b5566fc86b0", "paused": false, "created_at": "2026-05-07T18:02:11+00:00" }] } ``` `tmp + os.replace` makes a crash mid-write impossible to corrupt the file: either the rename happened (new file in place) or it didn't (old file untouched). ## Step 8: Run it ```bash python -m polybit ``` You should see: ``` INFO PolyBit starting up: 0 user(s), 0 alert(s) INFO Bitquery WS connected INFO PolyBit is running. Press Ctrl+C to stop. ``` Open your bot in Telegram, send `/start`, and try `/topmarkets`, `/search`, `/addalert`. The first trade that matches one of your alerts will arrive as a Telegram message with full context. ## Step 9: Deploy with persistent storage Because user and alert state lives in JSON files, your hosting target needs **persistent disk** rather than a free-tier ephemeral filesystem. The reference repo ships with a Render Blueprint: ```yaml # render.yaml services: - type: worker name: polybit runtime: python plan: starter buildCommand: pip install -r requirements.txt startCommand: python -m polybit envVars: - key: BITQUERY_TOKEN sync: false - key: TELEGRAM_BOT_TOKEN sync: false - key: POLYBIT_DATA_DIR value: /var/data disk: name: polybit-data mountPath: /var/data sizeGB: 1 ``` The `POLYBIT_DATA_DIR` env var redirects writes to the mounted disk, so `/var/data/users.json` and `/var/data/alerts.json` survive every redeploy. Total cost on Render: $7.25/mo (Starter Worker + 1 GB disk). For self-hosting, the same setup works under `systemd` on any VPS. Make sure the `WorkingDirectory` points at a directory that survives reboots. See [DEPLOY.md](https://github.com/Akshat-cs/PolyBit-Polymarket-Alerts-Telegram-Bot/blob/main/DEPLOY.md) in the repo for both flows. ## Inspect users and alerts at runtime The repo includes a tiny CLI for live introspection: ```bash python -m polybit.stats ``` ``` ============================================================ PolyBit · stats snapshot Data dir: /var/data ============================================================ 👥 Users: 142 Joined last 24h: 18 Joined last 7d: 64 🔔 Alerts: 287 Active: 251 Paused: 36 Bound to a market: 198 Triggered at least once: 113 🎯 Most-targeted markets (top 5): 21× Bitcoin Up or Down — May 8, 7:40AM-7:45AM ET 18× Will Trump visit China by May 15? ... ``` Useful for product checks; safe to run while the bot is live. ## What you can build next You can build other Polymarket products from the same Bitquery queries: - **Per-trader leaderboards**: aggregate `PredictionTrades` by `Buyer` over a window for top-volume wallets. - **PnL tracking**: combine `PredictionTrades` with `PredictionSettlements` to compute realized PnL per wallet. - **Market resolution alerts**: subscribe to `PredictionSettlements` to notify users when a market they hold positions in resolves. - **Whale-watch X/Twitter feeds**: same trade stream, different output channel. - **Custom dashboards**: the same aggregations power Grafana / Metabase / your own React app. All of these reuse the same GraphQL endpoint, same auth, same field shapes. ## Resources - [Bitquery Prediction Market API docs](/docs/examples/prediction-market/prediction-market-api/) - [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) - [Prediction Managements API](/docs/examples/prediction-market/prediction-managements-api/) - [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) - [Source code](https://github.com/Akshat-cs/PolyBit-Polymarket-Alerts-Telegram-Bot) - [Live demo, @PolyBit_Polymarket_Bot](https://t.me/PolyBit_Polymarket_Bot) - [Get a Bitquery token](https://account.bitquery.io) ## Conclusion A multi-user **Polymarket Telegram alerts bot** is one GraphQL subscription, a small filter loop, and a Telegram client away. Bitquery handles the chain-level work (decoded trades, market metadata, server-side aggregations), so you can spend your time on product, not on indexing. Get a free Bitquery token at [account.bitquery.io](https://account.bitquery.io), point it at the Prediction Market API, and start shipping. --- ## Polymarket Wallet & User Activity API URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-wallet-api/ Polymarket wallet activity: check whether an address ever interacted with Polymarket and list a wallet's recent trading activity. # Wallet & User Activity API Get **user- and wallet-level data** for Polymarket: recent activity, positions, trade volume, and market counts. Use Bitquery GraphQL to analyze trader behavior by wallet address, and combine with the [Polymarket API](/docs/examples/polymarket-api/) overview for trades and market data. ## Check if an address ever interacted with Polymarket (on-chain transfers) Polymarket collateral on Polygon flows through conditional tokens keyed to USDC denomination at `0x4d97DCd97eC945f40cF65F87097ACe5EA0476045`. To see whether a wallet likely has **any** Polymarket-related receipts, query for **at least one inbound transfer** of that token (`limit: { count: 1 }`). Empty results mean no indexed match. That is not a guarantee that the wallet never traded (e.g. only outbound paths), but useful for onboarding and tagging. The same pattern is documented under [Polygon (MATIC) Transfers](/docs/blockchain/Matic/matic-transfers/). **Try it:** [IDE: Polymarket interaction check](https://ide.bitquery.io/check-if-an-address-interacted-with-polymarket-ever) ```graphql query ($address: String) { EVM(dataset: combined, network: matic) { Transfers( limit: { count: 1 } orderBy: { descending: Block_Time } where: { Transfer: { Currency: { SmartContract: { is: "0x4d97DCd97eC945f40cF65F87097ACe5EA0476045" } } Receiver: { is: $address } } } ) { Block { Time Number } Transaction { Hash } Transfer { Sender Receiver Amount } } } } ``` **Variables:** ```json { "address": "0x0c79f21ec570f5cc0d52d1bc640845faef430ad2" } ``` ## Recent user activity by wallet Get aggregate stats for a trader over the last 5 hours: total outcomes traded, collateral amounts, and number of distinct markets. [Run query in IDE](https://ide.bitquery.io/Get-recent-user-activity-on-polymarket) ```graphql query MyQuery($trader: String) { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } any: [ { Trade: { OutcomeTrade: { Buyer: { is: $trader } } } } { Trade: { OutcomeTrade: { Seller: { is: $trader } } } } ] Block: { Time: { since_relative: { hours_ago: 5 } } } } ) { Total_Outcomes_traded: count Total_Outcome_Amount: sum(of: Trade_OutcomeTrade_Amount) Total_Collateral_Amount: sum(of: Trade_OutcomeTrade_CollateralAmount) Total_Markets: count(distinct: Trade_Prediction_Question_MarketId) } } } ``` **Variables:** ```json { "trader": "0x101f2f96db1e39a9f36a1fa067751d541fd38e1a" } ``` Replace `trader` with any Polygon wallet address (EOA or proxy wallet). --- ## What you can get by wallet | Data | Source | Notes | | ----------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------ | | **Recent activity & volume** | Bitquery (above) | GraphQL `PredictionTrades` filtered by Buyer/Seller | | **Closed positions** | Polymarket CLOB | Profile API | | **User activity timeline** | Polymarket CLOB | Profile API | | **Total value of positions** | Polymarket CLOB | Profile API | | **Trades for a user or market** | Polymarket CLOB | Profile API | | **Positions for a specific market** | Polymarket CLOB | Profile API | | **Public profile by wallet** | Polymarket Gamma API | `GET /public-profile?address=` (no auth) | | **Deposits & withdrawals** | Polymarket Bridge API | Supported assets, deposit/withdrawal addresses, transaction status | --- ## Related APIs | Need | API | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Trades, prices, volume** | [Polymarket API](/docs/examples/polymarket-api/) · [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) | | **Filter by market slug, condition ID, token** | [Polymarket Markets API](/docs/examples/polymarket-api/polymarket-markets-api/) | | **Settlements & redemptions** | [Prediction Settlements API](/docs/examples/prediction-market/prediction-settlements-api/) | | **Market lifecycle & resolution** | [Prediction Market API](/docs/examples/prediction-market/prediction-market-api/) | | **On-chain contracts & events** | Main Polymarket Contract (on-chain) | --- ## Support - [Bitquery Telegram](https://t.me/bloxy_info) --- ## Polymarket Wallet Realized PnL & Win Rate (Pattern) URL: https://docs.bitquery.io/docs/examples/polymarket-api/polymarket-wallet-realized-pnl/ Polymarket trader PnL: get a wallet's buys and sells, aggregate them by condition ID, then calculate realized profit and loss and win rate step by step. # Realized PnL & Win Rate for Polymarket Trader You can use Bitquery `PredictionTrades` on Polygon (`network: matic`) to pull a wallet’s **outcome buys** and **outcome sells** on Polymarket over a chosen window (for example the last **24 hours**). From that response, you can aggregate by `ConditionId`, calculate `realised PnL` per condition, and derive the total **realized PnL** and **win rate** for Polymarket trading. ## How is PnL and Win Rate calculated? - **Buys** (`IsOutcomeBuy: true`): collateral spent acquiring outcome tokens (tracked as `CollateralAmountInUSD` on the trade). - **Sells** (`IsOutcomeBuy: false`): collateral received when selling outcome tokens. For each **condition** (`Trade.Prediction.ConditionId`), treat **realized PnL in the window** as: ```js Realised PnL per condition = total(sell collateral USD) − total(buy collateral USD) Realised PnL = sum(Realised PnL per condition) ``` **Win rate**: Percentage of profitable trades in the given time window. Many algorithmic traders use this metric for rating the performance of their systems instead of using PnL as a judge for success. ```js Win Rate = 100* count(profitable trade)/count(trades) ``` ## Get Buys and Sells for a Trader on Polymarket [This query](https://ide.bitquery.io/buys-and-sells-of-a-wallet-on-polymarket) below uses variables for **hours ago** and the **buyer** wallet. It returns two lists: `buys` and `sells`, each with `CollateralAmountInUSD` and `ConditionId` so you can group and calculate at your system. ```graphql query WalletTrades($hoursAgo: Int!, $buyer: String!) { EVM(dataset: realtime, network: matic) { buys: PredictionTrades( where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } OutcomeTrade: { IsOutcomeBuy: true, Buyer: { is: $buyer } } } Block: { Time: { since_relative: { hours_ago: $hoursAgo } } } } ) { Trade { OutcomeTrade { CollateralAmountInUSD } Prediction { ConditionId } } } sells: PredictionTrades( where: { Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } OutcomeTrade: { IsOutcomeBuy: false, Buyer: { is: $buyer } } } Block: { Time: { since_relative: { hours_ago: $hoursAgo } } } } ) { Trade { OutcomeTrade { CollateralAmountInUSD } Prediction { ConditionId } } } } } ``` Variables: ```json { "hoursAgo": 24, "buyer": "Wallet Address" } ``` ## Aggregate by Condition ID After you receive `buys` and `sells`, aggregate `CollateralAmountInUSD` by `ConditionId` for each list. ```text INITIALIZE empty map buyUsdByCondition INITIALIZE empty map sellUsdByCondition FOR each record IN response.buys: id = record.Trade.Prediction.ConditionId amount = record.Trade.OutcomeTrade.CollateralAmountInUSD // parse as decimal number buyUsdByCondition[id] = buyUsdByCondition[id] + amount FOR each record IN response.sells: id = record.Trade.Prediction.ConditionId amount = record.Trade.OutcomeTrade.CollateralAmountInUSD sellUsdByCondition[id] = sellUsdByCondition[id] + amount ``` ## Realised PnL calculation Use the union of all **condition IDs** that appear in either `buyUsdByCondition` or `sellUsdByCondition`, so conditions with only buys or only sells are still included. For each condition, **realised PnL in the window** is **sell collateral USD minus buy collateral USD**; **total realised PnL** is the sum of those values across all conditions. ```text INITIALIZE totalRealizedPnL = 0 INITIALIZE empty map pnlByCondition allConditionIds = union(keys(buyUsdByCondition), keys(sellUsdByCondition)) FOR each conditionId IN allConditionIds: buyTotal = buyUsdByCondition[conditionId] OR 0 sellTotal = sellUsdByCondition[conditionId] OR 0 pnlForCondition = sellTotal - buyTotal pnlByCondition[conditionId] = pnlForCondition totalRealizedPnL = totalRealizedPnL + pnlForCondition ``` **Summary** - `pnlByCondition`: `Per-ConditionId` net USD collateral flow for a given time window. - `totalRealizedPnL`: wallet-level sum of `pnlForCondition` over every condition in `allConditionIds`. ## Win rate calculation Using the same `pnlByCondition` map from the previous step, count how many conditions had **positive** PnL versus how many had **any** activity in the window, then derive **win rate**. ```text INITIALIZE trades = 0 INITIALIZE profitableTrades = 0 FOR each conditionId IN keys(pnlByCondition): trades = trades + 1 IF pnlByCondition[conditionId] > 0 THEN profitableTrades = profitableTrades + 1 END IF IF trades > 0 THEN winRatePercent = 100 * (profitableTrades / trades) ELSE winRatePercent = undefined // or 0, depending on product rules END IF ``` **Interpreting win rate** - `trades`: number of distinct `ConditionId` values with at least one matching row in `buys` or `sells` in your query result. - `profitableTrades`: subset of those where `pnlByCondition[conditionId] > 0`. - `winRatePercent`: `100 * profitableTrades / trades` when `trades > 0`. ## Related APIs | Topic | Link | | ----- | ---- | | PolyMarket APIs Intro | [Polymarket API](/docs/examples/polymarket-api/) | | Advanced Polymarket Analytics | [Advanced Polymarket Metrics](/docs/examples/polymarket-api/polymarket-advanced-analytics-api/) | | Prediction trades reference | [Prediction Trades API](/docs/examples/prediction-market/prediction-trades-api/) | | Sports Related PolyMarkets | [Polymarket Sports API](/docs/examples/polymarket-api/polymarket-sports-api/) | --- ## Prediction Market API: Markets, Trades and Settlements URL: https://docs.bitquery.io/docs/examples/prediction-market/prediction-market-api/ One GraphQL API for prediction markets such as Polymarket: market creation and resolution, buy and sell trades, and split, merge and redemption settlements on Polygon. # Prediction Market API The **Prediction Market API** is a **universal** API for querying **market lifecycle events**, **trades**, and **settlements** across prediction markets (e.g. Polymarket). Use it to filter by question title, event type, outcome, collateral token, and more. Additional prediction markets will be supported soon. **Networks:** Currently **Polygon** (`network: matic`). More prediction markets coming soon. ### Lifecycle flow | Stage | Cube | Activities | | -------------- | ----------------------- | -------------------------- | | **Management** | `PredictionManagements` | Market Created / Resolved | | **Trades** | `PredictionTrades` | Buy / Sell outcome tokens | | **Settlement** | `PredictionSettlements` | Split / Merge / Redemption | Flow: **Management** (Created) → **Trades** (Buy/Sell) → **Settlement** (Split/Merge/Redemption) This is a **universal** prediction market API: the same cubes and fields work across supported chains. Use `EVM(network: matic)` for Polygon today; more chains will be added over time. For contract-level and event-based Polymarket data (e.g. OrderFilled, ConditionResolution), see the [Polymarket API](/docs/examples/polymarket-api/) docs. ## Video Tutorial | Polymarket Whale Alerts Telegram Bot (Full Python Tutorial) ## PredictionManagements **PredictionManagements** returns market lifecycle events: **Created** (new market) and **Resolved** (outcome determined). You can filter by question title, event type, and prediction metadata (e.g. image URL, resolution source). For more examples (real-time stream, creations-only and resolutions-only subscriptions, counts), see the [Prediction Market Managements API](../prediction-managements-api) doc. Each event includes: - `EventType`: `"Created"` or `"Resolved"` - **Image** — market image URL (e.g. Polymarket asset) - **ResolutionSource** — URL used to resolve the outcome (e.g. price feed or sports data URL) [Run API](https://ide.bitquery.io/Query-latest-created-resolved-prediction-markets-for-Bitcoin) ```graphql query PredictionManagements { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Transaction_Time } where: { Management: { Prediction: { Question: { Title: { includes: "Bitcoin" } } } } } ) { Transaction { Hash Time } Block { Number } Management { Description EventType Prediction { CollateralToken { Name Symbol Decimals } Condition { Id QuestionId Outcomes { Id Label } } Marketplace { ProtocolName } Question { CreatedAt Id Image MarketId ResolutionSource Title } Outcome { Id Index Label } OutcomeToken { AssetId SmartContract } } } } } } ``` ### Key fields - **Management.EventType** — `"Created"` or `"Resolved"`. - **Management.Prediction.Question** — Title, MarketId, Id, Image, ResolutionSource, CreatedAt. **Title** is useful for filtering (e.g. by keyword like "Bitcoin"). **MarketId** links to full info: `https://gamma-api.polymarket.com/markets/{MarketId}`. **ResolutionSource** can be any URL or source that indicates where the market outcome is resolved (e.g. sports scores, crypto oracles, esports). - **Management.Prediction.Condition** — Id, QuestionId, **Outcomes** (all possible outcomes; usually two: Id, Label). - **Management.Prediction.Outcome** / **OutcomeToken** — For **Resolved**: winning outcome token Id/AssetId; for **Created**: often empty. - **Management.Prediction.CollateralToken** — Name, Symbol (e.g. USDC), Decimals. - **Management.Prediction.Marketplace** — ProtocolName. ## PredictionTrades (Recent Buys) Buy/sell activity on outcome tokens: taker/maker (Buyer/Seller), amounts, and whether the trade is a buy or sell of the outcome. For more examples (real-time stream, trades by market or trader, volume by outcome, current prices), see the [Prediction Market Trades API](../prediction-trades-api) doc. ```graphql query PredictionTrades { EVM(network: matic) { PredictionTrades( limit: { count: 10 } where: { Trade: { OutcomeTrade: { IsOutcomeBuy: true } } } orderBy: { descending: Transaction_Time } ) { Transaction { Hash Time } Block { Hash } Trade { Prediction { CollateralToken { Decimals Name SmartContract Symbol } ConditionId Marketplace { ProtocolName } Outcome { Label Id Index } Question { MarketId CreatedAt Id Image Title } OutcomeToken { Decimals ProtocolName AssetId } } OutcomeTrade { Amount Buyer Seller CollateralAmount IsOutcomeBuy } } } } } ``` ### Key fields - **Trade.OutcomeTrade.IsOutcomeBuy** — `true`: Seller (maker) gives USDC (collateral), Buyer (taker) gives outcome tokens. `false`: Buyer gives USDC (collateral), Seller gives outcome tokens. - **Trade.OutcomeTrade** — Amount, Buyer, Seller, CollateralAmount. - **Trade.Prediction.Question** — MarketId (full info: `https://gamma-api.polymarket.com/markets/{MarketId}`), Title, Id, Image, CreatedAt. - **Trade.Prediction.Outcome** — Label, Id (tokenId), Index (index in Condition.Outcomes; see PredictionManagements). - **Trade.Prediction.CollateralToken** — Token used to pay for the outcome (e.g. USDC): Symbol, Name, Decimals, SmartContract. - **Trade.Prediction.OutcomeToken** — Outcome as a token: AssetId, Decimals, ProtocolName. --- ## PredictionSettlements Split, merge, and redemption of outcome tokens (minting, merging positions, redeeming after resolution). For more examples (real-time stream, whale settlements, top winners, top markets), see the [Prediction Market Settlements API](../prediction-settlements-api) doc. ```graphql query PredictionSettlements { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descending: Transaction_Time } where: { Settlement: { EventType: { in: ["Split", "Merge", "Redemption"] } Prediction: { CollateralToken: { Symbol: { not: "USDC" } } } } } ) { Transaction { Hash Time } Block { Number } Settlement { Holder Amounts { Amount CollateralAmount } EventType OutcomeTokenIds Prediction { CollateralToken { Name Symbol Decimals } ConditionId Marketplace { ProtocolName SmartContract } OutcomeToken { Decimals SmartContract } Question { CreatedAt Id Image MarketId Title ResolutionSource } } } } } } ``` ### Key fields - **Settlement.EventType** — `"Split"`, `"Merge"`, or `"Redemption"`. - **Settlement.OutcomeTokenIds** — Token IDs from Condition.Outcomes. **Split/Merge:** all possible outcome token IDs; **Redemption:** usually all possible values. - **Settlement.Amounts** — Amount, CollateralAmount. - **Settlement.Holder** — Address that receives or sends. - **Settlement.Prediction.CollateralToken** — Token used: for **Split** the sender gives this for OutcomeTokenIds; for **Merge/Redemption** they receive it. Symbol can be e.g. `"USDC"` or `"WCOL"`. - **Settlement.Prediction.Question** — MarketId, Title, Id, Image, ResolutionSource, CreatedAt. - **Settlement.Prediction.OutcomeToken** — Outcome token contract; AssetId is typically empty here. --- ## Prediction Market Managements API URL: https://docs.bitquery.io/docs/examples/prediction-market/prediction-managements-api/ Prediction market management events: real-time streams of market creations and resolutions, latest managements, and created vs resolved counts for 24 hours. # Prediction Market Managements API The **PredictionManagements** API returns **market lifecycle** events for prediction markets (e.g. Polymarket) on Polygon: **Created** (new market) and **Resolved** (outcome determined). Use it to stream live creations and resolutions, list latest events, and count Created vs Resolved over a time window. **Network:** Polygon (`network: matic`). Part of the [Prediction Market API](../prediction-market-api) lifecycle (**Management** → Trades → Settlement). ### Event types | EventType | Description | | ----------- | ----------- | | **Created** | New prediction market created; condition and possible outcomes are set. | | **Resolved** | Market resolved; the winning outcome is determined. | --- ## Key fields - **Management.EventType** — `"Created"` or `"Resolved"`. - **Management.Prediction.Question** — **Title**, **MarketId**, Id, Image, ResolutionSource, CreatedAt. Use **Title** to filter (e.g. by keyword). **MarketId** links to full info: `https://gamma-api.polymarket.com/markets/{MarketId}`. - **Management.Prediction.Condition** — Id, QuestionId, Oracle, **Outcomes** (all possible outcomes for this market: Id, Index, Label). For **Created** events, this lists every outcome the market can settle to. - **Management.Prediction.Outcome** — For **Resolved** events: the **winning** outcome (Id, Index, Label). For **Created**: often empty. - **Management.Prediction.OutcomeToken** — For **Resolved**: token details for the winning outcome (Name, Symbol, **AssetId**, SmartContract). Use this for contract address and asset ID of the winning outcome token. - **Management.Prediction.CollateralToken** — Collateral token (e.g. USDC): Name, Symbol, AssetId, SmartContract. - **Management.Prediction.Marketplace** — ProtocolName, ProtocolFamily, SmartContract. --- --- ## Real-time management stream (creations + resolutions) Subscribe to all prediction market lifecycle events (Created and Resolved) as they occur on Polygon. [Run in Bitquery IDE](https://ide.bitquery.io/Prediction-Managements-subscription-resolutions-creations) ```graphql subscription PredictionManagementsStream { EVM(network: matic) { PredictionManagements { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest managements (historical) Fetch the most recent creation and resolution events with full details, ordered by block time. [Run in Bitquery IDE](https://ide.bitquery.io/latest-Prediction-managements-resolutions-creations) ```graphql query LatestPredictionManagements { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Created vs Resolved count (last 24 hours) Count how many **Created** and **Resolved** events occurred in the last 24 hours. Use the **Log.Signature.Name** or **Management.EventType** in the response to distinguish them. [Run in Bitquery IDE](https://ide.bitquery.io/last-24-hr-resolution-and-ceated-count) ```graphql query CreatedResolvedCountLast24h { EVM(network: matic) { PredictionManagements( where: { Block: { Time: { since_relative: { hours_ago: 24 } } } } ) { Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { EventType } count } } } ``` --- ## Real-time market creations (subscription) Subscribe only to **Created** events so you can track new markets as they are created. For each creation, the full list of possible outcomes for that market is available under **Prediction.Condition.Outcomes** (Id, Index, Label for each outcome). [Run in Bitquery IDE](https://ide.bitquery.io/track-realtime-new-polymarket-creations) ```graphql subscription RealtimeMarketCreations { EVM(network: matic) { PredictionManagements(where: { Management: { EventType: { is: "Created" } } }) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Real-time market resolutions (subscription) Subscribe only to **Resolved** events. For each resolution, the **winning outcome** is given in **Prediction.Outcome** (Id, Index, Label). Token-level details for that outcome—such as **AssetId** and **SmartContract**—are in **Prediction.OutcomeToken**. [Run in Bitquery IDE](https://ide.bitquery.io/track-realtime-polymarket-resolutions) ```graphql subscription RealtimeMarketResolutions { EVM(network: matic) { PredictionManagements(where: { Management: { EventType: { is: "Resolved" } } }) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest market creations (query) Fetch the most recent **Created** events. For each market, all possible outcomes are listed under **Prediction.Condition.Outcomes**. [Run in Bitquery IDE](https://ide.bitquery.io/latest-polymarket-creations) ```graphql query LatestMarketCreations { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Created" } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` --- ## Latest market resolutions (query) Query that returns the 10 most recent **Resolved** events. The winning outcome is in **Prediction.Outcome**; **Prediction.OutcomeToken** holds the asset ID and contract details for that outcome. [Run in Bitquery IDE](https://ide.bitquery.io/latest-polymarket-resolutions_1) ```graphql query LatestMarketResolutions { EVM(network: matic) { PredictionManagements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Management: { EventType: { is: "Resolved" } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Management { Description EventType Prediction { CollateralToken { Name SmartContract Symbol AssetId } Condition { Id Oracle Outcomes { Id Index Label } QuestionId } Marketplace { ProtocolName ProtocolFamily SmartContract } Outcome { Id Index Label } OutcomeToken { Symbol SmartContract Name AssetId } Question { CreatedAt Id Image MarketId ResolutionSource Title } } } Transaction { From Hash } } } } ``` For trades on outcome tokens, see [Prediction Market Trades API](../prediction-trades-api). For settlements (split, merge, redemption), see [Prediction Market Settlements API](../prediction-settlements-api). --- ## Prediction Market Settlements API URL: https://docs.bitquery.io/docs/examples/prediction-market/prediction-settlements-api/ Prediction market settlements: real-time and historical redemptions, merges and splits, whale settlements, top winners, top markets and top redeemers. # Prediction Market Settlements API The **PredictionSettlements** API returns **Split**, **Merge**, and **Redemption** events for prediction markets (e.g. Polymarket) on Polygon. Use it to stream live settlements, list latest activity, count events by type, find large redemptions (whales), and rank top winners or top markets by redeemed amount. **Network:** Polygon (`network: matic`). Part of the [Prediction Market API](/docs/examples/prediction-market/prediction-market-api) lifecycle (Management → Trades → **Settlement**). ### Event types | EventType | Description | | -------------- | -------------------------------------------------------------------------- | | **Split** | Collateral converted into outcome tokens (minting). | | **Merge** | Outcome tokens converted back to collateral. | | **Redemption** | After resolution: winning outcome tokens redeemed for collateral (payout). | --- ## Key fields - **Settlement.EventType** — `"Split"`, `"Merge"`, or `"Redemption"`. - **Settlement.OutcomeTokenIds** — Token IDs from the condition. **Split/Merge:** all outcome token IDs; **Redemption:** typically all (redeemer holds winning outcome). - **Settlement.Amounts** — **Amount** (outcome token amount), **CollateralAmount**, **AmountInUSD**, **CollateralAmountInUSD**. - **Settlement.Holder** — Address that receives or sends tokens/collateral. - **Settlement.Prediction.CollateralToken** — Collateral token (e.g. USDC): Name, Symbol, AssetId, SmartContract. - **Settlement.Prediction.Question** — **Title**, MarketId, Id, Image, ResolutionSource, CreatedAt. Use **Title** to filter by market (e.g. specific question). - **Settlement.Prediction.Outcome** — Id, Index, Label (winning outcome on Redemption). - **Settlement.Prediction.Marketplace** — ProtocolName, ProtocolFamily, SmartContract. - **Settlement.Prediction.OutcomeToken** — Outcome token contract: Name, Symbol, AssetId, SmartContract. --- --- ## Real-time settlement stream Subscribe to live Split, Merge, and Redemption events as they occur on Polygon. [Run in Bitquery IDE](https://ide.bitquery.io/realtime-predicion-market-settlements-stream) ```graphql subscription PredictionSettlementsStream { EVM(network: matic) { PredictionSettlements { Block { Time } Log { Signature { Name } SmartContract } Settlement { Amounts { Amount AmountInUSD CollateralAmount CollateralAmountInUSD } EventType Holder OutcomeTokenIds Prediction { CollateralToken { Name Symbol AssetId SmartContract } ConditionId OutcomeToken { Name Symbol AssetId SmartContract } Marketplace { SmartContract ProtocolFamily ProtocolName } Question { Title MarketId ResolutionSource Image CreatedAt Id } Outcome { Id Index Label } } } Transaction { Hash } } } } ``` --- ## Latest settlements (historical) Fetch the most recent settlements with full details, ordered by block time. [Run in Bitquery IDE](https://ide.bitquery.io/latest-prediction-market-settlements_2) ```graphql query LatestPredictionSettlements { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descending: Block_Time } ) { Block { Time } Log { Signature { Name } SmartContract } Settlement { Amounts { Amount AmountInUSD CollateralAmount CollateralAmountInUSD } EventType Holder OutcomeTokenIds Prediction { CollateralToken { Name Symbol AssetId SmartContract } ConditionId OutcomeToken { Name Symbol AssetId SmartContract } Marketplace { SmartContract ProtocolFamily ProtocolName } Question { Title MarketId ResolutionSource Image CreatedAt Id } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` --- ## Redemption / Merge / Split count (last 1 hour) Count how many settlement events occurred in the last hour, grouped by event signature (Split, Merge, Redemption). [Run in Bitquery IDE](https://ide.bitquery.io/redemptions-merge-split-count-in-last-1-hour) ```graphql query RedemptionsMergeSplitCount { EVM(network: matic) { PredictionSettlements( where: { Block: { Time: { since_relative: { hours_ago: 1 } } } } ) { count Log { Signature { Name } } } } } ``` --- ## Latest whale settlements (large redemptions) Find the most recent high-value redemptions (e.g. amount ≥ 10,000 USD). Useful for tracking large payouts and whale activity. [Run in Bitquery IDE](https://ide.bitquery.io/latest-whale-settlements-on-prediction-market_2) ```graphql query MyQuery { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descending: Block_Time } where: { Settlement: { EventType: { is: "Redemption" } Amounts: { CollateralAmountInUSD: { ge: "10000" } } } } ) { Block { Time } Log { Signature { Name } SmartContract } Settlement { Amounts { Amount AmountInUSD CollateralAmount CollateralAmountInUSD } EventType Holder OutcomeTokenIds Prediction { CollateralToken { Name Symbol AssetId SmartContract } ConditionId OutcomeToken { Name Symbol AssetId SmartContract } Marketplace { SmartContract ProtocolFamily ProtocolName } Question { Title MarketId ResolutionSource Image CreatedAt Id } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` --- ## Top 10 winners of a specific market question Rank holders by total redeemed amount USD for one market (filter by question title). Replace the title with your market question. [Run in Bitquery IDE](https://ide.bitquery.io/top-10-winners-of-a-market-question_1) ```graphql query MyQuery { EVM(network: matic) { PredictionSettlements( limit: {count: 10} orderBy: {descendingByField: "redeemed_amount"} where: {Block: {Time: {since_relative: {hours_ago: 1}}}, Settlement: {EventType: {is: "Redemption"}, Prediction: {Question: {Title: {is: "Will Trump nominate Judy Shelton as the next Fed chair?"}}}}} ) { Settlement { Holder } redeemed_amount: sum(of: Settlement_Amounts_CollateralAmountInUSD selectWhere:{gt:"0"}) count } } } ``` --- ## Top 10 market questions by redeemed amount (last 1 hour) Aggregated redemptions by market question and sort by total redeemed amount. Use this to see which markets had the most payout activity recently. [Run in Bitquery IDE](https://ide.bitquery.io/top-10-market-questions-in-last-1-hour_2) ```graphql query MyQuery { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descendingByField: "redeemed_amount" } where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Settlement: { EventType: { is: "Redemption" } } } ) { Settlement { Prediction { Question { Title } Outcome { Label } } } count(distinct:Settlement_Holder) redeemed_amount: sum(of: Settlement_Amounts_CollateralAmountInUSD) } } } ``` --- ## Top 10 redeemers (last 1 hour) Rank addresses by total amount redeemed in the last hour across all markets. Useful for leaderboards and whale tracking. [Run in Bitquery IDE](https://ide.bitquery.io/top-10-redeemers) ```graphql query TopRedeemers { EVM(network: matic) { PredictionSettlements( limit: { count: 10 } orderBy: { descendingByField: "redeemed_amount" } where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Settlement: { EventType: { is: "Redemption" } } } ) { Settlement { Holder } redeemed_amount: sum(of: Settlement_Amounts_Amount) } } } ``` --- ## Use cases | Use case | Approach | | ---------------------------------- | ------------------------------------------------------------------------------------------------ | | **Live settlement feed** | Use the real-time subscription above. | | **Recent activity** | Use _Latest settlements_ with `limit` and `orderBy: { descending: Block_Time }`. | | **Volume by event type** | Use _Redemption/Merge/Split count_ with a time window. | | **Large payouts** | Use _Latest whale settlements_ with `Amounts.Amount: { ge: "..." }` and `EventType: Redemption`. | | **Winners for one market** | Use _Top 10 winners of a market question_ with `Question.Title: { is: "..." }`. | | **Hottest markets by redemptions** | Use _Top 10 market questions by redeemed amount_. | | **Top redeemers** | Use _Top 10 redeemers_ (optionally change `hours_ago` or add more filters). | For market creation and resolution events, see [PredictionManagements](/docs/examples/prediction-market/prediction-market-api#predictionmanagements). For trades, see [PredictionTrades](/docs/examples/prediction-market/prediction-market-api#predictiontrades-recent-buys). --- ## Prediction Market Trades API URL: https://docs.bitquery.io/docs/examples/prediction-market/prediction-trades-api/ Prediction market trades: real-time trade stream, latest trades, trades by market or trader, Yes/No volume, and current price per outcome. # Prediction Market Trades API The **PredictionTrades** API returns **buy/sell** activity on outcome tokens for prediction markets (e.g. Polymarket) on Polygon. Use it to stream live trades, list latest activity, filter by market or trader, compute volume per outcome, and get current prices per outcome from the latest trade. **Network:** Polygon (`network: matic`). Part of the [Prediction Market API](../prediction-market-api) lifecycle (Management → **Trades** → Settlement). ### Trade direction - **IsOutcomeBuy: true** — Seller (maker) gives USDC (collateral), Buyer (taker) gives outcome tokens. - **IsOutcomeBuy: false** — Buyer gives USDC (collateral), Seller gives outcome tokens. ## Key fields - **Trade.OutcomeTrade.IsOutcomeBuy** — `true`: Seller gives collateral (USDC), Buyer gives outcome tokens; `false`: Buyer gives collateral, Seller gives outcome tokens. - **Trade.OutcomeTrade** — **Buyer**, **Seller**, **Amount** (outcome tokens), **CollateralAmount**, **CollateralAmountInUSD**, **OrderId**, **Price**, **PriceInUSD**. - **Trade.Prediction.Question** — **Title**, **MarketId**, Id, Image, ResolutionSource, CreatedAt. Use **MarketId** to filter by market (e.g. `"1391179"`). Full info: `https://gamma-api.polymarket.com/markets/{MarketId}`. - **Trade.Prediction.Outcome** — **Label**, Id, Index. - **Trade.Prediction.CollateralToken** — Token used to pay for the outcome (e.g. USDC): Name, Symbol, SmartContract, AssetId. - **Trade.Prediction.OutcomeToken** — Outcome as a token: Name, Symbol, SmartContract, **AssetId** (use for volume/price per outcome). - **Trade.Prediction.Marketplace** — ProtocolName, ProtocolFamily, SmartContract, ProtocolVersion. --- --- ## Polymarket-only filter {#polymarket-only-filter} To restrict results to **Polymarket** only, add this to your `where` clause: ```graphql Trade: { Prediction: { Marketplace: { ProtocolName: { is: "polymarket" } } } } ``` Example: real-time stream for Polymarket only — use the [real-time trades stream](#real-time-trades-stream) query and add the filter above inside `PredictionTrades(where: { ... })`. ## Real-time trades stream Subscribe to live prediction market trades as they occur on Polygon (successful transactions only). [Run in Bitquery IDE](https://ide.bitquery.io/prediction-market-trades-subscription) ```graphql subscription PredictionTradesStream { EVM(network: matic) { PredictionTrades(where: { TransactionStatus: { Success: true } }) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## Latest Prediction Market trades API Fetch the most recent prediction market trades with full details, ordered by block time. [Run in Bitquery IDE](https://ide.bitquery.io/latest-prediction-market-trades) ```graphql query LatestPredictionTrades { EVM(network: matic) { PredictionTrades( limit: { count: 50 } orderBy: { descending: Block_Time } where: { TransactionStatus: { Success: true } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## Stream Trades for a specific market Subscribe to trades for one market only by filtering on **Question.MarketId**. Replace `"1391179"` with your market ID. [Run in Bitquery IDE](https://ide.bitquery.io/subscribe-to-specific-market-trades) ```graphql subscription TradesForSpecificMarket { EVM(network: matic) { PredictionTrades( where: { TransactionStatus: { Success: true } Trade: { Prediction: { Question: { MarketId: { is: "1391179" } } } } } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` ## Trades for a specific trader Fetch all trades where the given address is either **Buyer** or **Seller**. Pass the trader address as the `$trader` variable. [Run in Bitquery IDE](https://ide.bitquery.io/Trades-for-a-specific-trader) ```graphql query TradesForTrader($trader: String) { EVM(network: matic) { PredictionTrades( limit: { count: 50 } orderBy: { descending: Block_Time } where: { TransactionStatus: { Success: true } any: [ { Trade: { OutcomeTrade: { Buyer: { is: $trader } } } } { Trade: { OutcomeTrade: { Seller: { is: $trader } } } } ] } ) { Block { Time } Call { Signature { Name } } Log { Signature { Name } SmartContract } Trade { OutcomeTrade { Buyer Seller Amount CollateralAmount CollateralAmountInUSD OrderId Price PriceInUSD IsOutcomeBuy } Prediction { CollateralToken { Name Symbol SmartContract AssetId } ConditionId OutcomeToken { Name Symbol SmartContract AssetId } Marketplace { SmartContract ProtocolVersion ProtocolName ProtocolFamily } Question { Title ResolutionSource Image MarketId Id CreatedAt } Outcome { Id Index Label } } } Transaction { From Hash } } } } ``` **Variables (example):** ```json { "trader": "0x101f2f96db1e39a9f36a1fa067751d541fd38e1a" } ``` ## Total volume and Yes/No volume for a market Aggregate USD volume for a market over a time window: total volume plus volume per outcome. Many markets have **two outcomes** (e.g. Yes/No, Up/Down); this example uses a **Yes/No** market, so we split volume by outcome label "Yes" and "No". Pass the market’s outcome token **AssetId**s in `$marketAssets` (typically two: one per outcome). [Run in Bitquery IDE](https://ide.bitquery.io/total-volume-outcome-1-volume-outcome-2-volume-of-a-market) ```graphql query MarketVolumeByOutcome($marketAssets: [String!]) { EVM(network: matic) { PredictionTrades( where: { Block: { Time: { since_relative: { hours_ago: 1 } } } TransactionStatus: { Success: true } Trade: { Prediction: { OutcomeToken: { AssetId: { in: $marketAssets } } } } } ) { Trade { Prediction { Question { Title ResolutionSource Image MarketId Id CreatedAt } } } yes_volume: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { Prediction: { Outcome: { Label: { is: "Yes" } } } } } ) no_volume: sum( of: Trade_OutcomeTrade_CollateralAmountInUSD if: { Trade: { Prediction: { Outcome: { Label: { is: "No" } } } } } ) total_volume: sum(of: Trade_OutcomeTrade_CollateralAmountInUSD) } } } ``` **Variables (example):** ```json { "marketAssets": [ "46746822541461330721074821991383617225657173789584499857683753079229786702095", "70771354585365381988139008309072205730081182435161568795508496003376222185889" ] } ``` ## Current price per outcome (latest trade) Get the latest trade price for each outcome in a market (e.g. Yes/No, Up/Down—each market defines its own outcome labels). Uses `limitBy` so you get one row per outcome (by `Trade_Prediction_OutcomeToken_AssetId`), with **Price** and **PriceInUSD** taken at the maximum block time (most recent). The response includes **Outcome.Label** so you can see which outcome each price refers to. [Run in Bitquery IDE](https://ide.bitquery.io/Current-price-inside-the-market-for-all-options-based-on-latest-trade) ```graphql query CurrentPricePerOutcome { EVM(network: matic) { PredictionTrades( limitBy: { by: Trade_Prediction_OutcomeToken_AssetId, count: 1 } where: { TransactionStatus: { Success: true } Trade: { Prediction: { Question: { MarketId: { is: "1391179" } } } } } ) { Trade { OutcomeTrade { Price(maximum: Block_Time) PriceInUSD(maximum: Block_Time) } Prediction { OutcomeToken { Name AssetId } Outcome { Id Label } } } } } } ``` For market creation and resolution, see [PredictionManagements](../prediction-market-api#predictionmanagements). For settlements (split, merge, redemption), see [Prediction Market Settlements API](../prediction-settlements-api). --- ## Price Index Algorithm URL: https://docs.bitquery.io/docs/trading/crypto-price-api/price-index-algorithm/ Price Index Algorithm via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. See examples in the Bitquery IDE. # Price Index Algorithm This page describes how the Price Index (Crypto Price API) filters trades and computes prices for tokens and currencies. It is the single source of truth for the algorithm in effect from **April 2026**. ## Which Trades Are Filtered Out A trade is **excluded** from price and volume calculations only if: 1. **Zero amount**: The trade amount is 0. 2. **Below decimal precision**: The trade amount is so small that it would be lost to precision. For a token with `D` decimal places, trades with amount less than `10^D / 10,000` are excluded. **Example**: USDT has 9 decimals. So `10^9 / 10,000 = 100,000` in the smallest units, i.e. **0.00001 USDT**. Any trade smaller than 0.00001 USDT is not accounted for, to avoid precision loss. In other words: a trade must have at least 10,000 units of decimal precision (e.g. for 9 decimals, at least 0.00001 of the token). ## Time Window and Volume Weighting Price and volume aggregation use a **1-hour rolling** window. Each trade (or price entry) contributes to volume weighting with a factor **`exp(-decayRate × age)`**, where **age** is how far back in time the entry lies within that window (0 at the newest edge, increasing toward the older edge). That gives **exponential decay** toward older data: the **most recent** entries contribute **~100%** of their raw volume, while an entry at the **midpoint** of the window contributes **50%** of its volume (**50% exponential average** profile relative to the window). Volume weight decays from the newest edge of the 1-hour window toward older trades Aggregations (pool, token, and **non-stablecoin** currency) use these **decayed** volume contributions instead of raw sums over the window. Trade filtering, exponential decay within the 1-hour window, and effective volume per market ## 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. Multi-market aggregation to token price, market sort, volume-weighted blend, and API ranking ## 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) Solana Scam Token Checker app with a Pump.fun token address field and a table scoring recent tokens on liquidity, insider, creator-holdings and holder checks ## 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. Realtime Liquidity Drain Detection dashboard showing 6 critical alerts across 3 pools, including a rapid-drain alert for an HLS/WETH Uniswap V3 pool with its liquidity metrics > **⚠️ 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=&type=&severity=&limit= ``` Returns filtered list of alerts. Filters: - `pool`: Filter by pool address or pool ID (case-insensitive) - `type`: Filter by alert type (`liquidity_drop`, `max_amount_decrease`, `rapid_drain`) - `severity`: Filter by severity (`warning`, `critical`) - `limit`: Maximum number of alerts to return (default: 100) **Response:** ```json { "alerts": [...], "total": 10, "total_all": 50 } ``` #### Get Alert Statistics ```http GET /api/alerts/stats ``` Returns statistics about all alerts: ```json { "total_alerts": 50, "by_severity": {"critical": 20, "warning": 30}, "by_type": {"liquidity_drop": 45, "rapid_drain": 5}, "by_dex": {"uniswap_v2": 30, "uniswap_v3": 20}, "unique_pools": 15 } ``` #### Get Configuration ```http GET /api/config ``` Returns current detection thresholds: ```json { "liquidity_drop_warning": 20.0, "liquidity_drop_critical": 40.0, "max_amount_decrease_warning": 30.0, "max_amount_decrease_critical": 50.0, "rapid_drain_threshold": 20.0 } ``` #### Update Configuration ```http POST /api/config Content-Type: application/json { "liquidity_drop_warning": 25.0, "liquidity_drop_critical": 45.0 } ``` Updates detection thresholds (validates values are between 0-100). #### Add Alert (Internal) ```http POST /api/alerts Content-Type: application/json { "pool_id": "0x...", "severity": "critical", "alert_type": "liquidity_drop", "message": "WETH dropped 90.4% from baseline", "timestamp": "2026-01-15T13:17:25Z", "pool_info": { "pool_id": "...", "pool_address": "0x...", "currency_pair": "WETH/ASTRE", "dex": "uniswap_v2", "transaction_hash": "0x..." }, "metrics": {...} } ``` Called by the detector to add new alerts. Alerts are stored in memory (max 1000, or 24 hours retention). #### Get Pools ```http GET /api/pools ``` Returns list of unique pools that have generated alerts: ```json { "pools": [ { "pool_id": "0x...", "pool_address": "0x...", "currency_pair": "WETH/ASTRE", "dex": "uniswap_v2", "alert_count": 5 } ], "total": 15 } ``` ## Understanding the Data ### Protobuf Message Structure The tool processes `DexPoolBlockMessage` protobuf messages from Kafka, which contain: - **PoolEvents**: Array of pool update events - **Pool**: Pool information (address, PoolId, currencies, decimals) - **Liquidity**: Current reserves (AmountCurrencyA, AmountCurrencyB) - already human-readable floats - **PoolPriceTable**: Slippage and price data - **AtoBPrices**: Array of price info for A→B swaps at different slippage levels - **BtoAPrices**: Array of price info for B→A swaps at different slippage levels - Each price info contains: SlippageBasisPoints, MaxAmountIn, MinAmountOut, Price - **Dex**: DEX protocol information (ProtocolName, SmartContract) - **TransactionHeader**: Transaction hash (optional) ### Pool Identification The tool uses different pool identification strategies: - **Uniswap V4**: Uses `PoolId` (bytes converted to hex) since liquidity is aggregated in PoolManager - **Other Protocols**: Uses composite key: `{pool_address}_{currency_a}_{currency_b}` (sorted) ### Important Notes - **Uniswap V4**: Liquidity amounts are aggregated across all pools in the PoolManager contract. Use `PoolId` to differentiate between pools. MaxAmountIn is pool-specific and will show drops. - **Baseline Calculation**: Uses the mean of measurements from the last 24 hours (not max) for better sensitivity to gradual drains - **Human-Readable Values**: The protobuf schema now sends liquidity and slippage values as human-readable floats (no decimal conversion needed) - **Validation**: Always verify alerts by checking the transaction hash on Etherscan ## Use Cases ### 1. DeFi Security Monitoring Monitor DEX pools for suspicious liquidity withdrawals that may indicate: - Liquidity drain attacks - Protocol exploits - Market manipulation ### 2. Risk Management Before executing large trades: - Check current pool liquidity depth - Verify sufficient liquidity exists for your trade size - Monitor for recent liquidity drains that may affect execution ### 3. Trading Strategy Protection - Avoid entering positions when liquidity is thin - Detect when pools become less liquid - Identify pools experiencing rapid liquidity growth or decline ### 4. Portfolio Protection - Monitor pools for tokens in your portfolio - Get alerts before liquidity drains affect token prices - Track liquidity health across multiple pools ## Related Documentation - [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) - [EVM Protobuf Kafka Streams](/docs/streams/protobuf/chains/EVM-protobuf/) - [Ethereum Liquidity API](/docs/blockchain/Ethereum/dextrades/ethereum-liquidity-api) - [Ethereum Slippage API](/docs/blockchain/Ethereum/dextrades/ethereum-slippage-api) - [DEXPools Cube Documentation](/docs/cubes/evm-dexpool/) ## Important Considerations This tool is provided as a **proof of concept** and should be considered a starting point for building your own liquidity monitoring solution. The detection logic may generate false positives due to legitimate large trades, normal liquidity rebalancing, temporary market fluctuations, or legitimate protocol operations. For production use, you should modify the detection logic, tune thresholds based on your specific use case, implement additional validation rules, and integrate with your existing monitoring infrastructure. This tool is not intended for production use without significant modifications, testing, and hardening. ## Support For issues or questions: - Open an issue on [GitHub](https://github.com/Akshat-cs/realtime-liquidity-drain-detector) - Contact Bitquery support via [Telegram](https://t.me/Bloxy_info) - Check the [Bitquery Documentation](https://docs.bitquery.io/) --- ## Setting Up App.js URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/final-step/ Build Setting Up App.js: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. See examples in the Bitquery IDE. # Setting Up `App.js` To complete the setup and run the chart, we need to call the `TVChartContainer` component inside the main `App.js` file. Here's the process: --- ### 1. `App.js` ```javascript function App() { return (
); } export default App; ``` - **App.js**: This is the entry point of your React app. It imports the `TVChartContainer` component, which encapsulates the TradingView chart logic. The component is rendered inside a `div` with the class `App`. --- ### 2. Run the Application Now that you have set up `App.js`, you can run the application using the following command: ```bash npm start ``` #### Opening the URL The sample code uses the token address from the url, so you should pass it like this for example: ``` http://localhost:3000/?base=So11111111111111111111111111111111111111112"e=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v ``` This will start the development server, and your TradingView chart with both historical and real-time OHLC data should now be visible. --- ## Slippage Faq Using Dexpool Stream URL: https://docs.bitquery.io/docs/API-Blog/slippage-faq-using-dexpool-stream/ Slippage FAQ for DEX swaps: use DEXPool stream price tables to set amountOutMin, handle removeLiquidity, token decimals, front-running and price impact. # Slippage FAQ: Calculating Necessary Slippage for DEX Swaps Using Bitquery DEXPool Stream This FAQ addresses common developer questions about calculating necessary slippage before executing swaps or withdrawing liquidity on DEXs from smart contracts. Instead of guessing or using trial-and-error, you can use Bitquery's DEXPool stream data to programmatically determine the appropriate slippage tolerance. ## What is the Best Way to Calculate Necessary Slippage Before Making a Swap from a Smart Contract? Instead of hardcoding slippage tolerances or using trial-and-error, you can calculate necessary slippage programmatically using Bitquery's DEXPool stream data. The stream provides pre-calculated price tables showing exactly how much slippage to expect for different trade sizes. **The Problem with Fixed Slippage:** Many developers hardcode slippage tolerances (e.g., 1% or 5%) in their contracts, but this can lead to: - Failed transactions if liquidity drops (slippage too low) - Unnecessary losses if liquidity is high (slippage too high) - Front-running vulnerabilities when slippage tolerance is set too high **Solution Using Bitquery DEXPool Stream:** The DEXPool stream's `PoolPriceTable` contains `AtoBPrices` and `BtoAPrices` arrays with pre-calculated slippage data at multiple levels (0.1%, 0.5%, 1%, 2%, 5%, 10%). By querying this data before executing a swap, you can: 1. Find the appropriate slippage level for your trade size 2. Get the guaranteed minimum output amount (`MinAmountOut`) for that slippage level 3. Pass this as the `amountOutMin` parameter to functions like `swapExactTokensForTokens` or `removeLiquidity` **Example from DEXPool Stream Data:** ```json { "PoolPriceTable": { "AtoBPrices": [ { "SlippageBasisPoints": 10, "MaxAmountIn": 2557952147, "MinAmountOut": 860478002991619427, "Price": 0.0003364734002389014 }, { "SlippageBasisPoints": 100, "MaxAmountIn": 25456674083, "MinAmountOut": 8465959707294551328, "Price": 0.00033264263765886426 } ] } } ``` **Important:** All amounts (`MaxAmountIn`, `MinAmountOut`) are in raw units (smallest token units), NOT decimal-adjusted. For example, if CurrencyA is USDC (6 decimals), `MaxAmountIn: 2557952147` = 2,557.952147 USDC. If CurrencyB is WETH (18 decimals), `MinAmountOut: 860478002991619427` = 0.860478002991619427 WETH. If you want to swap 25,456 USDC (25,456,000,000 in raw units with 6 decimals), check the `AtoBPrices` array: - At 0.1% slippage (10 basis points): `MaxAmountIn: 2557952147` (2,557.95 USDC) - your trade is too large - At 1% slippage (100 basis points): `MaxAmountIn: 25456674083` (25,456.67 USDC) - your trade fits! Use `MinAmountOut: 8465959707294551328` as your `amountOutMin` **Note:** All `MaxAmountIn` and `MinAmountOut` values in the DEXPool stream are in raw units (smallest token units), not decimal-adjusted. `MaxAmountIn` is in CurrencyA's raw units (e.g., USDC with 6 decimals), and `MinAmountOut` is in CurrencyB's raw units (e.g., WETH with 18 decimals). ## How Do I Determine Slippage Tolerance Programmatically Instead of Guessing? You can determine slippage tolerance programmatically by: 1. **Query DEXPool Stream**: Get the latest `PoolPriceTable` for your target pool 2. **Find Your Trade Size**: Check which `SlippageBasisPoints` level has a `MaxAmountIn` that accommodates your trade 3. **Extract MinAmountOut**: Use the `MinAmountOut` value from that slippage level 4. **Calculate Tolerance**: `slippageTolerance = SlippageBasisPoints / 10000` (e.g., 100 = 1%) **Using the Data Structure:** The DEXPool stream data structure is: ``` PoolEvents[].PoolPriceTable.AtoBPrices[] (or BtoAPrices[]) - SlippageBasisPoints: 10, 50, 100, 200, 500, 1000 (0.1%, 0.5%, 1%, 2%, 5%, 10%) - MaxAmountIn: Maximum input amount at this slippage level - MinAmountOut: Guaranteed minimum output (use as amountOutMin) - Price: Average execution price at this slippage level ``` **Implementation Approach:** ```javascript // Pseudo-code example function calculateSlippageTolerance(tradeAmount, poolPriceTable) { const atoBPrices = poolPriceTable.AtoBPrices; // Find the lowest slippage level that can handle your trade for (const priceData of atoBPrices) { if (tradeAmount <= priceData.MaxAmountIn) { return { slippageBasisPoints: priceData.SlippageBasisPoints, slippageTolerance: priceData.SlippageBasisPoints / 10000, // Decimal format (0.01 for 1%) minAmountOut: priceData.MinAmountOut, price: priceData.Price, }; } } // Trade too large for even 10% slippage return null; } ``` ## How is Slippage Calculated in AMMs Like Uniswap? Slippage in Automated Market Makers (AMMs) is calculated based on the constant product formula (x \* y = k) and the liquidity available in the pool. However, manually calculating this is complex, especially for concentrated liquidity pools (Uniswap V3/V4). **Traditional Calculation:** The constant product formula means that when you swap tokens: - Removing tokens from one side increases the price - The larger your trade relative to pool liquidity, the higher the slippage - Formula: `(x + Δx) * (y - Δy) = k` where k is constant **Why Manual Calculation is Difficult:** - **Uniswap V2**: Requires current reserves and applying the constant product formula - **Uniswap V3/V4**: Much more complex due to concentrated liquidity, ticks, and price ranges - **Real-time updates**: Pool state changes with every trade, making calculations stale quickly **How Bitquery DEXPool Stream Solves This:** The DEXPool stream provides pre-calculated slippage data by simulating swaps through the pool's initialized ticks. This gives you: - **Accurate calculations**: Done by simulating actual swaps through pool ticks - **Real-time data**: Updates whenever liquidity changes (Mint, Burn, Swap events) - **Multiple slippage levels**: Six different slippage scenarios (0.1%, 0.5%, 1%, 2%, 5%, 10%) - **No manual math**: Just query and use the `MinAmountOut` value directly ## How Can I Use DEXPool Stream Data to Set amountOutMin for swapExactTokensForTokens? The `swapExactTokensForTokens` function requires an `amountOutMin` parameter, which is the minimum amount of output tokens you're willing to accept. You can get this value directly from the DEXPool stream's `PoolPriceTable`. **Step-by-Step Process:** 1. **Query DEXPool Stream** for your target pool address 2. **Access PoolPriceTable**: Get `PoolPriceTable.AtoBPrices` (swapping CurrencyA → CurrencyB) or `BtoAPrices` (swapping CurrencyB → CurrencyA) 3. **Find Matching Slippage Level**: Locate the entry where `MaxAmountIn >= yourTradeAmount` 4. **Use MinAmountOut**: The `MinAmountOut` field is your `amountOutMin` parameter **Example from Real Data:** For a USDC/WETH pool (Uniswap V4), if you want to swap 25,000 USDC (with 6 decimals = 25000000000 in raw units): ```json { "PoolPriceTable": { "AtoBPrices": [ { "SlippageBasisPoints": 100, "MaxAmountIn": 25456674083, "MinAmountOut": 8465959707294551328, "Price": 0.00033264263765886426 } ] } } ``` Since 25000000000 < 25456674083 (your trade amount is less than MaxAmountIn), this slippage level (1%) works. **Important Note:** The `MinAmountOut: 8465959707294551328` in the price table is the minimum output for a trade at `MaxAmountIn` (25,456.67 USDC). For a smaller trade like 25,000 USDC, the actual minimum output would be proportionally less. Using the table's `MinAmountOut` is conservative (safer) but may cause transactions to fail if the price moves. For precise calculations, you'd need to calculate the proportional minimum based on your actual trade size, but using the table value provides a safe upper bound. `MaxAmountIn: 25456674083` = 25,456.67 USDC in human-readable format (raw units ÷ 10^6). Your trade of 25,000 USDC fits within this limit. **In Your Smart Contract:** ```solidity // After querying DEXPool stream off-chain uint256 amountOutMin = 8465959707294551328; // From MinAmountOut field IUniswapV2Router02(router).swapExactTokensForTokens( amountIn, amountOutMin, // Use MinAmountOut from DEXPool stream path, to, deadline ); ``` ## How Do I Calculate Slippage for Removing Liquidity (removeLiquidity)? Similar to swaps, you can use DEXPool stream data to calculate slippage when removing liquidity. The `PoolPriceTable` shows liquidity depth, which helps determine how removing liquidity will affect token amounts received. **Understanding Liquidity Removal Slippage:** When you remove liquidity, you receive both tokens in the pair. The ratio depends on: - Current pool reserves (available in `Liquidity.AmountCurrencyA` and `AmountCurrencyB`) - Your liquidity share (LP token balance) - Current pool price **Using DEXPool Stream Data:** The DEXPool stream provides: - `Liquidity.AmountCurrencyA`: Current reserves of CurrencyA in the pool - `Liquidity.AmountCurrencyB`: Current reserves of CurrencyB in the pool - `PoolPriceTable.AtoBPrice`: Current spot price (CurrencyA per CurrencyB) - `PoolPriceTable.BtoAPrice`: Current spot price (CurrencyB per CurrencyA) **Calculating Expected Output:** 1. Calculate your share: `yourShare = yourLPTokens / totalLPTokens` 2. Expected CurrencyA: `expectedA = Liquidity.AmountCurrencyA * yourShare` 3. Expected CurrencyB: `expectedB = Liquidity.AmountCurrencyB * yourShare` 4. Apply slippage tolerance: `minA = expectedA * (1 - slippageTolerance)`, `minB = expectedB * (1 - slippageTolerance)` **Example:** If the pool has: - `AmountCurrencyA: 1000000000000` (1M USDC with 6 decimals) - `AmountCurrencyB: 337656994815915800` (0.337 WETH with 18 decimals) - Your LP tokens represent 1% of the pool Expected output: - CurrencyA: `1000000000000 * 0.01 = 10000000000` (10,000 USDC) - CurrencyB: `337656994815915800 * 0.01 = 3376569948159158` (0.003376 WETH) With 1% slippage tolerance: - `minAmountA = 10000000000 * 0.99 = 9900000000` - `minAmountB = 3376569948159158 * 0.99 = 3342804248677576` ## What's the Difference Between Price Impact and Slippage Tolerance? **Price Impact** is the change in the pool's price caused by your trade. It's calculated based on the AMM formula and current liquidity. **Slippage Tolerance** is the maximum price movement you're willing to accept - it's a parameter you set in your transaction. **In Practice:** - **Price Impact**: `(Price After Trade - Price Before Trade) / Price Before Trade` - **Slippage Tolerance**: The maximum price movement you allow (e.g., 1% means you'll accept up to 1% worse price) **How DEXPool Stream Helps:** The `PoolPriceTable` shows both: - `Price`: Average execution price at different slippage levels (represents price impact) - `SlippageBasisPoints`: The slippage tolerance level - `MinAmountOut`: Guaranteed minimum output at that slippage tolerance By comparing the `Price` field across different `SlippageBasisPoints` levels, you can see how price impact increases with larger trades. **Example from Data:** ```json { "AtoBPrices": [ { "SlippageBasisPoints": 10, "Price": 0.0003364734002389014, "MaxAmountIn": 2557952147 }, { "SlippageBasisPoints": 1000, "Price": 0.00030166094074957073, "MaxAmountIn": 278317227427 } ] } ``` At 0.1% slippage: Price = 0.00033647 (better price, smaller max trade) At 10% slippage: Price = 0.00030166 (worse price, larger max trade) The price difference shows the price impact: `(0.00033647 - 0.00030166) / 0.00033647 ≈ 10.34%` ## How Can I Avoid Front-Running by Setting Appropriate Slippage? Setting slippage tolerance too high can make you vulnerable to front-running bots that exploit the gap between your tolerance and actual price impact. Using DEXPool stream data helps you set precise slippage tolerances. **The Front-Running Problem:** If you set slippage tolerance to 10% but the actual price impact is only 1%, front-running bots can: 1. See your transaction with high slippage tolerance 2. Execute trades that push the price up by 9% 3. Your transaction still executes (within 10% tolerance) 4. You receive much less than expected **Solution Using DEXPool Stream:** 1. **Query Current Pool State**: Get latest `PoolPriceTable` from DEXPool stream 2. **Find Exact Slippage Needed**: Check which `SlippageBasisPoints` level matches your trade size 3. **Add Small Buffer**: Add 0.1-0.2% buffer for execution delay 4. **Set Precise Tolerance**: Use the exact slippage level instead of guessing **Example:** For a trade of 25,456 USDC (25,456,000,000 in raw units with 6 decimals): - DEXPool stream shows: At 1% slippage (100 basis points), `MaxAmountIn: 25456674083` (25,456.67 USDC) fits your trade - Set slippage tolerance to 1.2% (add 0.2% buffer) - This prevents front-runners from exploiting a large gap between tolerance and actual impact ## How Do I Interpret the PoolPriceTable Data Structure? The `PoolPriceTable` in DEXPool stream contains pre-calculated price data for swaps in both directions: **Structure:** ```json { "PoolPriceTable": { "AtoBPrices": [ { "SlippageBasisPoints": 10, "MaxAmountIn": 2557952147, "MinAmountOut": 860478002991619427, "Price": 0.0003364734002389014 } // ... more levels ], "BtoAPrices": [ { "SlippageBasisPoints": 10, "MaxAmountIn": 824929789949801466, "MinAmountOut": 2434012699, "Price": 2951.27197265625 } // ... more levels ], "AtoBPrice": 2961.585205078125, "BtoAPrice": 0.0003376569948159158 } } ``` **Field Explanations:** - **`AtoBPrices`**: Array for swapping CurrencyA → CurrencyB (e.g., USDC → WETH) - **`BtoAPrices`**: Array for swapping CurrencyB → CurrencyA (e.g., WETH → USDC) - **`SlippageBasisPoints`**: Slippage tolerance in basis points (10 = 0.1%, 100 = 1%, 1000 = 10%) - **`MaxAmountIn`**: Maximum input amount you can swap at this slippage level (in CurrencyA's raw units/smallest units, NOT decimal-adjusted) - **`MinAmountOut`**: Guaranteed minimum output amount (in CurrencyB's raw units/smallest units, NOT decimal-adjusted. Use directly as `amountOutMin` in your swap function) - **`Price`**: Average execution price at this slippage level - **`AtoBPrice`**: Current spot price (CurrencyA per CurrencyB) - **`BtoAPrice`**: Current spot price (CurrencyB per CurrencyA) **Real-World Example:** For a USDC/WETH pool where CurrencyA = USDC (6 decimals) and CurrencyB = WETH (18 decimals): ```json { "SlippageBasisPoints": 100, "MaxAmountIn": 25456674083, "MinAmountOut": 8465959707294551328, "Price": 0.00033264263765886426 } ``` This means: - At 1% slippage tolerance, you can swap up to 25,456.674083 USDC - You're guaranteed to receive at least 8.465959707294551328 WETH - The average execution price is 0.00033264 USDC per WETH (or ~3005.6 USDC per WETH) ## How Often Does DEXPool Stream Update and How Fresh is the Data? DEXPool stream updates in real-time whenever liquidity-changing events occur in the pool. The data is as fresh as the latest on-chain event. **Update Triggers:** **Uniswap V2:** - `Swap` events (every trade) - `Mint` events (liquidity added) - `Burn` events (liquidity removed) **Uniswap V3:** - `Swap` events - `Mint` events (liquidity positions added) - `Burn` events (liquidity positions removed) **Uniswap V4:** - `Swap` events - `ModifyLiquidity` events **Data Freshness:** Each event triggers a new DEXPool record with: - Updated `Liquidity.AmountCurrencyA` and `AmountCurrencyB` - Recalculated `PoolPriceTable` with new slippage data - Latest `AtoBPrice` and `BtoAPrice` spot prices This ensures the slippage calculations reflect the current pool state, not stale data from hours ago. **Best Practice:** For time-sensitive operations, query the DEXPool stream immediately before executing your transaction to get the most current slippage data. ## Can I Use DEXPool Stream to Compare Slippage Across Multiple Pools? Yes! You can query multiple pools simultaneously and compare their `PoolPriceTable` data to find the pool with the best execution (lowest slippage) for your trade size. **Comparison Strategy:** 1. **Query Multiple Pools**: Get DEXPool stream data for all pools trading your token pair 2. **Compare MaxAmountIn**: At your desired slippage level, see which pool can handle larger trades 3. **Compare Prices**: Look at the `Price` field to see which pool offers better execution price 4. **Check Liquidity**: Higher `Liquidity.AmountCurrencyA` and `AmountCurrencyB` usually means lower slippage **Example Comparison:** Pool A (Uniswap V3): ```json { "AtoBPrices": [ { "SlippageBasisPoints": 100, "MaxAmountIn": 25456674083, "MinAmountOut": 8465959707294551328, "Price": 0.00033264263765886426 } ] } ``` Pool B (Uniswap V2): ```json { "AtoBPrices": [ { "SlippageBasisPoints": 100, "MaxAmountIn": 15000000000, "MinAmountOut": 5000000000000000000, "Price": 0.0003333333333333333 } ] } ``` For a swap of 20,000 USDC (20,000,000,000 in raw units with 6 decimals): - Pool A: Can handle it (MaxAmountIn: 25456674083 raw units = 25,456.67 USDC > 20,000 USDC), better price (0.00033264) - Pool B: Cannot handle it (MaxAmountIn: 15000000000 raw units = 15,000 USDC < 20,000 USDC) at 1% slippage Pool A is better for this trade size. ## How Do I Handle Different Token Decimals When Using MinAmountOut? The `MinAmountOut` value in DEXPool stream is in the token's smallest unit (raw amount), so you need to account for token decimals when using it in your smart contract. **Understanding Decimals:** - USDC: 6 decimals (1 USDC = 1,000,000 raw units) - WETH: 18 decimals (1 WETH = 1,000,000,000,000,000,000 raw units) **Example from Data:** ```json { "Pool": { "CurrencyA": { "Symbol": "USDC", "Decimals": 6 }, "CurrencyB": { "Symbol": "WETH", "Decimals": 18 } }, "PoolPriceTable": { "AtoBPrices": [ { "MinAmountOut": 8465959707294551328 } ] } } ``` The `MinAmountOut: 8465959707294551328` is in WETH's raw units (18 decimals). To convert to human-readable: `8465959707294551328 / 10^18 = 8.465959707294551328 WETH` **In Your Smart Contract:** The `amountOutMin` parameter expects the raw amount (smallest units), so you can use `MinAmountOut` directly: ```solidity // MinAmountOut from DEXPool stream is already in correct format uint256 amountOutMin = 8465959707294551328; // Direct use, no conversion needed IUniswapV2Router02(router).swapExactTokensForTokens( amountIn, amountOutMin, // Use MinAmountOut value directly path, to, deadline ); ``` The DEXPool stream data includes `CurrencyA.Decimals` and `CurrencyB.Decimals` fields so you can verify the decimal places if needed. ## What If My Trade Size Exceeds All MaxAmountIn Values in the Price Table? If your trade size is larger than the highest `MaxAmountIn` value (even at 10% slippage), you have several options: **Option 1: Split the Trade** Break your large trade into smaller chunks that fit within the `MaxAmountIn` limits. Execute multiple swaps sequentially. **Option 2: Accept Higher Slippage (Not Recommended)** You could set slippage tolerance higher than 10%, but this exposes you to significant price impact and front-running risks. **Option 3: Wait for Better Liquidity** Monitor the DEXPool stream - when liquidity increases (higher `Liquidity.AmountCurrencyA`/`AmountCurrencyB`), the `MaxAmountIn` values will increase, allowing larger trades. **Option 4: Use Multiple Pools/Routes** Route your trade through multiple pools or use aggregation protocols that split trades across pools automatically. **Using DEXPool Stream to Monitor:** Subscribe to DEXPool stream updates for your target pool. When you see `Liquidity.AmountCurrencyA` and `AmountCurrencyB` increase, check if the new `MaxAmountIn` values can accommodate your trade size. ## Additional Resources - [DEXPools Cube Documentation](/docs/cubes/evm-dexpool/) - Complete guide to DEXPool data structure and concepts - [Bitquery IDE](https://ide.bitquery.io/) - Query DEXPool data in real-time - [Kafka Data Samples](https://github.com/bitquery/kafka-data-sample/blob/main/evm/eth_dexpools.json) - Sample DEXPool stream data structure - [EVM DEXPool Stream Documentation](/docs/cubes/evm-dexpool/) - Understanding when DEXPool records are emitted --- ## Solana Arbitrage Dashboard Project URL: https://docs.bitquery.io/docs/usecases/solana-arbitrage-dashboard/ Build Solana Arbitrage Dashboard Project: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Solana Arbitrage Dashboard Project The following tutorial is a step by step guide to build a Dashboard that displays arbitrage opportunities for WSOL/USDC pair on Solana. This is how it will look The app has the following features: 1. It displays a table with the DEX Name, Pair exchange rate on the DEX and DEX Address once the data is fetched. 2. It handles errors during the data fetching process. 3. It has a Execute Button which doesn't implement anything yet, but any PR regarding the same is welcome. The app uses the following libraries and APIs: 1. Next: An optimized version of React for building user interfaces with server-side rendering. 2. Axios: A promise-based HTTP client for the browser and Node.js. 3. Bitquery GraphQL API: A service that provides access to blockchain data through a GraphQL interface. To use this code, you need to have the following dependencies installed in your project: 1. react 2. react-dom 3. axios 4. tailwind-css ## GraphQL query The following query will be used to fetch the required data for the project. ```graphql subscription { Solana { DEXTrades( where: { Trade: { Buy: { Currency: { MintAddress: { is: "So11111111111111111111111111111111111111112" } } } Sell: { Currency: { MintAddress: { is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } } } } ) { Trade { Dex { ProgramAddress ProtocolName ProtocolFamily } Buy { PriceInUSD Account { Address } } } Block { Time } } } } ``` ## Create App Create an empty next app with the ``` npx create-next-app arbitrage-dashboard ``` command, then select the recommended options and clear the defaults in page.js file. ## Data Component Add a new file named `data.js` in the app folder and follow the below steps. ### Import Statements ```js ``` ### Functional Component ```js const getData = async () => { let data = JSON.stringify({ query: 'subscription {\n Solana {\n DEXTrades(\n where: {Trade: {Buy: {Currency: {MintAddress: {is: "So11111111111111111111111111111111111111112"}}}, Sell: {Currency: {MintAddress: {is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}}}}}\n ) {\n Trade {\n Dex {\n ProgramAddress\n ProtocolName\n ProtocolFamily\n }\n Buy {\n PriceInUSD\n Account {\n Address\n }\n }\n }\n Block {\n Time\n }\n }\n }\n}\n', variables: "{}", }); let config = { method: "post", maxBodyLength: Infinity, url: "https://streaming.bitquery.io/graphql", headers: { "Content-Type": "application/json", Authorization: process.env.NEXT_PUBLIC_BITQUERY_TOKEN, }, data: data, }; let response = await axios.request(config); // console.log(JSON.stringify(response.data.data.Solana.DEXTrades)) return response.data.data.Solana.DEXTrades; }; export default getData; ``` ## Page Component All the code blocks given below will be added to the `page.js` file in the app folder. ### Import Statements ```js "use client"; ``` Note that the `"use client"` on the top is essential in order to use `useState` and `useEffect` functionalities. ### Home Component - `const Home = () => {...};`: All the code blocks below will be added in place of dots. - `export default Home;`: Export statement for Home Component. ### State Management with `useState` Hook - `const [trades, setTrades] = useState([]);`: Declares a state variable `trades` that is initially an empty array. Also defines a setter function `setTrades` to update the state value of `trades`. - `const [currentPage, setCurrentPage] = useState(0);`: Declares a state variable `currentPage` that is initially `0`. Also defines a setter function `setCurrentPage` to update the state value of `currentPage`. - `const itemsPerPage = 10;`: Sets the number of opportunities displayed on one page as `10`. ### `useEffect` Hook - `useEffect(() => {...}, []);`: Defines a `useEffect` hook that makes an HTTP POST request to the Bitquery API to retrieve data for the arbitrage opportunities on Solana chain. ### `Pagination` Methods - `const handleNextPage = () => {...};`: Defines a method `handleNextPage` to display the next `10` arbitrage opportunities. - `const handlePrevPage = () => {...};`: Defines a method `handlePrevPage` to display the previous `10` arbitrage opportunities. ### Component Render ```js return (

Arbitrage Opportunities for WSOL/USDT

{currentTrades.map((trade, index) => ( ))}
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}
); ``` ### CSS Styling The app uses Tailwind CSS for styling which is much more effecient and easy to use. ## Video Tutorial for the Project --- ## Solana Perps Trader Cookbook — Copy Trading, PnL & Signals URL: https://docs.bitquery.io/docs/perpetuals/solana/perps-trader-cookbook/ Ready-to-run Solana perps queries: copy-trade a wallet, rank traders by PnL and win rate, unrealized positions, whale fills, OHLC, open interest, order flow. # Solana Perps Trader Cookbook Ready-to-run recipes for the questions traders, copy-traders and strategy builders actually ask, built on the [Perp DEX cubes](/docs/perpetuals/). Every query here was validated against the live endpoint; swap the example wallet/market for your own. Field semantics live on the [Phoenix Perpetuals API](/docs/perpetuals/solana/phoenix-perpetuals-api) page. Two rules apply to almost every recipe: - **`TraderIsAmm: false`** — the venue's AMM backstop trades every market; leave it in and it tops every leaderboard. - **Latest-state snapshots** use `limitBy` + `orderBy: {descending: Block_Time}` — the newest row per key *is* the current state. Don't pre-filter `Closed: false` or `Size: {ne: 0}`: that skips past closing rows and resurrects stale positions. Take the latest row, then drop flats (`Size == 0`) client-side. ## Copy trading ### Follow a trader's every fill, live You can run the wallet-fills version [in the Bitquery IDE](https://ide.bitquery.io/sol_perps_filled_orders_by_signer). Stream each execution of a wallet you follow — the signal feed a copy-trading bot subscribes to, including the position each fill produced: ```graphql subscription { Solana { PerpetualFills( where: { Fill: { Trader: { is: "DUGirckBgoaW3zoEPhTVVo68pZpXrTKuJrsLBLWcZQo2" } Liquidation: false } } ) { Block { Time } Fill { Asset { Symbol } Side ExecutionPrice Amount { Filled Quote } Position { Size EntryPrice } } } } } ``` `Position { Size, EntryPrice }` after each fill tells you their resulting exposure — you see reduces and flips, not just entries. Note that `Trader` is the venue's position account (a PDA), which you learn from any of their fills or positions. ### A trader's current open book Latest state per market for one wallet — what they hold right now: ```graphql query { Solana { PerpetualPositions( limitBy: { by: Position_Asset_Id, count: 1 } orderBy: { descending: Block_Time } limit: { count: 100 } where: { Position: { Trader: { is: "DUGirckBgoaW3zoEPhTVVo68pZpXrTKuJrsLBLWcZQo2" } } } ) { Block { Time } Position { Asset { Symbol } Position { EntryPrice Size } MarkPrice } } } } ``` Rows with `Size: 0` are markets they've fully closed — drop them and the rest is the live book, with entry prices. ### Who is worth copying — the report card You can run this query [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-trader-pnl). Realized PnL, close count, win rate and liquidation count per trader, in one aggregation: ```graphql query { Solana { PerpetualPositions( limit: { count: 20 } orderBy: { descendingByField: "realized" } where: { Position: { TraderIsAmm: false, Closed: true } } ) { Position { Trader } realized: sum(of: Position_RealizedPnl) closes: count wins: count(if: { Position: { RealizedPnl: { gt: 0 } } }) losses: count(if: { Position: { RealizedPnl: { lt: 0 } } }) liquidated: count(if: { Position: { Liquidation: true } }) } } } ``` Win rate is `wins / closes`; a high `realized` with `liquidated > 0` tells you how they treat risk. Add a `Block: { Time: { since: … } }` filter to score a recent window instead of all time. ## Positions & PnL ### Top unrealized positions and traders You can run this query [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-top-positions). Unrealized PnL is `(mark − entry) × size` over each trader's latest open position. One request returns both the position snapshot and fresh marks: ```graphql query { Solana { openPositions: PerpetualPositions( limitBy: { by: [Position_Trader, Position_Asset_Id], count: 1 } orderBy: { descending: Block_Time } limit: { count: 3000 } where: { Position: { TraderIsAmm: false } } ) { Block { Time } Position { Trader Asset { Id Symbol } Position { EntryPrice Size } MarkPrice } } marks: PerpetualPrices( limitBy: { by: Price_Asset_Id, count: 1 } orderBy: { descending: Block_Time } limit: { count: 200 } ) { Price { Asset { Id Symbol } Mark } } } } ``` Then a few lines client-side: ```python marks = {m["Price"]["Asset"]["Id"]: m["Price"]["Mark"] for m in d["marks"]} open_pos = [] for r in d["openPositions"]: p = r["Position"]; size = p["Position"]["Size"] if size == 0: continue # flat = closed mark = marks.get(p["Asset"]["Id"]) or p["MarkPrice"] upnl = (mark - p["Position"]["EntryPrice"]) * size # signed Size handles shorts open_pos.append((p["Trader"], p["Asset"]["Symbol"], size, upnl)) top_positions = sorted(open_pos, key=lambda x: x[3], reverse=True) ``` Sum per `Trader` for a whale-exposure leaderboard. Prefer the `marks` alias over the position row's own `MarkPrice` — the latter is denormalized and can be `0`. ### Funding a trader has paid or received Funding settlements are their own rows — `Funding` non-zero, size unchanged: ```graphql query { Solana { PerpetualPositions( limit: { count: 100 } orderBy: { descending: Block_Time } where: { Position: { Trader: { is: "DUGirckBgoaW3zoEPhTVVo68pZpXrTKuJrsLBLWcZQo2" } Funding: { ne: 0 } } } ) { Block { Time } Position { Asset { Symbol } Funding } } } } ``` Positive = received, negative = paid. Replace the field list with `total: sum(of: Position_Funding)` for the net carry cost of holding their positions. ## Market signals ### Whale fills You can run the query version [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-whale-trades). Every fill above a notional threshold — as history or a live tape: ```graphql subscription { Solana { PerpetualFills(where: { Fill: { Amount: { Quote: { gt: 5000 } } } }) { Block { Time } Fill { Asset { Symbol } Side ExecutionPrice Amount { Filled Quote } Trader Liquidation } } } } ``` As a `query`, add `orderBy: { descending: Block_Time }` and a `limit` for the recent whale prints. ### OHLC candles from the mark price You can run this query [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-ohlc-candles). Strategy builders and backtesters: bucket `PerpetualPrices` into intervals and take argMin/argMax aggregates — ```graphql query { Solana { PerpetualPrices( where: { Price: { Asset: { Symbol: { is: "BTC" } } } } orderBy: { ascendingByField: "Block_Time" } limit: { count: 96 } ) { Block { Time(interval: { in: minutes, count: 15 }) } Price { open: Mark(minimum: Block_Time) high: Mark(maximum: Price_Mark) low: Mark(minimum: Price_Mark) close: Mark(maximum: Block_Time) } } } } ``` `Mark(minimum: Block_Time)` reads "the Mark at the earliest time in the bucket" — open; `Mark(maximum: Price_Mark)` is the bucket's high. Price rows are emitted on trading activity, so an interval with no trades produces no candle (no zero-filled bars). ### Open interest, basis and fee revenue over time You can run this query [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-open-interest). One query per market gives an OI series, the perp-vs-spot basis, and — because `TakerFees`/`MakerFees` are cumulative counters — per-bucket fee revenue as end-minus-start: ```graphql query { Solana { PerpetualMarketSummaries( where: { MarketSummary: { Asset: { Symbol: { is: "SOL" } } } } orderBy: { ascendingByField: "Block_Time" } limit: { count: 168 } ) { Block { Time(interval: { in: hours, count: 1 }) } MarketSummary { oi: OpenInterest(maximum: Block_Time) mark: Mark(maximum: Block_Time) spot: SpotIndex(maximum: Block_Time) takerFeesEnd: TakerFees(maximum: Block_Time) takerFeesStart: TakerFees(minimum: Block_Time) } } } } ``` Basis = `mark − spot`; hourly taker fees = `takerFeesEnd − takerFeesStart`. Rising OI with a widening basis is the classic crowded-longs signal. ### Order-flow pressure — taker buys vs sells You can run this query [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-taker-buy-sell-pressure). Conditional sums split taker volume by side per bucket: ```graphql query { Solana { PerpetualFills( where: { Fill: { Asset: { Symbol: { is: "SOL" } } } } orderBy: { ascendingByField: "Block_Time" } limit: { count: 168 } ) { Block { Time(interval: { in: hours, count: 1 }) } buyVol: sum(of: Fill_Amount_Quote, if: { Fill: { Side: { is: "bid" } } }) sellVol: sum(of: Fill_Amount_Quote, if: { Fill: { Side: { is: "ask" } } }) trades: count } } } ``` `(buyVol − sellVol) / (buyVol + sellVol)` is a ready order-flow-imbalance series. ## Risk ### Biggest liquidations You can run this query [in the Bitquery IDE](https://ide.bitquery.io/solana-perps-liquidations). Rank forced closes by what they took: ```graphql query { Solana { PerpetualPositions( limit: { count: 20 } orderBy: { descendingByField: "lost" } where: { Position: { Type: { is: "Liquidation" } } } ) { Position { Trader Asset { Symbol } } lost: sum(of: Position_LiquidatedQuote) events: count } } } ``` For the live feed version and the multi-row anatomy of a liquidation, see the [liquidation section](/docs/perpetuals/solana/phoenix-perpetuals-api#positions-pnl--liquidations--perpetualpositions) of the Phoenix page. --- Every `query` above becomes a live stream by switching to `subscription` and removing `limit`/`orderBy`/`limitBy` — except the snapshot and interval recipes, which are inherently query-shaped. Run them over Kafka instead with the [`solana.perpetual.proto` topic](/docs/streams/protobuf/chains/Solana-perpetual-protobuf) when you need the full firehose. --- ## Solana Sniper Bot URL: https://docs.bitquery.io/docs/usecases/solana-sniper-bot/ Build Solana Sniper Bot: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. See examples in the Bitquery IDE. # Tutorial for Building a Solana Sniper Bot Using Bitquery Real-Time Solana Subscriptions and Jupiter Swap API This tutorial will guide you through building a Solana sniper bot using Bitquery for real-time Solana subscriptions and the Jupiter Swap API for executing swaps. By the end of this guide, you'll have a bot that listens for specific on-chain instructions and performs token swaps based on the detected instructions. > Note: This material is for educational and informational purposes only and is not intended as investment advice. The content reflects the author's personal research and understanding. While specific investments and strategies are mentioned, no endorsement or association with these entities is implied. Readers should conduct their own research and consult with qualified professionals before making any investment decisions. Bitquery is not liable for any losses or damages resulting from the application of this information. ## Tutorial Video ## Tutorial ### Prerequisites Before you begin, ensure you have the following: 1. **Node.js** and **npm** installed on your system (follow instructions [here](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)). 2. **Bitquery Free Developer Account** with OAuth token (follow instructions [here](/docs/authorization/how-to-generate/)). 3. **Solana Wallet** with some SOL for transaction fees. ### Step 1: Setting Up the Environment 1. **Initialize a new Node.js project:** ```bash mkdir solana-sniper-bot cd solana-sniper-bot npm init -y ``` 2. **Install the necessary dependencies:** ```bash npm install @solana/web3.js cross-fetch lodash @project-serum/anchor bs58 ``` ### Step 2: Creating the Bot 1. **Create a new file `index.js`:** ```bash touch index.js ``` 2. **Add the necessary imports:** ```javascript const { Connection, PublicKey, VersionedTransaction, Keypair, } = require("@solana/web3.js"); const fetch = require("cross-fetch"); const lodash = require("lodash"); const { Wallet } = require("@project-serum/anchor"); const bs58 = require("bs58"); ``` 3. **Define the Solana Instructions GraphQL query for Bitquery:** This query fetches new liquidity pools created on the Solana Raydium DEX along with token information. For detailed information on the Raydium API, check examples [here](/docs/blockchain/Solana/Solana-Raydium-DEX-API/) ```javascript const gql = (strings, ...values) => strings.reduce((final, str, i) => final + str + (values[i] || ""), ""); const query = gql` { Solana { Instructions( where: { Transaction: { Result: { Success: true } } Instruction: { Program: { Method: { is: "initializeUserWithNonce" } Address: { is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8" } } } } limit: { count: 1 } orderBy: { ascending: Block_Date } ) { Instruction { Accounts { Address } } } } } `; ``` Note: For real-time tracking of new tokens, use the subscription query below: ```javascript subscription { Solana { Instructions( where: {Transaction: {Result: {Success: true}}, Instruction: {Program: {Method: {is: "initializeUserWithNonce"}, Address: {is: "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"}}}} ) { Instruction { Accounts { Address } } } } } ``` 4. **Set up the Solana connection and wallet:** ```javascript const connection = new Connection("https://api.mainnet-beta.solana.com"); const walletPublicKey = new PublicKey("YOUR_PUBLIC_KEY"); const secretKeyUint8Array = new Uint8Array([/* YOUR_SECRET_KEY_ARRAY */]); const wallet = new Wallet(Keypair.fromSecretKey(secretKeyUint8Array)); ``` 5. **Define a function to fetch data from Bitquery:** ```javascript async function fetchGraphQL(query) { const response = await fetch("https://streaming.bitquery.io/graphql", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer YOUR_BITQUERY_OAUTH_TOKEN", }, body: JSON.stringify({ query }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } ``` 6. **Fetch pool addresses from Bitquery:** ```javascript async function getPoolAddresses() { try { const data = await fetchGraphQL(query); const instructions = lodash.get(data, "data.Solana.Instructions", []); return instructions.map(({ Instruction: { Accounts } }) => ({ poolAddress: Accounts.length > 4 ? Accounts[4].Address : undefined, tokenA: Accounts.length > 8 ? Accounts[8].Address : undefined, tokenB: Accounts.length > 9 ? Accounts[9].Address : undefined, }))[0]; } catch (error) { console.error("Error fetching data:", error); return { poolAddress: "", tokenA: "", tokenB: "" }; } } ``` 7. **Execute a token swap using Jupiter API:** This code has been derived from sample mentioned in the official docs [here](https://dev.jup.ag/docs/swap/). We add checks for "TOKEN_NOT_TRADABLE" and "COULD_NOT_FIND_ANY_ROUTE" to accommodate delays in the Jupiter Swap API. ```javascript async function swapTokens(tokenA, tokenB) { try { const quoteUrl = `https://quote-api.jup.ag/v6/quote?inputMint=${tokenB}&outputMint=${tokenA}&amount=10000&slippageBps=150`; console.log("quote url ", quoteUrl); const quoteResponse = await fetch(quoteUrl); const quoteData = await quoteResponse.json(); if ( quoteData["errorCode"] != "TOKEN_NOT_TRADABLE" && quoteData["errorCode"] != "COULD_NOT_FIND_ANY_ROUTE" ) { const swapTransactionResponse = await fetch( "https://quote-api.jup.ag/v6/swap", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ quoteResponse: quoteData, userPublicKey: wallet.publicKey.toString(), wrapAndUnwrapSol: true, }), } ); const { swapTransaction } = await swapTransactionResponse.json(); const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); console.log("swapTransactionBuf ", swapTransactionBuf); const transaction = VersionedTransaction.deserialize(swapTransactionBuf); transaction.sign([wallet.payer]); const rawTransaction = transaction.serialize(); const txid = await connection.sendRawTransaction(rawTransaction, { skipPreflight: false, maxRetries: 4, preflightCommitment: "confirmed", commitment: "confirmed", }); const confirmation = await connection.confirmTransaction( txid, "confirmed" ); console.log( `Transaction confirmed: ${confirmation.value.err ? "Error" : "Success"}` ); console.log(`Transaction successful: https://solscan.io/tx/${txid}`); } } catch (error) { console.error("Error during token swap:", error); } } ``` 8. **Define the main function to coordinate the steps:** ```javascript async function main() { const { tokenA, tokenB } = await getPoolAddresses(); await swapTokens(tokenA, tokenB); } main(); ``` ### Step 3: Running the Bot 1. **Replace placeholders:** - Replace `YOUR_PUBLIC_KEY` with your actual Solana wallet public key. - Replace `YOUR_SECRET_KEY_ARRAY` with your wallet's secret key array. - Replace `YOUR_BITQUERY_OAUTH_TOKEN` with your actual Bitquery OAuth token. 2. **Run the bot:** ```bash node index.js ``` ### Conclusion Congratulations! You've successfully built a Solana sniper bot similar to [SOL Sniper Bot](https://solsniperbot.net/) using Bitquery for real-time Solana subscriptions and the Jupiter Swap API for executing swaps. This bot actively listens for specific on-chain instructions and performs swaps based on the detected activities. Remember to monitor and manage your bot diligently, especially when operating on the mainnet with real funds. ### Disclaimer This tutorial is for educational purposes only. Trading cryptocurrencies involves significant risk, and you should consult with a professional advisor before making any investment decisions. --- ## Solana Wash Trading Dashboard URL: https://docs.bitquery.io/docs/usecases/wash-trading-detector/app/ Build Solana Wash Trading Dashboard: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Solana Wash Trading Dashboard This script runs an interactive Streamlit web application that: - Loads fresh Solana DEX trade data from Bitquery. - Applies a trained XGBoost model to detect wash trading. - Calculates various risk metrics. - Displays dynamic, real-time visualizations. ## Code Braakdown ### Imports Refer to the [file structure](../overview/#project-structure) to make sure that the imports are correct. ```py from sklearn.preprocessing import LabelEncoder from get_data import get_trades ``` ### Load Model and Features The code given below loads your pre-trained XGBoost model and the feature list used during training to ensure correct column alignment. ```py model = pickle.load(open("xgb_wash_model.pkl", "rb")) with open("model_features.json", "r") as f: feature_cols = json.load(f) ``` ### Fetch and Prepare Data This code fetch the latest Solana DEX trades using Bitquery API, flattens nested JSON into a usable DataFrame, encode categorical features and align the data with training features. ```py trade_data = get_trades() df = pd.json_normalize(trade_data) X = df.copy() for col in X.select_dtypes(include="object").columns: X[col] = LabelEncoder().fit_transform(X[col].astype(str)) for col in feature_cols: if col not in X.columns: X[col] = 0 X = X[feature_cols] ``` ### Run Predictions and Compute Risk Score This code adds a new column prediction indicating suspected wash trades and computes an overall risk score (0–100). ```py df["prediction"] = model.predict(X) wash_count = int(df["prediction"].sum()) total_trades = len(df) risk_score = min(100, int((wash_count / total_trades) * 100)) if total_trades else 0 ``` ### Compute Key Metrics The code given below returns: 1. Cleaned timestamp for resampling. 2. Buy volume in USD 3. Suspicious volume based on model prediction 4. Top 10 wallets' volume share 5. Most active wallet’s trade contribution 6. % of volume flagged as suspicious ```py df["timestamp"] = pd.to_datetime(df["Block.Time"], errors="coerce") df["volume"] = pd.to_numeric(df["Trade.Buy.AmountInUSD"], errors="coerce").fillna(0) df["suspiciousVolume"] = df.apply( lambda row: row["volume"] if row["prediction"] == 1 else 0, axis=1 ) top_wallets = df.groupby("Trade.Buy.Account.Address")["volume"].sum().sort_values(ascending=False) total_volume = top_wallets.sum() top_10_volume = top_wallets.head(10).sum() volume_concentration = round(100 * top_10_volume / total_volume, 2) top_wallet_trade_count = df["Trade.Buy.Account.Address"].value_counts().iloc[0] wallet_contribution = round(100 * top_wallet_trade_count / len(df), 2) wash_volume_pct = round(100 * df["suspiciousVolume"].sum() / df["volume"].sum(), 2) ``` ### Streamlit App Setup This code initializes the Streamlit app with a title and a button. ```py st.set_page_config(page_title="Solana Wash Trade Risk Dashboard", layout="wide") st.title("🚨 Solana Wash Trading Risk Assessment") st.button("🔍 Analyze Wash Trading Risk for Solana") ``` ### Show Risk Metrics The below code renders `Risk Score` and `Trading Metrics` on the Streamlit app. ```py st.subheader("Risk Assessment") st.markdown(f"**Risk Score: `{risk_score}`** – {'High' if risk_score > 70 else 'Medium' if risk_score > 40 else 'Low'} Risk") st.progress(risk_score / 100) col1, col2, col3, col4 = st.columns(4) col1.metric("Volume Concentration", f"{volume_concentration}%") col2.metric("Most Active Trader", f"{wallet_contribution}%") col3.metric("Suspicious Trades", f"{wash_count}") col4.metric("Wash Trade Volume", f"{wash_volume_pct}%") ``` ### Plot Volume Chart (Total vs Suspicious) The below code renders a bar chart of total trade volume v/s the suspicious volume for a timeframe of `1s`. ```py df.set_index("timestamp", inplace=True) agg = df.resample("1s").agg({ "volume": "sum", "suspiciousVolume": "sum" }).fillna(0).reset_index() fig = go.Figure() # Blue bars = total volume fig.add_trace(go.Bar( x=agg["timestamp"], y=agg["volume"], name="Total Volume", marker_color="blue", width=500 )) # Red bars = suspicious volume fig.add_trace(go.Bar( x=agg["timestamp"], y=agg["suspiciousVolume"], name="Suspicious Volume", marker_color="red", width=500 )) fig.update_layout( barmode="group", title="Grouped Bar Chart: Volume vs Suspicious Volume", xaxis_title="Time(UTC+00:00:00)", yaxis_title="Volume (USD)", xaxis_tickformat="%H:%M:%S", legend=dict(x=0.8, y=1.1), bargap=0.4, bargroupgap=0.1, height=400, ) st.plotly_chart(fig, use_container_width=True) ``` ### Raw Data Toggle The below code allows to optionally show the full processed dataset (including predictions and timestamps). ```py if st.checkbox("Show Raw Trade Data"): st.dataframe(df) ``` ## Deployment via Streamlit You can deploy this dashboard to Streamlit Cloud in a few easy steps just as this [Live Demo](https://washtrade.streamlit.app). ### Add `requirements.txt` In the project repository add the following `requirements.txt` file. ```txt streamlit pandas scikit-learn xgboost matplotlib plotly ``` ### Deploy on Streamlit Cloud 1. Go to [Streamlit Cloud](https://streamlit.io/cloud). 2. Click "New App". 3. Connect your GitHub repo. 4. Set app.py as the entry point. 5. Add your [access token](https://account.bitquery.io/user/api_v2/access_tokens) in Secrets. 6. Click Deploy. ## Final Product The final product looks like the image given below. ![Solana Wash Trading Dashboard](/img/wash_dashboard.png) --- ## Solana Wash Trading Detector URL: https://docs.bitquery.io/docs/usecases/wash-trading-detector/overview/ Detect Solana wash trades using Bitquery DEX data, rule-based labels, XGBoost scoring, and a Streamlit investigation UI. # Solana Wash Trades Detector This project fetches on-chain Solana DEX trades data from Bitquery, labels potential wash-trades based on a set of defined rules, trains an XGBoost model, and deploys an interactive Streamlit dashboard to visualize suspicious trades and compute risk metrics. ## How It Works 1. `Data Collection`: Live Solana DEX trades are fetched from Bitquery using GraphQL APIs. 2. `Trade Labeling`: A set of defined rules (e.g., self-trading, repeated trading loops, price spoofing) is applied to label trades as potentially suspicious (wash trades). 3. `Model Training`: A machine learning model (XGBoost) is trained on the labeled data to learn patterns indicative of wash trading. 4. `Visualization`: A clean and interactive dashboard built with Streamlit displays incoming trades, volume metrics, risk concentration, and predicted suspicious activity in real time. ## Project Structure ```bash ├── get_data.py # Fetches trades from Bitquery ├── label.py # Rule-based labeling functions ├── main.py # Preprocesses data, trains XGBoost, saves model + features ├── app.py # Streamlit app for live inference & visualization ├── requirements.txt # List of pip packages required for the project ├── model_features.json # Saved feature list used during training ├── xgb_wash_model.pkl # Trained XGBoost model └── .streamlit/secrets.toml # To store secret keys such as Bitquery Access Token ``` You can checkout the entire codebase of the project [here](https://github.com/Kshitij0O7/wash-trading-detector). --- ## Stablecoin Balance API URL: https://docs.bitquery.io/docs/stablecoin-APIs/stablecoin-balance-api/ Stablecoin Balance API using Bitquery stablecoin APIs for prices, transfers, payments, and cross-chain monitoring workflows. # Stablecoin Balance API The Stablecoin API by Bitquery provides you the comprehensive set of APIs which can provide you realtime transfers, realtime trades, realtime price, holder distribution of stablecoins across chains with a single API call. We are going to particularly deep-dive into how to get Stablecoin Balance data in this section. ## Stablecoin Balance of an Address ### Solana Below stream will give you balance of `9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM` for `FDUSD` on Solana. Test the query [here](https://ide.bitquery.io/FDUSD-balance-of-an-address). ```graphql query MyQuery { Solana { BalanceUpdates( where: {BalanceUpdate: {Account: {Owner: {is: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"}}, Currency: {MintAddress: {is: "9zNQRsGLjNKwCUU5Gq5LR8beUCPzQMVMqKAi3SSZh54u"}}}} orderBy: {descendingByField: "BalanceUpdate_Balance_maximum"} ) { BalanceUpdate { Balance: PostBalance(maximum: Block_Slot) Currency { Name Symbol } } } } } ``` ### Ethereum [Run Query](https://ide.bitquery.io/ethereum-stablecoin-balances-address) ```graphql query { EVM(network: eth, dataset: combined) { Balances( where: { Balance: { Address: { is: "0xcf1DC766Fc2c62bef0b67A8De666c8e67aCf35f6" } } } ) { Currency { Symbol SmartContract } Balance { Amount(selectWhere: { gt: "0" }) AmountInUSD } } } } ``` ### Tron Below query will give you **USDT** balance for address `TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ` on Tron. ```graphql query MyQuery { Tron(dataset: combined) { Balances( where: {Balance: {Address: {is: "TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ"}}, Currency: {SmartContract: {is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}}} ) { Currency { Name } Balance { Address Amount(selectWhere: {gt: "0"}) } } } } ``` More examples on balances on EVM chains can be found [here](/docs/blockchain/Ethereum/balances/balance-api/) Token holder API examples can be found [here](/docs/blockchain/Ethereum/token-holders/token-holder-api/) ## Get Top 100 Holders of a Particular Stablecoin [This query](https://ide.bitquery.io/top-100-holders-of-USDC-token-on-Solana) returns the top 100 holders of a particular Stablecoin. ```graphql query MyQuery { Solana { BalanceUpdates( orderBy: {descendingByField: "BalanceUpdate_Holding_maximum"} where: {BalanceUpdate: {Currency: {MintAddress: {is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}}, }, Transaction: {Result: {Success: true}}} ) { BalanceUpdate { Currency { Name MintAddress Symbol } Account { Address } Holding: PostBalance(maximum: Block_Slot selectWhere:{gt:"0"}) } } } } ``` --- ## Stablecoin Payments API URL: https://docs.bitquery.io/docs/stablecoin-APIs/stablecoin-payments-api/ Stablecoin Payments API using Bitquery stablecoin APIs for prices, transfers, payments, and cross-chain monitoring workflows. # Stablecoin Payments API Bitquery's Stablecoin Payments API exposes real-time and historical payment flows for USDT, USDC, FDUSD, EURC, DAI, TUSD, USDS, USD1, USDD and other major stablecoins across Solana, Ethereum, Tron, BSC, Base, Arbitrum, Polygon and more — through a single GraphQL endpoint. This page focuses on payment-oriented use cases (incoming/outgoing transfers, merchant detection, address monitoring, AML/compliance). For related capabilities, see the [Stablecoin Transfers API](/docs/stablecoin-APIs/stablecoin-transfers-api), [Stablecoin Balance API](/docs/stablecoin-APIs/stablecoin-balance-api), [Stablecoin Trades API](/docs/stablecoin-APIs/stablecoin-trades-api), [Stablecoin Reserve API](/docs/stablecoin-APIs/stablecoin-reserve-api) and [Stablecoin Price API](/docs/stablecoin-APIs/stablecoin-price-api). All examples below are runnable in the [Bitquery IDE](https://ide.bitquery.io). Streams are delivered over WebSocket / Kafka subscriptions; historical queries use the same schema over HTTP. For pre-built stablecoin dashboards (top tokens, flows, market activity), browse the [DEXrabbit Stablecoins dashboard](https://dexrabbit.bitquery.io/categories/stablecoins). ## Stablecoin Payment API Examples ### 1. Listening to All USDT and USDC Payments on Solana This GraphQL stream provides **live USDT and USDC stablecoin transfers** on Solana. 🔗 [Stream Example](https://ide.bitquery.io/USDT-and-USDC-token-Transfers-stream-on-solana) 🔗 [API Example](https://ide.bitquery.io/USDT-and-USDC-token-Transfers-api-on-solana) ```graphql subscription { Solana { Transfers( where: { Transfer: { Currency: { MintAddress: { in: [ "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] } } } } ) { Transfer { Amount AmountInUSD Sender { Address Owner } Receiver { Address Owner } Currency { Symbol Name MintAddress } } Instruction { Program { Method } } Block { Time Height Slot } Transaction { Signature Signer Fee FeeInUSD FeePayer } } } } ``` ### 2. Multi-Chain Stablecoin Payments Listen to **stablecoin payments across all major blockchains**. The Mempool option lets you detect a payment *before* it is confirmed — useful for instant merchant UX. | Blockchain | API | Stream | Mempool | |------------|-----|--------|---------| | **Tron** | [API](https://ide.bitquery.io/Latest-Tron-USDT-Transfers) | [Stream](https://ide.bitquery.io/Latest-Tron-USDT-Transfers-stream) | [Mempool](https://ide.bitquery.io/Latest-Tron-USDT-Transfers-stream-in-Mempool) | | **Ethereum** | [API](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-api-on-ethereum) | [Stream](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-stream-on-ethereum) | [Mempool](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-stream-on-ethereum-in-Mempool) | | **BSC** | [API](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-api-on-BSC_2) | [Stream](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-Stream-on-BSC) | [Mempool](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-Stream-on-BSC-on-Mempool) | | **Base** | [API](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-api-on-base) | [Stream](https://ide.bitquery.io/Latest-USDTUSDC-Transfer-stream-on-base) | Not Available | Mempool feeds are produced by simulating transactions on top of the current block. ### 3. Listening to USDT Payments on Tron (stream) This subscription streams **USDT** (`TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`) transfers on **Tron** — the highest-volume stablecoin payments network globally. 🔗 [Stream Example](https://ide.bitquery.io/Listening-to-All-USDT-and-USDC-Payments-on-Solana---stream) ```graphql subscription { Tron(network: tron) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}}}} ) { Transaction { Hash } Transfer { Amount Currency { Name Symbol } Receiver Sender } } } } ``` ### 4. Stablecoin Payments For a Specific Address (Tron) Listen to **USDT** sent or received by address `TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ`. This is the canonical "merchant/treasury wallet monitor" pattern — fan-out one subscription per wallet and route hits to your payments backend. 🔗 [Stream Example](https://ide.bitquery.io/Listening-to-stablecoin-Transfers-for-Specific-Addresse-on-tron) ```graphql subscription { Tron { Transfers( where: {any: [{Transfer: {Sender: {is: "TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ"}}}, {Transfer: {Receiver: {is: "TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ"}}}], Transfer: {Currency: {SmartContract: {is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}}}} ) { Transaction { Hash } Transfer { Amount AmountInUSD Sender Receiver } } } } ``` For the equivalent on Solana, see the [Stablecoin Transfers API → Stablecoin received and sent by an address](/docs/stablecoin-APIs/stablecoin-transfers-api#stablecoin-recieved-and-sent-by-an-address). ### 5. Stablecoin Payments For a Specific Address (BSC) Filter incoming **USDT and USDC** payments to a single BSC receiver across both stablecoin contracts in one subscription. 🔗 [Example Query](https://ide.bitquery.io/USDT-and-USDC-transfers-on-bnb-chain) ```graphql subscription { EVM(network: bsc) { Transfers( where: { Transfer: { Receiver: { in: ["0x443fa7bbf35c09ee0ebb5e15f1ea3f0704b89d04"] } Currency: { SmartContract: { in: [ "0x55d398326f99059ff775485246999027b3197955" "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d" ] } } } } ) { Block { Time Number } Transfer { Sender Receiver Amount AmountInUSD Currency { Name Symbol SmartContract } } Transaction { Hash From To } } } } ``` ### 6. Stablecoin Payments Stream on Ethereum A single-contract USDC payments stream on Ethereum mainnet. Swap the `SmartContract` for `0xdAC17F958D2ee523a2206206994597C13D831ec7` to get USDT, or extend with an `in: [...]` list to multiplex stablecoins. 🔗 [Stream Example](https://ide.bitquery.io/Stablecoin-Realtime-Payments-Stream-on-Eth-Mainnet) ```graphql subscription { EVM(network: eth) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}}}} ) { Transaction { Hash } Transfer { Amount Currency { Name Symbol } Receiver Sender Type } } } } ``` For the full transfer schema (mint/burn detection, `Type`, dataset selection), see the [Stablecoin Transfers API](/docs/stablecoin-APIs/stablecoin-transfers-api). ## Compliance & Risk Checks For **AML/KYC and risk monitoring**, you can analyze a payment counterparty's lifecycle on the asset — first and last activity dates, total change count, and current balance — in a single query. The `Holders` cube does not break activity down by direction. For separate inbound and outbound counts or amounts, aggregate the [Transfers cube](/docs/cubes/transfers-cube) by `Transfer.Sender` / `Transfer.Receiver` instead. 🔗 [Example API](https://ide.bitquery.io/stats-for-an-adddress) ```graphql { EVM(dataset: archive, network: eth) { Holders( date: "2025-08-25" where: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } }, Holder: { Address: { is: "0x72187db55473b693ded367983212fe2db3768829" } } } ) { Holder { Address } Balance { UpdateCount FirstChangeTime LastChangeTime Amount } } } } ``` To verify the live balance of a payer/payee on any chain, see the [Stablecoin Balance API](/docs/stablecoin-APIs/stablecoin-balance-api) (Solana, Ethereum and Tron examples included). ### Confirming the Sender's Solvency Before Settlement Before releasing goods or off-ramping a stablecoin payment, you may want to verify the sender actually held the asset at the moment of transfer. Use the [Stablecoin Balance API](/docs/stablecoin-APIs/stablecoin-balance-api#stablecoin-balance-of-an-address) to fetch a wallet's current stablecoin balance per chain. For supply-side checks (e.g. confirming an issuer mint backed an inbound payment), use the [Stablecoin Reserve API](/docs/stablecoin-APIs/stablecoin-reserve-api). ## Payment Analytics ### 1. First-Time Stablecoin Receivers Identify addresses receiving stablecoins for the **first time** on a given date — useful for new-customer attribution and onboarding analytics. 🔗 [Query Example](https://ide.bitquery.io/first-time-UDST-received-by-addresses-on-a-given-date) ```graphql { EVM(dataset: archive, network: eth) { Holders( limit: { count: 1000 } date: "2025-08-25" where: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } }, BalanceUpdate: { FirstDate: { is: "2025-08-25" } } } ) { Holder { Address } } } } ``` ### 2. Last-Time Stablecoin Receivers Identify addresses that **last received USDT** on a specific date — useful for churn detection and dormant-wallet analysis. 🔗 [Query Example](https://ide.bitquery.io/Address-which-received-USDT-on-a-given-date-last-time) ```graphql { EVM(dataset: archive, network: eth) { Holders( orderBy: { descending: Balance_Amount } limit: { count: 1000 } date: "2025-08-25" where: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } }, BalanceUpdate: { LastDate: { is: "2021-01-01" } } } ) { Holder { Address } Balance { FirstChangeTime LastChangeTime Amount } } } } ``` ### 3. Top Stablecoin Holders Find **top holders of USDT on Ethereum**, with current balance and activity history (first change, last change, total change count). For directional inflow/outflow totals, aggregate the [Transfers cube](/docs/cubes/transfers-cube) by sender and receiver. The [Stablecoin Balance API](/docs/stablecoin-APIs/stablecoin-balance-api#get-top-100-holders-of-a-particular-stablecoin) covers the same pattern on Solana. 🔗 [Query Example](https://ide.bitquery.io/Top-holders-of-usdt-on-specific-date) ```graphql { EVM(dataset: archive, network: eth) { Holders( orderBy: [{ descending: Balance_Amount }] limit: { count: 100 } date: "2025-08-25", where: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } } ) { Holder { Address } Balance { UpdateCount FirstChangeTime LastChangeTime Amount } } } } ``` ## Pricing Stablecoin Payments Accurately For high-value or cross-border payments, even a 20–50 bps deviation from the peg materially changes settlement value. Use the [Stablecoin Price API](/docs/stablecoin-APIs/stablecoin-price-api) to: - [Stream the latest price](/docs/stablecoin-APIs/stablecoin-price-api#stream-latest-stablecoin-price) of USDT/USDC/DAI/USDS at 1-second intervals. - [Compare prices across chains](/docs/stablecoin-APIs/stablecoin-price-api#check-arbitrage-of-a-stablecoin-across-chains) to mark the payment to its true on-chain rate. - [Monitor peg health per DEX/market](/docs/stablecoin-APIs/stablecoin-price-api#stablecoin-peg-health-api) to catch a depeg before it hits your treasury. For depeg detection at the trade level (alerting when any trade prints outside `0.95–1.05`), see the [Stablecoin Trades API → Depeg tracking streams](/docs/stablecoin-APIs/stablecoin-trades-api#stablecoin-depeg-tracking-stream-for-evm). ## Asset-Specific Guides For deep-dives on a single asset (price + payments + trades + reserves + balances curated together), see [USDT API](/docs/stablecoin-APIs/usdt-api). ## Why Use Bitquery for Stablecoin Payments? - **Real-time streams (WebSocket & Kafka)** for instant detection. - **Mempool visibility** on Tron, Ethereum and BSC for pre-confirmation UX. - **Webhook support** to integrate with your payment systems. - **Compliance-focused APIs** for AML/KYC and risk analysis. - **Multi-chain coverage** across Solana, Ethereum, Tron, BSC, Base, Arbitrum, Polygon and more. Bitquery enables **faster, compliant, and scalable stablecoin payment solutions** for businesses, fintechs, and governments. --- ## Stablecoin Price API URL: https://docs.bitquery.io/docs/stablecoin-APIs/stablecoin-price-api/ Stablecoin Price API using Bitquery stablecoin APIs for prices, transfers, payments, and cross-chain monitoring workflows. # Stablecoin Price API While stablecoins are designed to maintain a stable value (typically pegged to USD, EUR, or other assets), they can deviate slightly from their target price (e.g., $0.998 or $1.02 for USD-pegged stablecoins). For developers, traders, and businesses processing large volumes of stablecoin payments, even these small deviations can translate to significant financial impact at scale. Bitquery's Stablecoin API provides comprehensive real-time data including transfers, trades, prices, and holder distribution across multiple blockchain networks—all accessible through a single API call. Track minute price changes, fetch blended average prices, and identify arbitrage opportunities across different platforms with precision and ease. This is built on the extensive [Crypto Price APIs](/docs/trading/crypto-price-api/) Need help implementing stablecoin price APIs? Contact our support team or join our community discussion on [@Bloxy_info](https://t.me/bloxy_info). ## Stream Latest Stablecoin Price [Run Stream](https://ide.bitquery.io/stablecoin-1-second-price-stream) ```graphql subscription { Trading { Tokens( where: {Interval: {Time: {Duration: {eq: 1}}}, Currency: {Id: {in: ["usdt", "usdc", "tusd", "usdd", "usds", "usd₮0", "usd1", "dai"]}}} ) { Token { Address Id IsNative Name Network Name Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } } } } ``` ## 5 Minute Price Change Stablecoin API [Run Query](https://ide.bitquery.io/5-minute-price-change-stablecoin-API) ```graphql { Trading { Tokens( limit: {count: 10} limitBy: {count: 1, by: Token_Id} orderBy: [{descending: Block_Time}, {descendingByField: "change"}] where: {Currency: {Id: {in: ["usdt", "usdc", "tusd", "usdd", "usds", "usd₮0", "usd1", "dai"]}}, 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 Quote Usd } 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") } } } ``` ## Check Arbitrage of a Stablecoin Across Chains This query compares USDT prices across different blockchain networks in real-time. It fetches the latest price data for USDT from different networks, showing you where the same stablecoin trades at different prices. **What this tells traders:** - **Price discrepancies**: See which networks have USDT trading above or below $1.00 - **Volume context**: Understand trading volume on each network to assess liquidity - **Timing**: Get 1-second interval data to catch fleeting arbitrage windows - **Network comparison**: Compare prices across Ethereum, BSC, Polygon, and other major networks [Run Query](https://ide.bitquery.io/usdt-latest-price-arbitrage) ```graphql { Trading { Tokens( where: {Interval: {Time: {Duration: {eq: 1}}}, Currency: {Id: {is: "usdt"}}} limit: {count: 100} limitBy: {by: Token_Network, count: 1} ) { Token { Address Id IsNative Name Network Name Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } } } } ``` ## Stablecoin Peg Health API Monitor stablecoin peg health by getting the **latest price per DEX/market** for a stablecoin. This helps identify which exchanges or markets have the stablecoin trading closest to its peg (e.g., $1.00 for USD-pegged stablecoins) and detect de-pegging events across different trading venues. Browse live multi-chain stablecoin DEX prices on [DEXrabbit's Stablecoins category](https://dexrabbit.bitquery.io/categories/stablecoins). ### Solana Peg Health Get the latest price of a stablecoin across all Solana DEXs/markets. Returns one row per market with the most recent trade price. [Run in Bitquery IDE](https://ide.bitquery.io/Latest-Price-of-a-Token-on-all-exchanges_1) ```graphql { Solana { DEXTradeByTokens( orderBy: { descending: Block_Time } limitBy: { count: 1, by: Trade_Market_MarketAddress } where: { Trade: { Currency: { MintAddress: { is: "CZzgUBvxaMLwMhVSLgqJn3npmxoTo6nzMNQPAnwtHF3s" } } } } ) { Block { Time } Transaction { Signature } Trade { Amount AmountInUSD Price PriceInUSD Currency { Name MintAddress Symbol } Market { MarketAddress } Dex { ProtocolName ProtocolFamily } Side { Type Currency { Name MintAddress Symbol } AmountInUSD Amount } } } } } ``` ### Ethereum / BSC Peg Health Get the latest price of a stablecoin across all EVM DEXs. Returns one row per DEX protocol with the most recent trade price. Works on Ethereum, BSC, and other EVM chains. [Run in Bitquery IDE](https://ide.bitquery.io/evm-peg-health_1) ```graphql { EVM(network: eth) { DEXTradeByTokens( orderBy: { descending: Block_Time } limitBy: { count: 1, by: Trade_Dex_SmartContract } where: { Trade: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } } } ) { Block { Time } Transaction { Hash } Trade { Amount AmountInUSD Price PriceInUSD Currency { Name SmartContract Symbol } Dex { ProtocolName ProtocolFamily SmartContract } Side { Type Currency { Name SmartContract Symbol } AmountInUSD Amount } } } } } ``` ### Tron Peg Health Get the latest price of a stablecoin across all Tron DEXs. Returns one row per DEX protocol with the most recent trade price. [Run in Bitquery IDE](https://ide.bitquery.io/peg-health-tron) ```graphql { Tron { DEXTradeByTokens( orderBy: { descending: Block_Time } limitBy: { count: 1, by: Trade_Dex_SmartContract } where: { Trade: { Currency: { SmartContract: { is: "TXL6rJbvmjD46zeN1JssfgxvSo99qC8MRT" } } } } ) { Block { Time } Transaction { Hash } Trade { Amount AmountInUSD Price PriceInUSD Currency { Name SmartContract Symbol } Dex { ProtocolName ProtocolFamily SmartContract } Side { Type Currency { Name SmartContract Symbol } AmountInUSD Amount } } } } } ``` --- ## Stablecoin Reserve API URL: https://docs.bitquery.io/docs/stablecoin-APIs/stablecoin-reserve-api/ Stablecoin Reserve API using Bitquery stablecoin APIs for prices, transfers, payments, and cross-chain monitoring workflows. # Stablecoin Reserve API The Stablecoin API by Bitquery provides you the comprehensive set of APIs which can provide you realtime reserves data, realtime transfers, realtime trades, realtime price, holder distribution of stablecoins across chains with a single API call. We are going to particularly deep-dive into how to get Stablecoin Transfers data in this section. ## Ethereum ### USDT Stablecoin reserves on Ethereum Below API query will give you realtime reserves data of `USDT` on Ethereum. Test the API [here](https://ide.bitquery.io/USDT-Stablecoin-reserves-on-Ethereum). ```graphql query MyQuery { EVM(network: eth, dataset: combined) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "0xdAC17F958D2ee523a2206206994597C13D831ec7"}}, Success: true}} ) { minted: sum( of: Transfer_Amount if: {Transfer: {Sender: {is: "0x0000000000000000000000000000000000000000"}}} ) burned: sum( of: Transfer_Amount if: {Transfer: {Receiver: {is: "0x0000000000000000000000000000000000000000"}}} ) } } } ``` ## Solana ### USDC Stablecoin reserves on Solana Below API query will give you realtime reserves data of `USDC` on Solana. Test the API [here](https://ide.bitquery.io/USDC-Stablecoin-reserves-on-Solana). ```graphql { Solana { TokenSupplyUpdates( limit:{count:1} orderBy:{descending:Block_Time} where: {TokenSupplyUpdate: {Currency: {MintAddress: {is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}}}} ) { TokenSupplyUpdate { Amount Currency { MintAddress Name } PreBalance PostBalance } } } } ``` ## Tron ### USDT Stablecoin reserves on Tron Below API query will give you realtime reserves data of `USDT` on Tron. Test the API [here](https://ide.bitquery.io/USDT-Stablecoin-reserves-on-Tron). ```graphql query MyQuery { Tron(dataset: combined) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}}, Success: true}} ) { minted: sum( of: Transfer_Amount if: {Transfer: {Sender: {is: "THPvaUhoh2Qn2y9THCZML3H815hhFhn5YC"}}} ) burned: sum( of: Transfer_Amount if: {Transfer: {Receiver: {is: "THPvaUhoh2Qn2y9THCZML3H815hhFhn5YC"}}} ) } } } ``` --- ## Stablecoin Trades API: USDT and USDC DEX Trades, Depeg Checks and Wallet Flows URL: https://docs.bitquery.io/docs/stablecoin-APIs/stablecoin-trades-api/ Stablecoin DEX trades on Ethereum, Solana and Tron via Bitquery GraphQL: live USDT streams, off-peg trade checks with a USD floor, a wallet's stablecoin flows. # Stablecoin Trades API: USDT and USDC DEX Trades, Depeg Checks and Wallet Flows Stablecoins are the quote side of most DEX volume, so a stablecoin trade feed is close to a feed of everything. This page filters Bitquery's trade cubes down to one stablecoin at a time on Ethereum, Solana and Tron: the live trades, the trades that printed away from one dollar, and what one wallet did with its USDC. Every example runs in the [IDE](https://ide.bitquery.io) on a free account. Solana queries go to the `eap` endpoint, Ethereum and Tron to `graphql`. The [Trading cube](/docs/trading/crypto-trades-api/trades-api) gives the same trades across ten chains with USD on every row for the last month, which is the first section; the chain cubes below add history and per-pool detail. ## Live USDT trades across all chains One subscription, every chain, filtered on the symbol. ```graphql subscription { Trading { Trades(where: { Pair: { Currency: { Symbol: { is: "USDT" } } } }) { Block { Time } Price PriceInUsd AmountsInUsd { Base Quote } Pair { Currency { Id Symbol } Token { Symbol Network } QuoteToken { Symbol } Market { Network Protocol } } } } } ``` For ready-made stablecoin dashboards, browse [DEXrabbit's Stablecoins category](https://dexrabbit.bitquery.io/categories/stablecoins). ## Ethereum ### USDT trades on Ethereum, live `DEXTradeByTokens` gives one row per trade with USDT under `Trade.Currency`, whichever side it was on. Saved stream [here](https://ide.bitquery.io/Stablecoin-trades-for-etheruem). ```graphql subscription { EVM(network: eth) { DEXTradeByTokens( where: { Trade: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } } } ) { Block { Time } Transaction { Hash From } Trade { Amount AmountInUSD Price PriceInUSD Side { Type Amount AmountInUSD Currency { Symbol SmartContract } } Dex { ProtocolName SmartContract } } } } } ``` ### USDT trades away from the peg on Ethereum {#stablecoin-depeg-tracking-stream-for-evm} A depeg check has to ignore dust and thin pairs, or it fires all day. This query keeps trades against WETH or USDC worth over a thousand dollars whose USDT price printed more than half a percent from one dollar, over the last day. Two ranges on one field need `any`; a single `PriceInUSD: {lt, gt}` block is an AND and matches nothing. Widen the band or shorten the window for alerts, or run it as a subscription without the `Block` filter. Saved query [here](https://ide.bitquery.io/Stablecoin-Depeg-tracking-Stream-for-evm). ```graphql { EVM(network: eth) { DEXTradeByTokens( where: { Trade: { Currency: { SmartContract: { is: "0xdac17f958d2ee523a2206206994597c13d831ec7" } } Side: { AmountInUSD: { gt: "1000" } Currency: { SmartContract: { in: [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" ] } } } } any: [ { Trade: { PriceInUSD: { lt: 0.995 } } } { Trade: { PriceInUSD: { gt: 1.005 } } } ] Block: { Time: { since_relative: { hours_ago: 24 } } } } limit: { count: 50 } orderBy: { descending: Block_Time } ) { Block { Time } Transaction { Hash } Trade { PriceInUSD Amount AmountInUSD Side { Currency { Symbol } AmountInUSD } Dex { ProtocolName SmartContract } } } } } ``` ## Solana ### USDT trades on Solana, live Both sides of each swap, with the accounts and the market. Saved stream [here](https://ide.bitquery.io/solana-trades-subscription_10_1). ```graphql subscription { Solana { DEXTrades( where: { any: [ { Trade: { Buy: { Currency: { MintAddress: { is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" } } } } } { Trade: { Sell: { Currency: { MintAddress: { is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" } } } } } ] Transaction: { Result: { Success: true } } } ) { Block { Time Slot } Transaction { Signature Signer } Trade { Dex { ProgramAddress ProtocolName } Market { MarketAddress } Buy { Amount AmountInUSD Price PriceInUSD Currency { Symbol MintAddress } Account { Address } } Sell { Amount AmountInUSD Currency { Symbol MintAddress } Account { Address } } } } } } ``` ### One wallet's stablecoin flows on Solana Filter the transaction signer and the stablecoin mints. The example wallet swaps USDC around the clock at the time of writing; the totals split what it bought from what it sold. Saved query [here](https://ide.bitquery.io/real-time-stablecoin-portfolio_2). ```graphql { Solana { DEXTradeByTokens( where: { Transaction: { Signer: { is: "3i51cKbLbaKAqvRJdCUaq9hsnvf9kqCfMujNgFj7nRKt" } Result: { Success: true } } Trade: { Currency: { MintAddress: { in: [ "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" ] } } } Block: { Time: { since_relative: { hours_ago: 24 } } } } ) { Trade { Currency { Symbol MintAddress } } trades: count bought: sum(of: Trade_Amount, if: { Trade: { Side: { Type: { is: sell } } } }) sold: sum(of: Trade_Amount, if: { Trade: { Side: { Type: { is: buy } } } }) } } } ``` Drop the aggregates and the `Block` filter and subscribe to the same `where` for each swap as it happens: ```graphql subscription { Solana { DEXTradeByTokens( where: { Transaction: { Signer: { is: "3i51cKbLbaKAqvRJdCUaq9hsnvf9kqCfMujNgFj7nRKt" } Result: { Success: true } } Trade: { Currency: { MintAddress: { in: [ "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" ] } } } } ) { Block { Time } Trade { Currency { Symbol } Amount AmountInUSD Side { Type Amount Currency { Symbol } } } Transaction { Signature } } } } ``` ### USDT trades away from the peg on Solana Same band and floor as the Ethereum check, against USDC or WSOL so that thin memecoin pairs do not set off the alarm. Saved query [here](https://ide.bitquery.io/stablecoin-depeg-tracking-stream-for-USDC). ```graphql { Solana { DEXTradeByTokens( where: { Trade: { Currency: { MintAddress: { is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" } } Side: { AmountInUSD: { gt: "1000" } Currency: { MintAddress: { in: [ "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" "So11111111111111111111111111111111111111112" ] } } } } any: [ { Trade: { PriceInUSD: { lt: 0.995 } } } { Trade: { PriceInUSD: { gt: 1.005 } } } ] Transaction: { Result: { Success: true } } Block: { Time: { since_relative: { hours_ago: 24 } } } } limit: { count: 50 } orderBy: { descending: Block_Time } ) { Block { Time } Transaction { Signature } Trade { PriceInUSD Amount AmountInUSD Side { Currency { Symbol } AmountInUSD } Dex { ProtocolName } Market { MarketAddress } } } } } ``` ## Tron ### USDT trades on Tron, live Most Tron DEX volume is USDT against TRX on SunSwap. Streams on Tron match currencies by symbol; use the contract address, `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t`, in queries. Saved stream [here](https://ide.bitquery.io/Stablecoin-trades-for-tron). ```graphql subscription { Tron { DEXTradeByTokens(where: { Trade: { Currency: { Symbol: { is: "USDT" } } } }) { Block { Time } Transaction { Hash } Trade { Amount AmountInUSD PriceInUSD Side { Type Amount Currency { Symbol } } Dex { ProtocolName } } } } } ``` ### USDT trades away from the peg on Tron The band check for Tron, with the USD floor and no counter-token list, since almost everything on Tron is quoted in TRX. Saved query [here](https://ide.bitquery.io/Stablecoin-Depeg-tracking-Stream-for-tron). ```graphql { Tron { DEXTradeByTokens( where: { Trade: { Currency: { SmartContract: { is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t" } } Side: { AmountInUSD: { gt: "1000" } } } any: [ { Trade: { PriceInUSD: { lt: 0.995 } } } { Trade: { PriceInUSD: { gt: 1.005 } } } ] Block: { Time: { since_relative: { hours_ago: 24 } } } } limit: { count: 50 } orderBy: { descending: Block_Time } ) { Block { Time } Transaction { Hash } Trade { PriceInUSD Amount AmountInUSD Side { Currency { Symbol } AmountInUSD } Dex { ProtocolName } } } } } ``` ## Related pages - [Crypto Trades API](/docs/trading/crypto-trades-api/trades-api) - [Ethereum DEX trades API](/docs/blockchain/Ethereum/dextrades/dex-api) - [Solana DEX trades API](/docs/blockchain/Solana/solana-dextrades) - [Tron DEX trades API](/docs/blockchain/Tron/tron-dextrades) --- ## Stablecoin Transfers API URL: https://docs.bitquery.io/docs/stablecoin-APIs/stablecoin-transfers-api/ Stablecoin Transfers API using Bitquery stablecoin APIs for prices, transfers, payments, and cross-chain monitoring workflows. # Stablecoin Transfers API The Stablecoin API by Bitquery provides you the comprehensive set of APIs which can provide you realtime transfers, realtime trades, realtime price, holder distribution of stablecoins across chains with a single API call. We are going to particularly deep-dive into how to get Stablecoin Transfers data in this section. ## Ethereum ### Stablecoin Realtime Transfers Stream on Eth Mainnet Below stream will give you realtime transfers of `USDC` on Eth mainnet. Test the stream [here](https://ide.bitquery.io/Stablecoin-Realtime-Payments-Stream-on-Eth-Mainnet). ```graphql subscription { EVM(network: eth) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}}}} ) { Transaction { Hash } Transfer { Amount Currency { Name Symbol } Receiver Sender Type } } } } ``` ### Stablecoin mint/burn on Ethereum Below API query will give you mint/burn data of `USDT` on Ethereum. Test the API [here](https://ide.bitquery.io/USDT-Stablecoin-reserves-on-Ethereum). ```graphql query MyQuery { EVM(network: eth, dataset: combined) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "0xdAC17F958D2ee523a2206206994597C13D831ec7"}}, Success: true}} ) { minted: sum( of: Transfer_Amount if: {Transfer: {Sender: {is: "0x0000000000000000000000000000000000000000"}}} ) burned: sum( of: Transfer_Amount if: {Transfer: {Receiver: {is: "0x0000000000000000000000000000000000000000"}}} ) } } } ``` ## Solana ### Stablecoin Realtime Transfers Stream Below stream will give you realtime transfers of `USDC` on Solana. Test the stream [here](https://ide.bitquery.io/stablecoin-transfers-websocket). ```graphql subscription { Solana { Transfers( where: {Transfer: {Currency: {MintAddress: {is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}}}} ) { Transfer { Currency { MintAddress Symbol Name Fungible Native } Receiver { Address } Sender { Address } Amount AmountInUSD } Transaction{ Signature } } } } ``` ### Stablecoin recieved and sent by an address Below query will give you `EURC` transfers from/to `cHxJ2uC6vgcCfoFSfupkfCWbKHAkekrGfG39DXRamXT` on Solana. Test the query [here](https://ide.bitquery.io/stablecoin-Transfers-fromto-an-address). ```graphql { Solana { Transfers( limit: {count: 10} orderBy: {descending: Block_Time} where: {any: [{Transfer: {Sender: {Address: {is: "cHxJ2uC6vgcCfoFSfupkfCWbKHAkekrGfG39DXRamXT"}}}}, {Transfer: {Receiver: {Address: {is: "cHxJ2uC6vgcCfoFSfupkfCWbKHAkekrGfG39DXRamXT"}}}}], Transfer: {Currency: {MintAddress: {is: "HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr"}}}} ) { Transaction { Signature } Transfer { Amount AmountInUSD Sender { Address } Receiver { Address } } } } } ``` ## Tron ### Stablecoin Realtime Transfers Stream on Tron Below stream will give you realtime transfers of `USDT` on Tron. Test the stream [here](https://ide.bitquery.io/Stablecoin-Realtime-Transfers-Stream-on-tron). ```graphql subscription { Tron(network: tron) { Transfers( where: {Transfer: {Currency: {SmartContract: {is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}}}} ) { Transaction { Hash } Transfer { Amount Currency { Name Symbol } Receiver Sender } } } } ``` ### Stablecoin recieved and sent by an address Below query will give you `USDT` transfers from/to `TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ` on Tron. Test the query [here](https://ide.bitquery.io/Stablecoin-recieved-and-sent-by-an-address). ```graphql { Tron(dataset: combined) { Transfers( limit: {count: 10} orderBy: {descending: Block_Time} where: {any: [{Transfer: {Sender: {is: "TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ"}}}, {Transfer: {Receiver: {is: "TUTQj7VJ1QjR3t2GJByvrP25yZNFcj38VJ"}}}], Transfer: {Currency: {SmartContract: {is: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}}}} ) { Transaction { Hash } Transfer { Amount AmountInUSD Sender Receiver } } } } ``` --- ## Supply Fields Reference URL: https://docs.bitquery.io/docs/trading/crypto-price-api/supply-fields/ Supply Fields Reference via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live streams. # Supply fields reference The **`Supply`** object appears on the **Tokens**, **Currency**, and **Pairs** cubes with the same field names. Values describe the **underlying asset** (**currency**), not a specific pool or DEX pair. - On **Tokens** and **Pairs**, **price** and **volume** on the row are **chain- or pair-specific**; **`Supply`** is still **currency-level** (aggregated for the asset across the platform’s reference data). - On **Currency**, price and volume are already aggregated across chains and representations; **`Supply`** uses the same currency-level semantics. For how **prices** and **volumes** are computed from trades, see the [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm). ## Field definitions Amounts below are **human-readable token units** (not raw integer amounts with decimals applied on-chain), unless your client or tooling documents otherwise. ### `CirculatingSupply` An estimate of the number of tokens **available to the market** (public float), from CryptoRank. - Often **unavailable** for assets CryptoRank does not cover well (e.g. many meme tokens). - May be **null** when a reliable circulating figure is not available. **Circulating supply** and **max supply** coverage is available for assets that trade on **CEXs**; many **meme tokens** and thinly covered assets may **not** have these supply figures, so those fields are often missing for those names. ### `TotalSupply` The **total issued supply** of the asset as represented in reference data taken from on-chain **total supply**. May differ from **circulating** when a large portion is not treated as circulating. ### `MaxSupply` The **maximum supply cap** for the asset. For assets **without** a fixed cap, or when CryptoRank has no figure, this field is typically 0. Often **unavailable** for the same asset classes as **circulating supply** (e.g. many meme tokens). ### `MarketCap` **Market capitalization in USD**, usually **price × circulating supply** when **circulating supply** is known (using the currency-level figure and the Price Index USD price for that row). When **circulating supply** is **unknown**, **`MarketCap` equals `FullyDilutedValuationUsd`** (see below). ### `FullyDilutedValuationUsd` **Fully diluted valuation in USD**, computed as **`TotalSupply` x the currency-level USD price** (`Price.Average.Estimate`). When **circulating supply** is **unknown**, we set **`FullyDilutedValuationUsd` to the same value as `MarketCap`** (so both fields carry the same fallback valuation). :::note `MaxSupply` is not used in either valuation `MaxSupply` is reference data only — it is **not** the multiplier for FDV, and it is `0` for the large majority of currencies, which is why it cannot be. Both valuations are driven by `TotalSupply` / `CirculatingSupply` and `Price.Average.Estimate`. ::: ## Supply is a currency-level figure, repeated on every row This is the most important thing to know about the whole block. `Supply` is **one currency-level reference figure**. It is byte-identical on every token representation of that currency, on every pool of that token, and on the `Currencies` row — it is not computed per row. Consequences: - On a **`Pairs`** row, `MarketCap` and `FullyDilutedValuationUsd` are that currency-level valuation copied down verbatim. They are **not** derived from the row's own `Price.Ohlc.Close`, so a pool trading away from the blended price still reports the same market cap as every other pool of that token. - On **`Currencies`**, the figure is neither summed across chains nor taken from a top-ranked chain. It is the same single number. :::danger Summing MarketCap across rows double-counts Because the same valuation is repeated on every token representation and every pool, a `sum(of: Supply_MarketCap)` across a currency's rows multiplies it by the row count. Take `maximum:` or read one row instead. ::: :::caution Two ways a Supply figure can look wrong - The reference figure **refreshes in steps**, so rows a few seconds apart can carry slightly different values. Do not treat small differences as a data error. - `Trading.Tokens` can return a duplicate row for the same token and bar in which `Token.Symbol` is empty and **every `Supply` field is `0`** — a metadata-arrival split on freshly launched tokens. Filter those out before valuing anything. See [tokens.md](/docs/trading/crypto-price-api/tokens#identity-fields). ::: ## Bitcoin (`bid:bitcoin`) and on-chain supply For **Bitcoin**, the **`Supply`** block on **`Tokens`** rows describes **on-chain supply of wrapped and bridged BTC** (for example WBTC on Ethereum), **not** native Bitcoin UTXO supply on the Bitcoin network. **Native BTC does not exist as a single token contract** on EVM (and similar) chains the way ERC-20s do, so any **`TotalSupply`**-style figure in this API is tied to **those on-chain representations**. That is why you **do not** see **~21 million** in **`TotalSupply`** for a Bitcoin currency query: **21M** is the **network-level** cap for Bitcoin itself, while **`Supply` here** reflects **how much wrapped BTC is tracked on the chain(s)** backing that token row. ```graphql {Trading { Tokens( where: { Currency: { Id: { is: "bid:bitcoin" } } Interval: { Time: { Duration: { eq: 1 } } } } 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 Quote Usd } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } Supply { TotalSupply MarketCap FullyDilutedValuationUsd } } } } ``` --- ## Telegram Bot for Live Blockchain Data URL: https://docs.bitquery.io/docs/usecases/telegram-bot/ Build a Telegram bot that streams Bitquery WebSocket GraphQL subscriptions and sends real-time blockchain alerts to chats. # Get Blockchain Data on Telegram Chat in Real-Time With this code, your Telegram bot will respond to the `/start` command by initiating a WebSocket connection to Bitquery and sending blockchain data updates to the Telegram chat. This is how it will look: You can find the complete code [here](https://github.com/divyasshree-BQ/telegram-bot/blob/main/getTransfer_WS.py) ## Step-by-Step Tutorial 1. Install the required Python libraries: ```bash pip install asyncio json websockets tracemalloc telegram-bot-api ``` **Step 2: Define Your Token and Keys** You need to provide your Telegram bot token from the BotFather. For this step you need to create a new Telegram bot. Check official tutorial [here](https://core.telegram.org/bots/tutorial) ![bot](/img/ApplicationExamples/telegram_bot.png) Replace `'tokenn'` with your actual bot token. BOT_TOKEN = 'YOUR_BOT_TOKEN' You also need to get your API OAuth Token from Bitquery, you can get it for free by creating an account [here](https://account.bitquery.io/user/account) **Step 3: Define a Function to Send Messages** A function named `send_message` is defined. It takes an `update` object and a `message` string as arguments and sends the message to the Telegram chat. def send_message(update: Update, message: str): update.message.reply_text(message) **Step 4: Define Functions for Handling Long Messages** Since the response received from the Bitquery API is much longer than allowed limits( 4000 characters), we will write a function that splits the text and sends it to the chat. ```python def split_text(text, max_length): return [text[i:i + max_length] for i in range(0, len(text), max_length)] def send_long_message(update: Update, long_message, max_message_length=4000): message_parts = split_text(long_message, max_message_length) for part in message_parts: send_message(update, part) ``` Two functions, `split_text` and `send_long_message`, are defined to handle long messages. `split_text` breaks a long message into smaller parts, and `send_long_message` sends a long message as multiple smaller messages to avoid Telegram's message length limits. **Step 5: Define WebSocket Code** The `my_component` function is an asynchronous function that handles the WebSocket connection to Bitquery. You can read more about how to use it [here](/docs/subscriptions/websockets/) The below code sends a GraphQL subscription query that listens to server for latest transfers on the Ethereum chain, i.e. it subscribes to the `EVM.Transfers` event. ```python async def my_component(update): url = 'wss://streaming.bitquery.io/graphql' message = json.dumps({ "type": "start", "id": "1", "payload": { "query": "subscription {\n EVM {\n Transfers {\n Transfer {\n Amount\n __typename\n Currency {\n __typename\n Symbol\n }\n }\n }\n }\n}", "variables": {} }, "headers": { Authorization: "Bearer your_access_token_here", } }) async def connect(update): async with websockets.connect(url, subprotocols=['graphql-ws']) as ws: await ws.send(message) while True: response = await ws.recv() response = json.loads(response) if response.get('type') == 'data': response_text = f"{response['payload']['data']['EVM']['Transfers']}" send_long_message(update, response_text) await connect(update) ``` 1. It waits for new events. 2. When it receives a new event, it sends the event data to the Telegram bot. **Step 6: Start WebSocket and Send Updates to Telegram** The `start_websocket_and_send_updates` function initiates the WebSocket connection defined in `my_component`. It also handles exceptions if the connection encounters any issues. ```python async def start_websocket_and_send_updates(update): try: await my_component(update) except Exception as e: print(str(e)) ``` **Step 7: Command Handler to Start WebSocket Connection** The `start` function is a command handler that responds to the `/start` command on Telegram. It sends a message indicating that it's starting the WebSocket connection and then calls `start_websocket_and_send_updates` to begin the WebSocket connection. ```python def start(update: Update, context: CallbackContext): update.message.reply_text("Starting WebSocket connection...") asyncio.run(start_websocket_and_send_updates(update)) ``` **Step 8: Create and Configure the Telegram Bot** In the `main` function, the Telegram bot is created and configured. It registers the `/start` command handler. It then starts the bot and waits for updates. ```python def main(): tracemalloc.start() updater = Updater(BOT_TOKEN, use_context=True) dp = updater.dispatcher dp.add_handler(CommandHandler("start", start)) updater.start_polling() updater.idle() **Step 9: Run the Bot** The script checks if it's the main module and starts the bot. if __name__ == "__main__": main() ``` --- ## Track Hyperliquid Order Flow by Wallet — Market Makers, Spoofing & Quote Behaviour URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/hyperliquid-order-flow-by-wallet/ Use Hyperliquid L3 market-by-order data to follow a single wallet's order flow: watch a market maker quote and pull, measure cancel-to-fill ratios, spot orders never intended to trade, and reconstruct full order lifecycles by trader address. # Track Hyperliquid Order Flow by Wallet On an aggregated **L2** feed you can see that 40 BTC rests at $95,000. You cannot see whether that is one order or twenty, who placed it, or whether it was pulled the instant a buyer approached. Bitquery serves Hyperliquid's book at **L3 (market-by-order)**: one event per individual order, carrying its order id (`Oid`) and the **wallet address** that placed it. Because Hyperliquid settles on a transparent L1, that attribution is public — L3 with real identity, which does not exist on centralised venues where L3 feeds are anonymised. This page shows what that unlocks. For the schema itself see the [L3 Order Book API](/docs/perpetuals/hyperliquid/hyperliquid-orders-api). :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## 1. Watch one wallet quote in real time Filter the book stream to a single address and you see that participant's quoting as it happens — every level they add, resize and pull. Run it in the IDE: [Hyperliquid Wallet Book Activity ➤](https://ide.bitquery.io/hyperliquid-wallet-book-activity) ```graphql subscription { Hyperliquid { BookUpdates( where: {BookUpdate: {Trader: {Address: {is: "0x0000000000000000000000000000000000000000"}}}} ) { Block { Time } BookUpdate { Kind Side Px Size SizeBefore Oid Market { Symbol } } } } } ``` `Kind` tells you what happened to that specific order: `new` (placed), `change` (resized) or `remove` (pulled or filled). `SizeBefore` gives you the delta on that one order rather than a net change across the level. Swap the address for a list to follow a whole desk: ```graphql where: {BookUpdate: {Trader: {Address: {in: ["0xaaa...", "0xbbb...", "0xccc..."]}}}} ``` ## 2. Reconstruct a trader's full order lifecycle Fills alone tell you what a trader *did*. Orders tell you what they *intended* — including everything they placed and then cancelled, which never appears in trade data. Run it in the IDE: [Hyperliquid Wallet Order Lifecycle ➤](https://ide.bitquery.io/hyperliquid-wallet-order-lifecycle) ```graphql query { Hyperliquid { Orders( limit: {count: 200} orderBy: {descending: Block_Time} where: {Order: {Trader: {Address: {is: "0x0000000000000000000000000000000000000000"}}}} ) { Block { Time } Order { Market { Symbol } Oid Cloid Status OrderType Tif Side LimitPx Size OrigSz IsTrigger TriggerPx ReduceOnly } } } } ``` `Oid` is the join key. The same id appears on `BookUpdate.Oid` (how the order sat in the book) and on `Trade.Execution.Oid` (how it filled), so one order can be followed end to end across all three cubes. ## 3. Cancel-to-fill ratio — the spoofing signal An order placed and pulled without ever trading is normal market making. A wallet doing it at scale, on one side, repeatedly, is a different thing. L3 is what makes the ratio measurable at all: on L2 you cannot tell a cancellation from a fill, because both simply reduce the level. Count orders by status for a market over a window: Run it in the IDE: [Hyperliquid Order Status Breakdown ➤](https://ide.bitquery.io/hyperliquid-order-status-breakdown) ```graphql query { Hyperliquid { Orders( limit: {count: 100} orderBy: {descendingByField: "placed"} where: { Order: {Market: {Symbol: {is: "BTC"}}} Block: {Time: {since_relative: {hours_ago: 24}}} } ) { Order { Trader { Address } } placed: count canceled: count(if: {Order: {Status: {is: "canceled"}}}) filled: count(if: {Order: {Status: {is: "filled"}}}) } } } ``` This returns one row per wallet with all three counts, so the cancel-to-fill ratio is `canceled / filled` directly. A wallet with a very high cancel share, concentrated on one `Side`, placing size well away from the touch and pulling it as price approaches, is worth a closer look — pull that address back through query 1 to see the behaviour tick by tick. :::caution Interpret with care A high cancel rate on its own is not evidence of manipulation. Market makers legitimately cancel the large majority of their orders as they requote. Treat this as a screen that tells you where to look, not as a conclusion. ::: ## 4. Follow the wallet off Hyperliquid This is the part no Hyperliquid-only data source can do. Once you have an address of interest, the same API covers Solana, Ethereum, Base, BNB, Tron and Bitcoin — so you can ask where that trader's funds came from before the position and where they went after it. ```graphql query { EVM(network: eth) { Transfers( limit: {count: 50} orderBy: {descending: Block_Time} where: {Transfer: {Sender: {is: "0x0000000000000000000000000000000000000000"}}} ) { Block { Time } Transfer { Amount Currency { Symbol SmartContract } Receiver } } } } ``` Liquidation on Hyperliquid, followed by a deposit somewhere else, is a single question here rather than a reconciliation job across two vendors. ## Data window The `Hyperliquid` cubes serve a **rolling ~30-day window**, which covers live monitoring and recent-history analysis. For longer studies — behavioural baselines over months, backtests — use the [Blockchain Data Lake](/docs/data-lake/) for the full archive. ## Related - [Hyperliquid L3 Order Book API](/docs/perpetuals/hyperliquid/hyperliquid-orders-api) — `Orders`, `BookUpdates`, `Twaps` schema - [Hyperliquid Trades & Candles](/docs/perpetuals/hyperliquid/hyperliquid-trades-api) — fills, PnL, OHLCV - [Hyperliquid Signed Actions](/docs/perpetuals/hyperliquid/hyperliquid-signed-actions-api) — raw L1 actions beneath the book - [Bitquery vs Hyperliquid's free data](/docs/perpetuals/hyperliquid/vs-hyperliquid-api) — what the native API and S3 archive do and don't cover --- ## Track Millions Of Solana Wallets URL: https://docs.bitquery.io/docs/usecases/track-millions-of-solana-wallets/ Build Track Millions Of Solana Wallets: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # How to efficiently track millions of Solana Wallets Balance with Bitquery Kafka Streams Accurate, real-time wallet balance data is mission-critical for crypto businesses. Whether it's exchanges and custodians monitoring incoming deposits, DeFi protocols watching collateral positions, or analytics platforms tracking whale movements—speed matters. Just a few milliseconds can mean the difference between a successful liquidation or a costly missed opportunity. But tracking wallet balances on Solana is uniquely challenging. As a high-throughput blockchain, Solana processes thousands of transactions per second across millions of wallets. This makes traditional polling-based methods both inefficient and costly. Check this [bitquery API](https://ide.bitquery.io/Transaction-in-1-hour_1) here, we are getting around 4000 TPS on Solana at the time of writing this article. ### Why polling doesn't scale on Solana: - **High volume**: You risk missing updates due to polling intervals. - **Expensive**: RPC calls scale poorly and become cost-prohibitive at millions of wallets. - **Laggy**: Polling introduces delays between actual balance changes and detection. - **Unreliable**: Under heavy load, RPC nodes can time out or fail. Traditionally, teams rely on Remote Procedure Call (RPC) polling to query wallet balances. But as your tracking scales, that model breaks down—wasting resources, increasing latency, and burning through RPC limits. For a deeper dive into this problem space, check out our post on [Scalable Balance Tracking for Custodians: A Better Alternative to Node-Based Approaches](https://www.linkedin.com/pulse/scalable-balance-tracking-custodians-better-alternative-node-based-wiyuc). In this article, we’ll walk through a practical implementation of scalable Solana wallet balance tracking using Bitquery’s Kafka Streams. We'll share code examples, design insights, and explain how Kafka helps you stream balance changes in real time—without hammering RPC nodes. If you're new to Kafka, start with [Kafka Streaming Concepts](/docs/streams/kafka-streaming-concepts/) on Bitquery to get a basic understanding before diving in. ## Who Needs Real-Time Wallet Tracking? - **Exchanges**: Detect deposits, monitor wallets, verify withdrawals - **DeFi**: Track collateral, trigger liquidations, verify transactions - **Wallets**: Show live balances, send alerts, log history - **Analytics**: Spot whale moves, analyze trends, assess risks :::note The code presented here is a proof-of-concept intended to demonstrate core concepts. Production implementations for exchanges and financial institutions will require additional engineering as briefly outlined later in this article. ::: ## How is Balance Calculated in Solana? Solana handles balances in a unique way that's important to understand for accurate tracking: ### What is PostBalance? In Solana transaction data, each account involvement includes two important balance values: - **PreBalance**: The account's balance before the transaction execution - **PostBalance**: The account's balance after the transaction execution These are in the smallest token units (lamports for SOL, where 1 SOL = 1,000,000,000 lamports). ### How We Use PostBalance We use PostBalance because it's: 1. The final account state after all operations 2. Blockchain-verified (more reliable than manual calculations) Our implementation: ```python # Extract PostBalance from Kafka stream post_balance = balance_update.PostBalance # Convert to human-readable format human_balance = raw_balance / (10 ** decimals) ``` This ensures accurate balance tracking that matches wallet and explorer displays. ## Quick Start Guide **GitHub Repository**: https://github.com/akshat-cs/solana-wallet-tracker ### Prerequisites 1. Python 2. Access to Bitquery Kafka Streams (reach out to the Bitquery team on [Telegram](https://t.me/bloxy_info) for credentials) is a completely free trial ### Installation & Setup 1. Clone the repository: ``` git clone https://github.com/akshat-cs/solana-wallet-tracker.git cd solana-wallet-tracker ``` 2. Install dependencies: ```bash pip install confluent-kafka protobuf base58 bitquery-pb2-kafka-package python-dotenv ``` 3. Configure your credentials: Set these variables in a newly created .env file with the credentials you got from BQ support TG channel. ``` # Kafka credentials KAFKA_USERNAME = KAFKA_PASSWORD = ``` 4. Run the wallet tracker: ```bash python wallet_balance_extractor.py ``` ## Understanding the Kafka Stream Data Before diving into the implementation, let's look at what the actual data from the Bitquery Kafka stream looks like. This will help you understand the rich information available to work with: ``` Full Message Details: Header: Slot: 333862663 Hash: ParentSlot: 0 Height: 0 Timestamp: 0 ParentHash: Transactions (repeated): [0]: Index: 190 Signature: 5SLabhmtAh8Bynx4Ee9aPUd8g7pWSBCMSvEoWJaKN9aGRzpxgUS83oqyvPYje4rHLBjH2h6tCttNFC6RmsMyLFMM Status: Success: False ErrorMessage: Error processing Instruction 2: custom program error: 0x1771 Header: Fee: 5625 FeePayer: CtxJeBMW3kGg3yG4MPacVxFNp76pieVrWXVe86VEcrfP RecentBlockhash: EB3zVPGyV3ia6sDVxi2MHvu2B9KVGRuzUm2biggwBnbw Signer: CtxJeBMW3kGg3yG4MPacVxFNp76pieVrWXVe86VEcrfP Transfers (repeated): [0]: InstructionIndex: 4 Amount: 1691000000 Sender: Address: AkG8tvC27HbMLunku4GLSEXbmmTwgX1nDEGoBjyHTJFJ IsSigner: False IsWritable: True Token: Mint: So11111111111111111111111111111111111111112 Owner: CtxJeBMW3kGg3yG4MPacVxFNp76pieVrWXVe86VEcrfP Decimals: 9 ProgramId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA Receiver: Address: DrVWWu8y13x5ZdSX7JTNH3XJJwptzh8xJ8nUphJZxY94 IsSigner: False IsWritable: True Token: Mint: So11111111111111111111111111111111111111112 Owner: 8wJymmvXgo7eK7kGgvwU3GaKaTN9nvZV2S36WC9gbrq4 Decimals: 9 ProgramId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA Currency: Name: Wrapped Solana Decimals: 9 Uri: Symbol: WSOL Native: False Wrapped: True Fungible: True MintAddress: So11111111111111111111111111111111111111112 TokenBalanceUpdates (repeated): [0]: PreBalance: 6012783083 PostBalance: 4321783083 AccountIndex: 5 [1]: PreBalance: 1172836026228 PostBalance: 1174527026228 AccountIndex: 7 BalanceUpdates (repeated): [0]: BalanceUpdate: PreBalance: 120566794 PostBalance: 120561169 AccountIndex: 0 Currency: Name: Solana Decimals: 9 Symbol: SOL Native: True Wrapped: False Fungible: True MintAddress: 11111111111111111111111111111111 ``` This is just a small fraction of a single message - the actual messages can be several thousand lines long, containing detailed information about all token transfers, balance updates, and transaction details in a block. ## Complete Code Walkthrough Let's examine the entire implementation in detail: ### Imports ```javascript from dotenv import load_dotenv from datetime import datetime from confluent_kafka import Consumer, KafkaError, KafkaException from google.protobuf.message import DecodeError from solana import token_block_message_pb2 ``` ### Class Structure: IndexedWalletTracker The IndexedWalletTracker class is the heart of our implementation. It manages the in-memory wallet index and processes Kafka messages to update balances. #### Initialization ```python def __init__(self): # Create output directory self.output_dir = "wallet_balances" if not os.path.exists(self.output_dir): os.makedirs(self.output_dir) # Timestamp for file naming self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # In-memory index of wallets: {address: {token: {balance, decimals, symbol, last_updated}}} self.wallet_index = {} # Token metadata cache: {token_mint: {symbol, decimals, name}} self.token_metadata = {} # Statistics tracking self.stats = { 'messages_processed': 0, 'balance_updates_received': 0, 'unique_addresses': 0, 'unique_tokens': 0, 'start_time': time.time(), 'last_export_time': time.time() } # Export settings self.export_interval = 30 # seconds between exports ``` This initializes: - A directory for storing exported data - The in-memory wallet index (nested dictionaries for fast lookups) - A token metadata cache (to avoid redundant processing) - Statistics tracking - Export interval settings #### Utility Functions ```python def convert_bytes(self, value): """Convert bytes to base58 string""" if isinstance(value, bytes): return base58.b58encode(value).decode() return str(value) ``` This simple utility converts byte values (like addresses) to Solana's base58 format. ```python def calculate_human_balance(self, raw_balance, decimals): """Calculate human-readable balance based on token decimals""" if decimals > 0: return raw_balance / (10 ** decimals) return raw_balance ``` This function converts raw token amounts (like 1000000000 lamports) to human-readable values (1 SOL) based on the token's decimal places. #### Extracting Data from Messages ```python def extract_token_metadata(self, currency): """Extract token metadata from Currency object""" token_mint = "UNKNOWN" token_symbol = "UNKNOWN" token_decimals = 0 token_name = "UNKNOWN" if hasattr(currency, 'MintAddress') and currency.MintAddress: token_mint = self.convert_bytes(currency.MintAddress) if hasattr(currency, 'Symbol') and currency.Symbol: token_symbol = currency.Symbol if hasattr(currency, 'Decimals'): token_decimals = currency.Decimals if hasattr(currency, 'Name') and currency.Name: token_name = currency.Name else: token_name = token_symbol # Update token metadata cache if token_mint != "UNKNOWN": self.token_metadata[token_mint] = { "symbol": token_symbol, "decimals": token_decimals, "name": token_name } # Update unique tokens count if len(self.token_metadata) > self.stats['unique_tokens']: self.stats['unique_tokens'] = len(self.token_metadata) return { "mint": token_mint, "symbol": token_symbol, "decimals": token_decimals, "name": token_name } ``` This function extracts token metadata (mint address, symbol, decimals, name) from the Currency objects in the Kafka messages. It also updates the token metadata cache for future reference. ```python def extract_address(self, tx, account_index): """Extract address from transaction using account index""" address = None # Try Header.Accounts if hasattr(tx, 'Header') and hasattr(tx.Header, 'Accounts'): accounts = tx.Header.Accounts if account_index < len(accounts): account = accounts[account_index] if hasattr(account, 'Address'): address = self.convert_bytes(account.Address) # Try Accounts directly if address is None and hasattr(tx, 'Accounts'): accounts = tx.Accounts if account_index < len(accounts): account = accounts[account_index] if hasattr(account, 'Address'): address = self.convert_bytes(account.Address) return address ``` This function extracts a wallet address from a transaction given an account index. It tries multiple locations because the Solana message structure can vary. #### Updating Wallet Balances ```python def update_wallet_balance(self, address, token_mint, token_info, raw_balance): """Update the wallet balance index with the latest balance""" if address == "UNKNOWN" or address is None: return False # Skip unknown addresses # Initialize address in index if needed if address not in self.wallet_index: self.wallet_index[address] = {} # Update unique addresses count self.stats['unique_addresses'] = len(self.wallet_index) # Calculate human-readable balance human_balance = self.calculate_human_balance(raw_balance, token_info['decimals']) # Update token balance for this address self.wallet_index[address][token_mint] = { 'raw_balance': raw_balance, 'human_balance': human_balance, 'symbol': token_info['symbol'], 'decimals': token_info['decimals'], 'last_updated': int(time.time()) } return True ``` This is the core function that updates our in-memory index with the latest wallet balance. It also calculates a human-readable balance value and tracks when the balance was last updated. ```python def process_balance_update(self, address, token_info, raw_balance): """Process a balance update and update the index""" if address and token_info['mint'] != "UNKNOWN": success = self.update_wallet_balance( address, token_info['mint'], token_info, raw_balance ) if success: self.stats['balance_updates_received'] += 1 return True return False ``` This is a wrapper around `update_wallet_balance` that also updates our statistics tracking. #### Exporting Balances ```python def export_balances(self, force=False): """Export current balances to file if interval has passed or forced""" current_time = time.time() elapsed = current_time - self.stats['last_export_time'] if force or elapsed >= self.export_interval: # Create export file with timestamp export_file = os.path.join(self.output_dir, f"balances_{self.timestamp}_latest.json") # Prepare export data export_data = { 'timestamp': datetime.now().isoformat(), 'stats': self.stats.copy(), 'wallets': self.wallet_index } # Add elapsed time and rate stats total_elapsed = current_time - self.stats['start_time'] export_data['stats']['elapsed_seconds'] = total_elapsed export_data['stats']['updates_per_second'] = ( self.stats['balance_updates_received'] / total_elapsed if total_elapsed > 0 else 0 ) # Write to file with open(export_file, 'w') as f: json.dump(export_data, f, indent=2) # Also create a CSV version for easy viewing csv_file = os.path.join(self.output_dir, f"balances_{self.timestamp}_latest.csv") with open(csv_file, 'w') as f: # Write header f.write("Address,Token,Symbol,HumanReadableBalance,RawBalance,Decimals,LastUpdated\n") # Write each wallet balance for address, tokens in self.wallet_index.items(): for token_mint, data in tokens.items(): f.write(f"{address},{token_mint},{data['symbol']}," + f"{data['human_balance']},{data['raw_balance']}," + f"{data['decimals']},{data['last_updated']}\n") # Update last export time self.stats['last_export_time'] = current_time print(f"Exported {len(self.wallet_index)} wallets with " + f"{self.stats['balance_updates_received']} balance records to {export_file}") print(f"CSV export available at: {csv_file}") return True return False ``` This function exports our in-memory wallet balances to both JSON and CSV files. It only exports if the export interval has passed or if forced (e.g., at shutdown). #### Processing Messages ```python def process_message(self, token_block): """Process a token block message""" self.stats['messages_processed'] += 1 # Process balance updates at block level if hasattr(token_block, 'BalanceUpdates'): for update in token_block.BalanceUpdates: if hasattr(update, 'BalanceUpdate') and hasattr(update, 'Currency'): balance_update = update.BalanceUpdate currency = update.Currency # Extract token metadata token_info = self.extract_token_metadata(currency) # Extract balance and account if hasattr(balance_update, 'PostBalance') and hasattr(balance_update, 'AccountIndex'): post_balance = balance_update.PostBalance account_index = balance_update.AccountIndex # For block level updates, we often can't resolve the address # But we can try to look in transactions address = None if hasattr(token_block, 'Transactions'): for tx in token_block.Transactions: addr = self.extract_address(tx, account_index) if addr: address = addr break if address: self.process_balance_update(address, token_info, post_balance) ``` This is the first part of the `process_message` method, which handles block-level balance updates. The method is quite long, as it needs to handle multiple data sources within each message. ``` # Process transactions if hasattr(token_block, 'Transactions'): for tx in token_block.Transactions: # Process transfers - these usually have the best address information if hasattr(tx, 'Transfers'): for transfer in tx.Transfers: # Extract token metadata token_info = {"mint": "UNKNOWN", "symbol": "UNKNOWN", "decimals": 0, "name": "UNKNOWN"} if hasattr(transfer, 'Currency'): token_info = self.extract_token_metadata(transfer.Currency) # Get sender and receiver addresses sender_address = None if hasattr(transfer, 'Sender') and hasattr(transfer.Sender, 'Address'): sender_address = self.convert_bytes(transfer.Sender.Address) receiver_address = None if hasattr(transfer, 'Receiver') and hasattr(transfer.Receiver, 'Address'): receiver_address = self.convert_bytes(transfer.Receiver.Address) # Process balance updates in instruction if hasattr(transfer, 'Instruction') and hasattr(transfer.Instruction, 'TokenBalanceUpdates'): for balance_update in transfer.Instruction.TokenBalanceUpdates: if hasattr(balance_update, 'PostBalance') and hasattr(balance_update, 'AccountIndex'): post_balance = balance_update.PostBalance account_index = balance_update.AccountIndex # Determine address based on account index address = None if account_index == 0 and sender_address: address = sender_address elif account_index == 2 and receiver_address: address = receiver_address if address: self.process_balance_update(address, token_info, post_balance) ``` The next segment processes transfers, which provide the most accurate and direct information about wallet balance changes. It extracts the token metadata, sender and receiver addresses, and processes the balance updates for each. ``` # Process transaction-level balance updates if hasattr(tx, 'BalanceUpdates'): for update in tx.BalanceUpdates: if hasattr(update, 'BalanceUpdate') and hasattr(update, 'Currency'): balance_update = update.BalanceUpdate currency = update.Currency # Extract token metadata token_info = self.extract_token_metadata(currency) # Extract balance and account if hasattr(balance_update, 'PostBalance') and hasattr(balance_update, 'AccountIndex'): post_balance = balance_update.PostBalance account_index = balance_update.AccountIndex # Get address from transaction address = self.extract_address(tx, account_index) if address: self.process_balance_update(address, token_info, post_balance) ``` This segment processes transaction-level balance updates, which provide additional balance information that might not be captured in transfers. ``` # Process token balance updates if hasattr(tx, 'TokenBalanceUpdates'): for update in tx.TokenBalanceUpdates: if hasattr(update, 'PostBalance') and hasattr(update, 'AccountIndex'): post_balance = update.PostBalance account_index = update.AccountIndex # Get address from transaction address = self.extract_address(tx, account_index) # Get token info from account token_info = {"mint": "UNKNOWN", "symbol": "UNKNOWN", "decimals": 0, "name": "UNKNOWN"} if hasattr(tx, 'Accounts') or (hasattr(tx, 'Header') and hasattr(tx.Header, 'Accounts')): accounts = tx.Accounts if hasattr(tx, 'Accounts') else tx.Header.Accounts if account_index < len(accounts): account = accounts[account_index] if hasattr(account, 'Token'): token = account.Token if hasattr(token, 'Mint'): token_info["mint"] = self.convert_bytes(token.Mint) if hasattr(token, 'Decimals'): token_info["decimals"] = token.Decimals # Use token metadata from cache if available if token_info["mint"] != "UNKNOWN" and token_info["mint"] in self.token_metadata: cached_data = self.token_metadata[token_info["mint"]] token_info["symbol"] = cached_data["symbol"] token_info["name"] = cached_data["name"] if address: self.process_balance_update(address, token_info, post_balance) ``` Finally, the method processes token balance updates, which provide yet another source of balance information. This multi-layered approach ensures we capture all possible balance changes. ``` # Export balances if it's time self.export_balances() # Periodically print stats if self.stats['messages_processed'] % 10 == 0: self.print_stats() ``` After processing all the balance updates, the method checks if it's time to export the balances and prints statistics periodically. #### Statistics Reporting ```python def print_stats(self): """Print tracker statistics""" elapsed = time.time() - self.stats['start_time'] updates_per_sec = ( self.stats['balance_updates_received'] / elapsed if elapsed > 0 else 0 ) print(f"\n--- Wallet Balance Tracker Stats ---") print(f"Runtime: {elapsed:.2f} seconds") print(f"Messages processed: {self.stats['messages_processed']}") print(f"Balance updates received: {self.stats['balance_updates_received']} ({updates_per_sec:.2f}/sec)") print(f"Unique addresses tracked: {self.stats['unique_addresses']}") print(f"Unique tokens tracked: {self.stats['unique_tokens']}") ``` This method prints statistics about the tracker's performance, including runtime, messages processed, balance updates received, and the number of unique addresses and tokens tracked. #### Main Consumer Function ```python def run_consumer(): """Run the Kafka consumer with the indexed wallet tracker""" # Load environment variables from .env file load_dotenv() # Get credentials from environment variables kafka_username = os.getenv("KAFKA_USERNAME") kafka_password = os.getenv("KAFKA_PASSWORD") # Kafka configuration group_id_suffix = uuid.uuid4().hex conf = { 'bootstrap.servers': 'rpk0.bitquery.io:9092,rpk1.bitquery.io:9092,rpk2.bitquery.io:9092', 'group.id': f'{kafka_username}-group-{group_id_suffix}', 'session.timeout.ms': 30000, 'security.protocol': 'SASL_PLAINTEXT', 'ssl.endpoint.identification.algorithm': 'none', 'sasl.mechanisms': 'SCRAM-SHA-512', 'sasl.username': kafka_username, 'sasl.password': kafka_password, 'auto.offset.reset': 'latest', } # Initialize consumer consumer = Consumer(conf) topic = 'solana.tokens.proto' consumer.subscribe([topic]) # Initialize wallet tracker tracker = IndexedWalletTracker() print(f"Starting indexed wallet balance tracker on topic: {topic}") print("Press Ctrl+C to stop...") ``` This function initializes the Kafka consumer with the appropriate configuration and sets up the wallet tracker. ``` try: while True: msg = consumer.poll(timeout=1.0) if msg is None: continue if msg.error(): if msg.error().code() == KafkaError._PARTITION_EOF: continue else: raise KafkaException(msg.error()) try: # Parse the message buffer = msg.value() token_block = token_block_message_pb2.TokenBlockMessage() token_block.ParseFromString(buffer) # Process the message tracker.process_message(token_block) except DecodeError as err: print(f"Protobuf decoding error: {err}") except Exception as err: print(f"Error processing message: {err}") import traceback traceback.print_exc() ``` The main processing loop polls for messages from Kafka, parses them using the protobuf definition, and passes them to the tracker for processing. It includes error handling to catch and report any issues. ``` except KeyboardInterrupt: print("\nStopping wallet balance tracker...") finally: # Export final balances tracker.export_balances(force=True) consumer.close() print("Consumer closed.") ``` The function concludes with cleanup code that exports the final balances and closes the consumer when the program is stopped. ## Data Flow Through the System To understand how data flows through this system: 1. Kafka Consumer retrieves messages from Bitquery's Solana token stream 2. Each message is parsed into a protobuf TokenBlockMessage object 3. The process_message method extracts: - Block-level balance updates - Transfers and their associated balance updates - Transaction-level balance updates - Token balance updates 4. For each balance update, the system: - Extracts token metadata (mint, symbol, decimals) - Finds the associated wallet address - Updates the in-memory wallet index with the latest balance 5. Periodically, the system exports the in-memory balances to JSON and CSV files 6. Statistics are printed to the console to monitor performance ## Scaling for Production Use While this proof-of-concept demonstrates the core concepts, a production implementation would require several additional components: - **Database integration**: Replace file exports with a proper database system - **Distributed processing**: Run multiple consumer instances with the same group ID - **Caching strategy**: Implement memory management for less active wallets - **Error recovery**: Add robust error handling and consumer position checkpointing - **Monitoring and alerting**: Add systems to detect processing lags or failures - **API layer**: Create endpoints for querying balance data For exchanges, custodial services, and other financial institutions, additional considerations around security, high availability, and compliance would also be necessary. ## Conclusion: Beyond RPC Polling Traditional RPC-based balance monitoring simply can't keep up with the demands of modern blockchain applications. By leveraging Bitquery's Kafka Streams, you can: - Reduce infrastructure costs by up to 90% compared to RPC polling - Improve detection latency from seconds to milliseconds - Scale to millions of wallets without performance degradation - Achieve 100% accuracy with no missed transactions Ready to explore how Bitquery's Kafka Streams could transform your wallet monitoring capabilities? Clone the repository for a starting point and reach out to the Bitquery team on [Telegram](https://t.me/bloxy_info) to discuss your specific requirements and get access to the streams. --- _This article provides a technical overview and implementation guidance. The code presented is a proof-of-concept that demonstrates core principles but would require additional engineering for production use. Always conduct thorough testing and security reviews before deploying systems handling financial data._ --- ## Track Token Lock Unlock URL: https://docs.bitquery.io/docs/API-Blog/track-token-lock-unlock/ Token lock and unlock tracking: add and remove liquidity events, unlock event monitoring, unknown log signatures, and Arbitrum and Solana unlock schedules. # How to Easily Track Token Lock and Unlock Events Using Bitquery APIs In this article, we are going to understand why tracking token lock and unlock events is necessary for investors and project teams in the quickly changing crypto space. Token lockups provide the project with stability and confidence by restricting token sales or transfers for a certain amount of time. The sudden release of a large number of tokens, however, may result in market instability. Tracking these occurrences allows stakeholders to anticipate market movements and make informed decisions. Anyone can follow this guide to understand how to use Bitquery APIs to track vested tokens and receive alerts when unlocking events are about to occur. ## Understanding Token Locks and Unlocks ### What Are Token Locks? Token locks are methods applied by blockchain projects to limit the ability to transfer of tokens for a certain duration. They're frequently used for: Vesting Periods: Tokens are gradually released to team members or early investors to ensure long-term commitment. This can help prevent immediate sell-offs that could negatively impact the token's price. Liquidity Locks: A certain amount of tokens is kept in liquidity pools for a defined duration to aid in market stability. This ensures there is always sufficient liquidity available, which is crucial for the smooth functioning of decentralized exchanges (DEXs) and other financial mechanisms within the ecosystem. ### Why Track Token Unlocks? Tracking token unlocks is necessary for: Investors: To predict possible adjustments in token supply that may affect market values. Knowing when big sums of tokens will be released might assist investors make informed purchases or sales. Project Teams: To oversee token distributions and maintain transparency with stakeholders. Transparency about token unlock schedules can build trust within the community and among investors. ## Key Data Points to Track When tracking token lock and unlock events, the following data points are essential: - Token Name and Symbol: Identify the token in question. - Unlock Date: When the tokens will be unlocked. - Amount to be Unlocked: The quantity of tokens that will be released. - Vesting Schedule: Details of the vesting period and intervals. - Contract Address: The smart contract address governing the lockup. - Transaction History: Past lock and unlock transactions for context. ### Adding Liquidity Event In decentralized finance (DeFi), adding liquidity means putting tokens into a pool on a decentralized exchange (DEX). This process ensures that there are enough tokens available for trading, which is essential for maintaining market stability and enabling smooth transactions. #### Why It's Important Adding liquidity can earn rewards for those who contribute tokens. It also helps keep the market running smoothly. By adding liquidity, token holders can participate in the ecosystem, earn transaction fees, and support the overall health of the market. #### How to Track Use Bitquery APIs to see when large amounts of tokens are added to pools. This can indicate important moves by project teams or big investors. By monitoring these events, you can gain insights into market behavior and anticipate potential price changes based on liquidity movements. #### Example Query To track liquidity events, you can use a query similar to the one for tracking token unlocks, but focusing on transactions related to liquidity pools. Here’s a sample query to track [liquidity additions](https://ide.bitquery.io/liquidity-additions) on the Ethereum network: ```graphql subscription { EVM(network: eth) { Events( where: {LogHeader: {Address: {is: "0x663A5C229c09b049E36dCc11a9B0d4a8Eb9db214"}}, Log: {Signature: {Name: {is: "onDeposit"}}}} ) { Block { Number Time } Arguments { Name Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } Transaction { From } } } } ``` This query helps identify significant liquidity additions, providing insights into market dynamics. ### Removing Liquidity Event Removing liquidity means taking tokens out of a pool. This action can have a significant impact on the market, as it reduces the available liquidity, which can lead to price volatility and affect the overall market stability. #### Why It's Important Removing a lot of liquidity can make the market less stable and cause price swings. It can signal that investors or project teams are withdrawing support from the market, which might indicate upcoming price drops or market corrections. #### How to Track In order to see when large amounts of tokens are taken out of pools, we can use Bitquery Event APIs. This can help predict potential market changes. By monitoring these events, you can stay ahead of market movements and adjust your strategies accordingly. #### Example Query To track liquidity removal events, use a query similar to the one for tracking liquidity additions, but focus on transactions related to liquidity removals. Here’s a sample query to [track liquidity removals](https://ide.bitquery.io/liquidity-removals) on the Ethereum network: ```graphql { EVM(dataset: combined, network: eth) { Events( where: { LogHeader: { Address: { is: "0x663A5C229c09b049E36dCc11a9B0d4a8Eb9db214" } } Log: { Signature: { Name: { is: "onWithdraw" } } } } limit: { count: 50 } orderBy: { descending: Block_Time } ) { Block { Number Time } Arguments { Name Value { ... on EVM_ABI_Integer_Value_Arg { integer } ... on EVM_ABI_String_Value_Arg { string } ... on EVM_ABI_Address_Value_Arg { address } ... on EVM_ABI_BigInt_Value_Arg { bigInteger } ... on EVM_ABI_Bytes_Value_Arg { hex } ... on EVM_ABI_Boolean_Value_Arg { bool } } } Transaction { From } } } } ``` This query helps identify significant liquidity removals, providing insights into market behavior. - Detailed Steps to Track Token Unlocks - Identify the Token and Smart Contract - First, find out which token and smart contract you're dealing with. - Query the Lockup Transactions In order to search for transactions that lock tokens, we can use Bitquery real-time APIs to get alerts in realtime. Here’s a sample query for [locked tokens](https://ide.bitquery.io/realtime-token-lock) on the Ethereum network: ```graphql subscription { EVM( network: eth) { Calls(where: {Call: {Signature: {Signature: {is: "lock()"}}}}) { Call { LogCount InternalCalls } Transaction { Gas Hash From To } Block { Date Number } } } } ``` This query fetches transactions related to the lockup, providing details such as the date, transaction hash, gas price, and gas used. It helps in understanding the initial lockup conditions and timelines. ### Monitor Unlock Events Set up a query to keep an eye on upcoming [token unlock events](https://ide.bitquery.io/token-unlock). For example: ```graphql subscription { EVM(network: eth) { Calls(where: {Call: {Signature: {Signature: {is: "unlock()"}}}}) { Call { LogCount InternalCalls } Transaction { Gas Hash From To } Block { Date Number } } } } ``` This subscription query tracks transactions that signal token unlocks, providing details about the dates, transaction hash, gas price, and gas used. It helps in predicting when tokens will become available for trading. 4. Set Up Notifications [Create notifications to alert](/docs/usecases/monitoring-solana-blockchain-real-time-tutorial/) you when tokens are about to be unlocked. You can use email alerts, websockets, or messaging platforms like Slack. Setting up notifications ensures you stay informed about important events and can react promptly to market changes. ### Tracking Token Lock/ Unlock Without Knowing Log Signatures In any query if you are not sure about the event name or signature, use the `includes` filter to filter the events. In the below example, let's track latest token lock and unlock events on Optimism by using `includes: "locked"` filter for Log Signatures. You can run the query [here](https://ide.bitquery.io/Optimism-token-unlocked) ```graphql { EVM(dataset: archive, network: optimism) { Events( where: {Log: {Signature: {Name: {includes: "locked"}}}} limit: {count: 10} orderBy: {descending: Block_Time} ) { ChainId Transaction { Hash } Log { Signature { Name Signature } } Fee { SenderFee } Block { Time Number } } } } ``` ### Arbitrum Token Unlock Arbitrum, a solution to help Ethereum scale better, has a schedule for when its tokens will be unlocked. Tracking these events helps investors predict market behavior and price impacts. By monitoring Arbitrum's token unlock schedule, investors can anticipate changes in token supply and adjust their strategies accordingly. You can run the query [here](https://ide.bitquery.io/Arbitrum-Token-Unlock_1) ```graphql query ($network: evm_network, $limit: Int, $method: String) { EVM(dataset: archive, network: $network) { Events( where: {Log: {Signature: {SignatureHash: {is: $method}}}} limit: {count: $limit} orderBy: {descending: Block_Time} ) { ChainId Transaction { Hash } Log { Signature { Name } } Fee { SenderFee } Block { Time Number } } } } { "limit": 10, "network": "arbitrum", "method": "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67" } ``` ### Solana Token Unlock Schedule Solana, known for its fast and cheap transactions, has important token unlock events. Tracking these can help understand its market dynamics and potential price movements. You can run the query [here](https://ide.bitquery.io/Solana-Token) ```graphql { Solana { Instructions( where: {Instruction: {Program: {Name: {is: "timelock"}}}} limit: {count: 10} orderBy: {descending: Block_Time} ) { Instruction { Accounts { Address IsWritable Token { Mint Owner ProgramId } } Program { AccountNames Address Name Method } Logs } Transaction { Signature Signer } } } } ``` ## Conclusion Token lockups are becoming common in the cryptocurrency world. They help prevent price volatility and ensure long-term success for projects. This builds trust between investors and development teams. However, choosing the right lockup structure is crucial. It should fit the project's goals and objectives. Tracking token locks and unlocks is essential for managing cryptocurrencies effectively. Bitquery APIs offer powerful tools to monitor these events, providing detailed data and customizable queries. By using Bitquery, investors and project teams can stay informed about token movements, make well-informed decisions, and maintain market confidence. --- ## Track ZEC on Hyperliquid — Positions, Liquidations, Funding & Trades API URL: https://docs.bitquery.io/docs/perpetuals/hyperliquid/track-zec-on-hyperliquid/ Track Zcash (ZEC) perp activity on Hyperliquid with the Bitquery API: every open ZEC position market-wide, real-time liquidations, funding payments, whale trades, candles and mark prices over GraphQL and WebSocket. # Track ZEC on Hyperliquid ZEC (Zcash) is one of the most actively traded perp markets on Hyperliquid. This page collects ready-to-run queries for tracking the entire ZEC market — every open position, liquidations as they happen, funding flow, whale-sized fills, candles and mark prices — using the `Hyperliquid` cube on the [streaming API](https://streaming.bitquery.io/graphql). Every query below also works as a real-time WebSocket stream: change `query` to `subscription` and drop `limit`/`orderBy`. :::note API Key Required To query or stream data outside the Bitquery IDE, you need an API access token. Follow the steps here: [How to generate Bitquery API token ➤](/docs/authorization/how-to-generate/) ::: ## Every open ZEC position on the exchange `CurrentPositions` is a state cube: it holds the currently open perp position of every trader. Unlike the native Hyperliquid API — which only returns positions for an address you already know — this enumerates the whole ZEC book, so you can compute market-wide long/short totals or find the biggest whales without knowing a single wallet in advance. Negative `Size` is a short; `Funding` is the position's accumulated funding (positive = collected, negative = paid). Run it in the IDE: [All Open ZEC Positions ➤](https://ide.bitquery.io/zec-hyperliquid-open-positions) ```graphql query { Hyperliquid { CurrentPositions( limit: {count: 200} orderBy: {descending: LastTime} where: {Market: {Symbol: {is: "ZEC"}}} ) { LastTime Market { Symbol Kind } Position { Size Leverage IsCross Funding RealizedPnl } Trader { Address } } } } ``` To watch one whale instead, swap the filter to `where: {Trader: {Address: {is: "0x..."}}}` and you get every open position of that wallet across all markets. ## ZEC liquidations — history and live stream Each liquidation carries the liquidated user, the `Method` (`market` for open-market liquidation, `backstop` when the backstop vault takes over), the mark price and the forced execution. The execution `Side` tells you which side got wiped: a liquidated short is closed by a `Buy`, a liquidated long by a `Sell`. Run it in the IDE: [ZEC Liquidations ➤](https://ide.bitquery.io/zec-hyperliquid-liquidations) ```graphql query { Hyperliquid { PerpLiquidations( limit: {count: 100} orderBy: {descending: Block_Time} where: {Liquidation: {Market: {Symbol: {is: "ZEC"}}}} ) { Block { Time } Liquidation { Market { Symbol } Method MarkPx Liquidator LiquidatedUser Execution { Price Size Side } Position { Leverage IsCross Side SizeBefore } } } } } ``` ### Real-time ZEC liquidation alerts Run it in the IDE: [ZEC Liquidations Stream ➤](https://ide.bitquery.io/zec-hyperliquid-liquidations-stream) ```graphql subscription { Hyperliquid { PerpLiquidations( where: {Liquidation: {Market: {Symbol: {is: "ZEC"}}}} ) { Block { Time } Liquidation { Market { Symbol } Method MarkPx LiquidatedUser Execution { Price Size Side } Position { Leverage IsCross SizeBefore } } } } } ``` ## ZEC funding payments — who pays whom `PerpFundings` records every per-trader funding transfer at each hourly tick: the signed `Amount` (negative = the trader paid funding), the `Rate` applied and the position `Size`. When ZEC longs are crowded the rate is positive and longs pay shorts — summing `Amount` over a window shows exactly how much it costs to stay long. Run it in the IDE: [ZEC Funding Payments ➤](https://ide.bitquery.io/zec-hyperliquid-funding-payments) ```graphql query { Hyperliquid { PerpFundings( limit: {count: 200} orderBy: {descending: Block_Time} where: {Funding: {Market: {Symbol: {is: "ZEC"}}}} ) { Block { Time } Funding { Market { Symbol } Amount Rate Size Trader { Address } } } } } ``` Add `Trader: {Address: {is: "0x..."}}` inside the `Funding` filter to compute the total funding one wallet has paid or collected on ZEC. ## Whale-sized ZEC trades Every fill carries direction, leverage and realized PnL. Filter on `Execution: {Size: ...}` to see only whale prints — here, fills of 200 ZEC or more. Run it in the IDE: [ZEC Whale Trades ➤](https://ide.bitquery.io/zec-hyperliquid-whale-trades) ```graphql query { Hyperliquid { Trades( limit: {count: 100} orderBy: {descending: Block_Time} where: {Trade: {Market: {Symbol: {is: "ZEC"}}, Execution: {Size: {ge: "200"}}}} ) { Block { Time } Trade { Execution { Price Size Side Direction IsAggressor } Position { Leverage IsCross RealizedPnl } Trader { Address } } } } } ``` ### Stream every ZEC fill in real time Run it in the IDE: [ZEC Trades Stream ➤](https://ide.bitquery.io/zec-hyperliquid-trades-stream) ```graphql subscription { Hyperliquid { Trades( where: {Trade: {Market: {Symbol: {is: "ZEC"}}}} ) { Block { Time } Trade { Execution { Price Size Side Direction IsAggressor } Position { Leverage IsCross RealizedPnl } Trader { Address } } } } } ``` ## ZEC candles (OHLCV) Candle `Duration` is in seconds — `60` for 1-minute, `300` for 5-minute, `3600` for hourly. Run it in the IDE: [ZEC Hourly Candles ➤](https://ide.bitquery.io/zec-hyperliquid-hourly-candles) ```graphql query { Hyperliquid { Candles( limit: {count: 168} orderBy: {descending: Interval_Time_Start} where: {Market: {Symbol: {is: "ZEC"}}, Interval: {Time: {Duration: {eq: 3600}}}} ) { Interval { Time { Start Duration } } Market { Symbol } Ohlc { Open High Low Close Volume } } } } ``` ## ZEC mark price The latest mark price per market, streamable for live dashboards. Run it in the IDE: [ZEC Mark Price ➤](https://ide.bitquery.io/zec-hyperliquid-mark-price) ```graphql query { Hyperliquid { MarkPrices( limit: {count: 1} orderBy: {descending: LastTime} where: {Market: {Symbol: {is: "ZEC"}}} ) { LastTime Mark Market { Symbol Kind } } } } ``` ## ZEC leverage changes `TraderLeverageUpdates` fires whenever a trader changes leverage or flips between cross and isolated margin on ZEC — often the tell that a large position is about to be opened or defended. Run it in the IDE: [ZEC Leverage Updates ➤](https://ide.bitquery.io/zec-hyperliquid-leverage-updates) ```graphql query { Hyperliquid { TraderLeverageUpdates( limit: {count: 100} orderBy: {descending: Block_Time} where: {LeverageUpdate: {Market: {Symbol: {is: "ZEC"}}}} ) { Block { Time } LeverageUpdate { Leverage IsCross Market { Symbol } Trader { Address } } } } } ``` ## Related pages - [Hyperliquid API overview](/docs/perpetuals/hyperliquid) — all available cubes and the Kafka streams - [Trades & Candles](/docs/perpetuals/hyperliquid/hyperliquid-trades-api) - [Liquidations, Funding, Positions & Leverage](/docs/perpetuals/hyperliquid/hyperliquid-perpetuals-api) - [Mark Prices & Price Updates](/docs/perpetuals/hyperliquid/hyperliquid-prices-api) --- ## Trade Labelling Module URL: https://docs.bitquery.io/docs/usecases/wash-trading-detector/prepare-data/label/ Build Trade Labelling Module: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. # Trade Labelling Module The `label.py` module provides a single function, `label_trades`, that accepts raw DEX trade data and applies a series of rule-based checks to label each trade as wash trade or not. These rules are imported from `token_rules.py`, allowing us to maintain a clean separation between rule logic and labeling orchestration. ## Purpose The goal of this module is to return a labeled DataFrame with a new column `is_wash_trade`, where: - `1` means the trade is flagged as wash trade - `0` means the trade is considered normal ## Understanding Code Logic ### Imports and Function Initialisation ```py from token_rules import ( detect_self_trades, detect_repeated_pairs, detect_loops, detect_spoofing, get_suspicious_summary ) def label_trades(trade_data): # All the code written below is placed here ``` - Imports all rule functions from `token_rules.py`. - Uses `pandas` to work with structured tabular data. ### Flattening Trade Data Converts the nested JSON object into a flat pandas Dataframe. ```py df = pd.json_normalize(trade_data) ``` ### Applying Rules Each function returns a filtered DataFrame of suspicious trades that match one rule and finaly returns suspicious trades. ```py self_trades = detect_self_trades(df) repeated_pairs = detect_repeated_pairs(df) loops = detect_loops(df) spoofing = detect_spoofing(df) suspicious_tokens, suspicious_tx, suspicious_wallets = get_suspicious_summary( self_df=self_trades, repeated_df=repeated_pairs, loops_df=loops, spoofed_df=spoofing, original_df=df ) ``` ### Return Labeled Data The suspicious trades are labelled as wash trades and returned. ```py df["is_wash_trade"] = ( df["Trade.Buy.Account.Address"].isin(suspicious_wallets) | df["Trade.Sell.Account.Address"].isin(suspicious_wallets) | df["Transaction.Signature"].isin(suspicious_tx) ) return df ``` --- ## Traders API: Stream and Rank Wallet Trades on Solana, Ethereum, BSC, Base URL: https://docs.bitquery.io/docs/trading/crypto-trades-api/traders-api/ Wallet view of the Trading API: stream one or many wallets' DEX trades, filter by token, chain, DEX or pair, catch whale trades, rank traders by volume and PnL. # Traders API: Stream and Rank Wallet Trades on Solana, Ethereum, BSC, Base The Traders API is the wallet view of `Trading.Trades`: every DEX trade a wallet makes on Solana, Ethereum, BSC, Base, Arbitrum and the other chains the cube covers, with the token pair, the USD amounts, the market and the side on each row. Filter on `Trader.Address` for one wallet, `in` for a watchlist, and combine with token, chain, DEX or pair filters. The cube keeps about a month of trades, which is where copy trading, whale alerts and leaderboards live; for older wallet history use the chain cubes. Every example runs in the [IDE](https://ide.bitquery.io) on a free account. The worked wallet, `AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51`, is one of the most active Solana wallets at the time of writing; take any address from the top-traders query below. ## Video Tutorial ## How do I stream all trades of a wallet? Every DEX trade the wallet sends, on every chain, as it lands. Saved stream [here](https://ide.bitquery.io/All-trades-of-a-trader). ```graphql subscription { Trading { Trades(where: { Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } }) { Block { Time } Side Price PriceInUsd Amounts { Base Quote } AmountsInUsd { Base Quote } Pair { Token { Symbol Id } QuoteToken { Symbol Id } Market { Address Protocol Network } } TransactionHeader { Fee } } } } ``` ## How do I track a wallet's trades on one token? The token can sit on either side of a pair, so match `Pair.Token.Id` or `Pair.QuoteToken.Id` with `any`. Token ids are `bid::
`. Saved stream [here](https://ide.bitquery.io/trades-of-a-specific-trader-of-a-specific-token_1). ```graphql subscription { Trading { Trades( where: { Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } any: [ { Pair: { Token: { Id: { is: "bid:solana:AGi2s9zPRPHs3zEDPhPTroumTEXK5ufymYSfEFndCSSW" } } } } { Pair: { QuoteToken: { Id: { is: "bid:solana:AGi2s9zPRPHs3zEDPhPTroumTEXK5ufymYSfEFndCSSW" } } } } ] } ) { Block { Time } Side PriceInUsd AmountsInUsd { Base Quote } Pair { Token { Symbol } QuoteToken { Symbol } Market { Protocol } } } } } ``` ## How do I monitor several wallets in one subscription? An `in` list on the trader address; each message carries the wallet it belongs to. Saved stream [here](https://ide.bitquery.io/How-do-I-monitor-multiple-wallets-in-one-subscription). ```graphql subscription { Trading { Trades( where: { Trader: { Address: { in: [ "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" "Gygj9QQby4j2jryqyqBHvLP7ctv2SaANgh4sCb69BUpA" "FHpcNSe6tb2n15bAdq4BkeYWGyZKFD7yLYrH92ng7wCT" ] } } } ) { Block { Time } Trader { Address } Side AmountsInUsd { Base } Pair { Token { Symbol } QuoteToken { Symbol } Market { Network Protocol } } } } } ``` ## How do I stream a wallet's trades on one chain? Add `Pair.Market.Network`. Saved stream [here](https://ide.bitquery.io/How-do-I-stream-a-wallets-trades-on-a-specific-chain). ```graphql subscription { Trading { Trades( where: { Pair: { Market: { Network: { is: "Solana" } } } Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } } ) { Block { Time } Side AmountsInUsd { Base } Pair { Token { Symbol } QuoteToken { Symbol } Market { Address Protocol } } } } } ``` ## How do I catch whale trades as they happen? Trades over a hundred thousand dollars on any chain, which arrive steadily across the cube. Saved stream [here](https://ide.bitquery.io/Stream---Trades-over-100k-usd). ```graphql subscription { Trading { Trades(where: { AmountsInUsd: { Base: { gt: 100000 } } }) { Block { Time } Side Trader { Address } AmountsInUsd { Base Quote } Pair { Token { Symbol } QuoteToken { Symbol } Market { Network Protocol } } } } } ``` ## How do I get the whale trades of one wallet? Combine the trader and the USD floor. The example wallet makes six-figure stablecoin trades on Manifest at the time of writing, so the query form over the last day returns rows; the same `where` as a subscription fires only when the wallet trades. Saved query [here](https://ide.bitquery.io/How-do-I-stream-whale-trades-for-a-specific-wallet). ```graphql { Trading { Trades( where: { Trader: { Address: { is: "5edLA6ZZFAUfDa8mkdPTi6GXY7fqiZ3CrbRwF7dCGjgr" } } AmountsInUsd: { Base: { gt: 100000 } } Block: { Time: { since_relative: { hours_ago: 24 } } } } orderBy: { descending: Block_Time } limit: { count: 20 } ) { Block { Time } Side AmountsInUsd { Base } Pair { Token { Symbol } QuoteToken { Symbol } Market { Network Protocol } } } } } ``` ## How do I get a wallet's recent trades? The last ten minutes as a query, newest first. Saved query [here](https://ide.bitquery.io/How-do-I-get-recent-trades-for-a-wallet-last-10-minutes). ```graphql { Trading { Trades( orderBy: { descending: Block_Time } limit: { count: 50 } where: { Block: { Time: { since_relative: { minutes_ago: 10 } } } Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } } ) { Block { Time } Side Price PriceInUsd AmountsInUsd { Base Quote } Pair { Token { Symbol Id } QuoteToken { Symbol } Market { Address Protocol Network } } } } } ``` ## How do I watch several wallets on one token? A watchlist and a token together, the copy-trading shape. Saved stream [here](https://ide.bitquery.io/How-do-I-monitor-multiple-wallets-trading-a-specific-token). ```graphql subscription { Trading { Trades( where: { Trader: { Address: { in: [ "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" "Gygj9QQby4j2jryqyqBHvLP7ctv2SaANgh4sCb69BUpA" ] } } any: [ { Pair: { Token: { Id: { is: "bid:solana:AGi2s9zPRPHs3zEDPhPTroumTEXK5ufymYSfEFndCSSW" } } } } { Pair: { QuoteToken: { Id: { is: "bid:solana:AGi2s9zPRPHs3zEDPhPTroumTEXK5ufymYSfEFndCSSW" } } } } ] } ) { Block { Time } Trader { Address } Side AmountsInUsd { Base } Pair { Token { Symbol } QuoteToken { Symbol } } } } } ``` ## How do I stream a wallet's trades on one DEX? `Pair.Market.Program` is the DEX program on Solana and the router or pool manager on EVM chains; the example is Raydium's CPMM. Saved stream [here](https://ide.bitquery.io/How-do-I-stream-a-wallets-trades-on-a-specific-DEX). ```graphql subscription { Trading { Trades( where: { Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } Pair: { Market: { Program: { is: "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C" } } } } ) { Block { Time } Side AmountsInUsd { Base } Pair { Token { Symbol } QuoteToken { Symbol } Market { Address Protocol } } } } } ``` ## How do I get a wallet's trades on one pair? Pin both token ids; here WSOL against USDC. Saved stream [here](https://ide.bitquery.io/How-do-I-get-a-wallets-trades-on-a-specific-pair). ```graphql subscription { Trading { Trades( where: { Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } Pair: { Token: { Id: { is: "bid:solana:So11111111111111111111111111111111111111112" } } QuoteToken: { Id: { is: "bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } } } ) { Block { Time } Side Price AmountsInUsd { Base } Pair { Market { Address Protocol } } } } } ``` ## Who are the most active traders on Solana this hour? Wallets ranked by trade count, with USD volume split by side. Change the network or the window as needed. Saved query [here](https://ide.bitquery.io/Most-active-traders-by-trade-count). ```graphql { Trading { Trades( limit: { count: 100 } orderBy: [{ descendingByField: "Trades_count" }] where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Pair: { Market: { Network: { is: "Solana" } } } } ) { Trader { Address } Trades_count: count Total_Volume: sum(of: AmountsInUsd_Quote) buy_volume: sum(of: AmountsInUsd_Quote, if: { Side: { is: "Buy" } }) sell_volume: sum(of: AmountsInUsd_Quote, if: { Side: { is: "Sell" } }) buys: count(if: { Side: { is: "Buy" } }) sells: count(if: { Side: { is: "Sell" } }) } } } ``` Sort the same query on `Total_Volume` to rank whales by USD volume instead. Saved query [here](https://ide.bitquery.io/Whales-traders-by-total-USD-volume_1). ## Which wallets only bought, or only sold, this hour? `selectWhere` on an aggregate keeps rows where that count is zero. Sort by volume to see the largest one-sided flows. Saved queries: [only buys](https://ide.bitquery.io/Traders-who-only-buys), [only sells](https://ide.bitquery.io/Traders-who-only-sells_1). ```graphql { Trading { Trades( limit: { count: 100 } orderBy: [{ descendingByField: "Total_Volume" }] where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Pair: { Market: { Network: { is: "Solana" } } } } ) { Trader { Address } Trades_count: count Total_Volume: sum(of: AmountsInUsd_Quote) buys: count(if: { Side: { is: "Buy" } }) sells: count(if: { Side: { is: "Sell" } }, selectWhere: { eq: "0" }) } } } ``` For wallets that only sold, move `selectWhere` to `buys`. ## Which pools did a wallet trade this hour? One row per market with the counts and USD volume. Saved query [here](https://ide.bitquery.io/Trader-interacted-with-these-tokens). ```graphql { Trading { Trades( limit: { count: 100 } orderBy: [{ descendingByField: "Trades_count" }] where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Pair: { Market: { Network: { is: "Solana" } } } Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } } ) { Pair { Market { Address Protocol } Token { Symbol Id } QuoteToken { Symbol } } Trades_count: count Total_Volume: sum(of: AmountsInUsd_Base) buys: count(if: { Side: { is: "Buy" } }) sells: count(if: { Side: { is: "Sell" } }) } } } ``` ## How do I calculate a wallet's PnL on one token? Sum what the wallet paid and what it received in USD over the window; `calculate` takes the difference. It is a cash-flow PnL over the window, so open inventory is not marked to market. Saved query [here](https://ide.bitquery.io/Traders-PnL-for-the-last-30mins-for-a-specific-token). ```graphql { Trading { Trades( where: { Block: { Time: { since_relative: { minutes_ago: 30 } } } Pair: { Token: { Id: { is: "bid:solana:AGi2s9zPRPHs3zEDPhPTroumTEXK5ufymYSfEFndCSSW" } } } Trader: { Address: { is: "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" } } } ) { Trader { Address } Pair { Token { Symbol } } Amount_Bought: sum(of: AmountsInUsd_Base, if: { Side: { is: "Buy" } }) Amount_Sold: sum(of: AmountsInUsd_Base, if: { Side: { is: "Sell" } }) Amount_Bought_native: sum(of: Amounts_Base, if: { Side: { is: "Buy" } }) Amount_Sold_native: sum(of: Amounts_Base, if: { Side: { is: "Sell" } }) PnL: calculate(expression: "$Amount_Sold - $Amount_Bought") buys: count(if: { Side: { is: "Buy" } }) sells: count(if: { Side: { is: "Sell" } }) } } } ``` ## How do I rank traders by PnL on one pool? Filter `Pair.Market.Address` and sort on the calculated field. Saved query [here](https://ide.bitquery.io/Top-Traders-by-PnL-of-a-specific-pair). ```graphql { Trading { Trades( limit: { count: 10 } orderBy: [{ descendingByField: "PnL" }] where: { Block: { Time: { since_relative: { minutes_ago: 30 } } } Pair: { Market: { Address: { is: "CsjcF4mEmJsXZDWzBtuviVEV7JiKTY4NAJ1rdcHhVxi3" } } } } ) { Trader { Address } Amount_Bought: sum(of: AmountsInUsd_Base, if: { Side: { is: "Buy" } }) Amount_Sold: sum(of: AmountsInUsd_Base, if: { Side: { is: "Sell" } }) PnL: calculate(expression: "$Amount_Sold - $Amount_Bought") buys: count(if: { Side: { is: "Buy" } }) sells: count(if: { Side: { is: "Sell" } }) } } } ``` ## How do I rank traders by PnL across Solana? Drop the pool filter and keep one row per wallet with `limitBy`. Saved query [here](https://ide.bitquery.io/Top-Traders-on-Solana_2). ```graphql { Trading { Trades( limit: { count: 10 } limitBy: { count: 1, by: Trader_Address } orderBy: [{ descendingByField: "PnL" }] where: { Block: { Time: { since_relative: { minutes_ago: 30 } } } Pair: { Market: { Network: { is: "Solana" } } } } ) { Trader { Address } Amount_Bought: sum(of: AmountsInUsd_Base, if: { Side: { is: "Buy" } }) Amount_Sold: sum(of: AmountsInUsd_Base, if: { Side: { is: "Sell" } }) PnL: calculate(expression: "$Amount_Sold - $Amount_Bought") } } } ``` ## Which traders paid the most fees this hour? `TransactionHeader.Fee` summed per wallet, a quick way to spot bots. Saved query [here](https://ide.bitquery.io/Traders-paying-the-highest-total-fees). ```graphql { Trading { Trades( limit: { count: 100 } orderBy: [{ descendingByField: "Total_fees_paid_by_trader" }] where: { Block: { Time: { since_relative: { hours_ago: 1 } } } Pair: { Market: { Network: { is: "Solana" } } } } ) { Trader { Address } Trades_count: count Total_fees_paid_by_trader: sum(of: TransactionHeader_Fee) Total_Volume: sum(of: AmountsInUsd_Quote) } } } ``` ## Related APIs {#related-apis} - [Crypto Trades API](/docs/trading/crypto-trades-api/trades-api) - [Crypto Price API](/docs/trading/crypto-price-api/) - [Solana DEX trades API](/docs/blockchain/Solana/solana-dextrades) - [Ethereum DEX trades API](/docs/blockchain/Ethereum/dextrades/dex-api) --- ## Trading Data Overview — Chain-Level Trades vs Trading Cube URL: https://docs.bitquery.io/docs/trading/trading-data-overview/ Trading Data Overview — Chain-Level Trades vs Trading Cube via Bitquery Trading APIs for multi-chain prices, OHLC candles, volume metrics, and live. # Trading Data Overview — Chain-Level Trades vs Trading Cube Bitquery exposes DEX trading data through **two complementary product families**. The choice between them is driven primarily by **how far back you need to look**. :::tip Rule of thumb — pick by time window - **Real-time + last ~30 days** → use the **Trading cube** ([`Trading.Trades`](/docs/trading/crypto-trades-api/trades-api) for swap-level rows, [`Trading.Tokens`](/docs/trading/crypto-price-api/tokens) / [`Currencies`](/docs/trading/crypto-price-api/currency) / [`Pairs`](/docs/trading/crypto-price-api/pairs) for pre-aggregated OHLC). USD price, market cap, and supply come baked in — across **10 chains in one API**. - **Older than ~30 days (historical / archive)** → use chain-level [**`DEXTrades`**](/docs/cubes/dextrades) or [**`DEXTradeByTokens`**](/docs/cubes/dextradesbyTokens) (with `dataset: combined` or `dataset: archive`). Full history, raw on-chain detail, but you derive USD yourself. ::: > Already know you want a chain-level cube and just need to choose between `DEXTrades` and `DEXTradeByTokens`? Jump to the row-shape-level comparison: [**DEXTrades vs DEXTradeByTokens vs Trades cube**](/docs/cubes/dextrades-dextradebytokens-trading-trades). | | **Chain-Level Trades** | **Trading Cube** | |---|---|---| | **Time window** | **Full historical archive** — years of data via `dataset: combined` / `archive` | **Real-time + last ~30 days only** (rolling window) | | **Cubes / fields** | `EVM.DEXTrades`, `EVM.DEXTradeByTokens`, `Solana.DEXTrades`, `Solana.DEXTradeByTokens`, etc. | `Trading.Trades`, `Trading.Currencies`, `Trading.Tokens`, `Trading.Pairs` | | **What it is** | Raw, parsed swaps directly from each chain | Curated, multi-chain trading feed built **on top of** chain-level trades | | **Granularity** | Per-chain, per-DEX, per-swap (with calls / instructions / events context) | Per-swap (`Trades`) + pre-aggregated OHLC (`Tokens` / `Currencies` / `Pairs`) | | **USD prices** | Not present for every token — you derive prices yourself | USD price, market cap, FDV, supply on every row via the **Bitquery Price Index** | | **OHLC** | Built **on the fly** from raw trades inside your query (any interval) | **Pre-aggregated** down to **1 second** (fixed intervals) | | **Quality filtering** | Raw — every on-chain swap, including MEV / outliers | **MEV and low-quality trades filtered** out for cleaner feeds | | **Calls / events / instructions** | Yes — full transaction context available | No — trade-only schema | | **Chains** | Each chain has its own root (EVM, Solana, Tron, etc.) | **10 chains under one API**: Ethereum, BSC, Solana, Base, Arbitrum, Tron, Optimism, Polygon (`Matic`), Robinhood, Arc | | **Best for** | **Historical analytics** (anything older than ~30 days), archive backfills, on-chain research, anything that needs call / event context | **Real-time + last ~30 days** — trading UIs, charting apps, price tickers, bots, screeners, alerts — anything that wants "ready-to-use" trade + price + supply data | > **TL;DR** — **Last 30 days + real-time → Trading cube. Older than 30 days → DEXTrades / DEXTradeByTokens.** Same trades underneath: the Trading cube reads from DEXTrades, attaches Price-Index USD + supply, drops MEV / bad trades, and ships a clean multi-chain stream — but only for the rolling 30-day window. For anything deeper into history, drop down to the chain-level archive. --- ## 1. Chain-Level Trades — `DEXTrades` & `DEXTradeByTokens` Chain-level trades are parsed **directly from each blockchain**. Every DEX swap that lands on-chain is captured, decoded, and exposed under the chain's root in GraphQL (e.g. `EVM.DEXTrades`, `Solana.DEXTradeByTokens`). **Characteristics** - **Raw and complete** — every swap on every supported DEX, including MEV bots, sandwich attacks, and zero-value noise. - **No USD price on every row** — many long-tail tokens have no direct USD pair, so price has to be derived (e.g. via a routing token like WETH/USDC). - **Full chain context** — same row can be joined to **calls / instructions** and **events / logs** for the originating transaction. - **Full historical archive** — use `dataset: archive` or `dataset: combined` to backfill years of trades. - **OHLC is built in-query** — aggregate `Trade.Price` / `Trade.PriceInUSD` with `Block.Time(interval: ...)` to build candles at **any** custom interval. - **Two access shapes for the same data**: - **`DEXTrades`** — one row per swap, *from the pool's perspective* (Buy / Sell side). - **`DEXTradeByTokens`** — two rows per swap, *from each token's perspective* (ideal for token-level OHLC and "all pairs a token trades in"). Read more in the [DEXTradeByTokens cube guide](/docs/cubes/dextradesbyTokens). **Use chain-level trades when you need:** - **Data older than ~30 days** — the Trading cube doesn't go back further; only chain-level archives do. - Deep historical OHLC, backfills, or archive ranges of any size. - Per-trade detail that includes the originating call, instruction, or event log. - Custom OHLC intervals **below** the pre-aggregated grid, or bar sizes the Trading cube's ten fixed durations cannot express. Note that 4-hour, daily and weekly candles *can* be built on the Trading cube by rolling native candles up — see [custom candles](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api#custom-4-hour-daily-and-weekly-candles). - On-chain analytics scoped to a single chain or a specific DEX protocol. Learn more: [DEX Trades API (EVM)](/docs/schema/evm/dextrades) · [DEXTradeByTokens Cube](/docs/cubes/dextradesbyTokens) · [Crypto Price API vs DEXTradeByTokens](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api#crypto-price-api-vs-dextradebytoken). --- ## 2. Trading Cube — `Trading.Trades`, `Tokens`, `Currencies`, `Pairs` The Trading cube is the **product layer** built on top of chain-level trades. It is designed for people who want **trade and price data they can put straight into a UI, bot, or chart** without writing aggregation logic. It is exposed under a single `Trading` root and covers **10 chains in one API**: Ethereum, BSC, Solana, Base, Arbitrum, Tron, Optimism, Polygon, Robinhood, and Arc. :::note Network filters take the API's own names `Network` values are **case-sensitive display names**, and a wrong value returns 0 rows with no error. Polygon is `"Matic"` and BSC is `"Binance Smart Chain"` — `"Polygon"` matches nothing. ::: ### What lives in the Trading cube? | Cube | What it gives you | Typical use | |---|---|---| | **`Trading.Trades`** | Individual swap-level rows with **USD price**, **USD amounts**, **market cap**, **FDV**, **supply**, and pair / trader / tx context | Live trade feeds, copy-trading bots, whale alerts, per-swap analytics | | **`Trading.Tokens`** | Pre-aggregated OHLC, volume, supply and moving averages for a **token on a specific chain**, blended across all of its pools | Chain-wide price streams, [token screeners](/docs/trading/query-operators/selectwhere-screeners/) | | **`Trading.Currencies`** | Pre-aggregated OHLC for a **currency aggregated across chains** (e.g. BTC across WBTC, cbBTC, native BTC, etc.) | Chain-agnostic global price for an asset | | **`Trading.Pairs`** | Pre-aggregated OHLC and volume **per trading pair on a specific market/DEX** | Pair-specific charts (e.g. SOL/USDC on Raydium), and — with [rank 1](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) — the most accurate price for a single token | ### Characteristics - **USD price on every row** — powered by the [Bitquery Price Index](/docs/trading/crypto-price-api/price-index-algorithm), which derives a USD value for **every** token (even long-tail tokens with no direct stable pair). - **Supply & market-cap snapshots** included — `MarketCap`, `FullyDilutedValuationUsd`, `CirculatingSupply`, `TotalSupply`, `MaxSupply`. See [Supply fields](/docs/trading/crypto-price-api/supply-fields). - **Pre-aggregated OHLC** in `Tokens`, `Currencies` and `Pairs` — down to **1-second** candles, with fixed intervals (`1, 3, 5, 10, 30, 60, 300, 900, 1800, 3600` seconds). - **MEV and low-quality trades are filtered out** — outliers, sandwich attacks, near-zero amounts, and bad prints are removed so the feed is safe to render to end users. - **No calls / events / instructions** — the schema is intentionally trade-shaped; for transaction context, drop down to the chain-level APIs. - **Rolling ~30-day window** — Trading cube data is not a deep archive; for older trades use the chain-level DEXTrades archive. ### Use the Trading cube when you need: - **Real-time data or anything within the last ~30 days** — this is the default for live trading UIs, bots, dashboards, and screeners. For screeners specifically, see [chain-scale screeners with `selectWhere`](/docs/trading/query-operators/selectwhere-screeners/) — the whole filter runs server-side in one request. - A **multi-chain trade or price stream** without writing per-chain queries. - **USD pricing, market cap, and supply** ready on every row (no separate price lookups). - **Pre-aggregated OHLC** at 1-second or longer intervals (Tokens / Currencies / Pairs). - A **clean, MEV-filtered** feed safe to render in a trading UI or feed to a bot. - Sub-second latency over GraphQL subscriptions or the [`trading.prices`](/docs/trading/crypto-price-api/#kafka-topic-for-crypto-price-stream-tradingprices) Kafka topic. Learn more: [Crypto Trades API](/docs/trading/crypto-trades-api/trades-api) · [Crypto Price API](/docs/trading/crypto-price-api/) · [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm). --- ## How the two layers relate ``` ┌───────────────────────────────────────┐ │ On-chain swaps (every DEX) │ └────────────────────┬──────────────────┘ │ parsed per chain ▼ ┌──────────────────────────────────────────────────────────────┐ │ Chain-Level Trades │ │ EVM.DEXTrades / EVM.DEXTradeByTokens │ │ Solana.DEXTrades / Solana.DEXTradeByTokens / … │ │ + calls, events, instructions │ │ + full historical archive │ └────────────────────┬─────────────────────────────────────────┘ │ MEV + bad-trade filtering │ Bitquery Price Index attaches USD + supply ▼ ┌──────────────────────────────────────────────────────────────┐ │ Trading Cube (10 chains under one API) │ │ Trading.Trades — clean swap-level rows + USD │ │ Trading.Tokens — pre-aggregated token OHLC │ │ Trading.Currencies — cross-chain currency OHLC │ │ Trading.Pairs — per-market pair OHLC │ │ ~30-day rolling window │ └──────────────────────────────────────────────────────────────┘ ``` In short: **`Trading.Trades` is sourced from `DEXTrades`**, with MEV / low-quality trades dropped and Price-Index USD + supply data joined on. The aggregated cubes (`Tokens`, `Currencies`, `Pairs`) are then built on top of that cleaned trade stream. --- ## Which API should I use? The first two rows answer the question for 80% of users — pick by **how far back you need data**. The rest are tie-breakers when both windows would technically work. | If you want to… | Use | |---|---| | Get **real-time** or **last ~30 days** of trades / OHLC | **`Trading.Trades`** (swap-level) or **`Trading.Tokens` / `Pairs` / `Currencies`** (OHLC) | | Get **older than ~30 days** of trades or candles (historical / archive) | **`EVM.DEXTradeByTokens`** / **`Solana.DEXTradeByTokens`** (with `dataset: combined` or `archive`) | | Render a real-time trade tape in a trading UI | `Trading.Trades` | | Power a price ticker / candle chart with ready USD values | `Trading.Tokens` or `Trading.Pairs` | | Get the **most accurate price for one specific token** | [`Trading.Pairs` + `Ranking: { Position: { eq: 1 } }`](/docs/trading/crypto-price-api/pairs#most-accurate-token-price) — prices from the token's top market instead of a blend across all its pools | | Get a chain-agnostic price for an asset (e.g. BTC across all chains) | `Trading.Currencies` | | Stream all swaps on 10 chains in **one** subscription | `Trading.Trades` | | Build OHLC at a **custom** interval (e.g. 7-second, 4-hour) | `DEXTradeByTokens` (in-query aggregation) | | Join trades to the originating **call / instruction / event log** | `EVM.DEXTrades` / `Solana.DEXTrades` | | Analyze MEV, sandwich attacks, or raw flow | Chain-level (Trading cube filters these out) | | Build wallet-level PnL with USD attribution out-of-the-box | `Trading.Trades` | :::tip Mixing both layers A common pattern is to use the **Trading cube** for the live + 30-day-window tab of your UI (clean USD prices, low latency, multi-chain) and drop down to **`DEXTradeByTokens`** for the *historical* tab of the same UI (deep archive, custom intervals). ::: --- ## Next steps - **Trading cube docs:** [Crypto Trades API](/docs/trading/crypto-trades-api/trades-api) · [Crypto Price API](/docs/trading/crypto-price-api/) · [Tokens cube](/docs/trading/crypto-price-api/tokens) · [Currencies cube](/docs/trading/crypto-price-api/currency) · [Pairs cube](/docs/trading/crypto-price-api/pairs) - **Chain-level trade docs:** [DEX Trades (EVM)](/docs/schema/evm/dextrades) · [DEXTradeByTokens cube](/docs/cubes/dextradesbyTokens) · [Solana DEX Trades](/docs/blockchain/Solana/solana-dextrades) - **Price Index internals:** [Price Index Algorithm](/docs/trading/crypto-price-api/price-index-algorithm) · [Supply fields reference](/docs/trading/crypto-price-api/supply-fields) - **API delivery comparison:** [GraphQL Query vs Subscription vs Kafka](/docs/api-comparison) ## What the Trading cube cannot do Knowing the absences saves more time than knowing the features. None of the following exist on any of the four Trading cubes: | Not available | What to use instead | | --- | --- | | **Liquidity, reserves, TVL or pool depth** — no such field, and nothing in the filter surface | Chain-level `DEXPools` (realtime window only; for historical depth, a [cloud export](/docs/cloud/)) | | **A trader grain outside `Trades`** — `Trader_Address` exists only on `Trading.Trades` | Aggregate `Trading.Trades`; per-entity concentration metrics need one row per entity | | **Joins to chain-level cubes** — there are no `join*` fields on any Trading cube | Query both and join client-side on transaction hash | | **A success / failure filter** — no `Result` or `Status` branch | Chain-level `DEXTrades` with `Transaction.Result.Success` | | **Fees or trade-count columns** | Derive trade counts with `count`; fees come from chain-level cubes | | **Volume-based intervals** | [Time intervals plus a volume floor in `where`](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api#volume-based-aggregation) | | **Candles above 3600s natively** | [Roll native candles up](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api#custom-4-hour-daily-and-weekly-candles) | | **`selectWhere`, `if` and most statistics on streams** | [What does not survive streaming](/docs/subscriptions/what-does-not-survive-streaming/) | For what the cube *can* do beyond price and volume — server-side screening, argmax selectors, concentration statistics and wallet overlap — see [Query Operators](/docs/trading/query-operators/selectwhere-screeners/). --- ## Trading Filters and Operators Reference URL: https://docs.bitquery.io/docs/trading/query-operators/filters-and-operators/ The complete where-clause surface of the Bitquery Trading cubes: operator sets per scalar type, the any combinator, per-cube filter branches, and the traps that return silently wrong results. # Trading Filters and Operators Reference Every `where` clause on a Trading cube is built from a small, regular set of leaf filter types. This page is the reference for which operators exist, which branches each cube exposes, and the handful of places where a filter returns something other than what it looks like. ## Operator sets by leaf type There is no single operator vocabulary — it depends on the column's type. | Leaf type | Operators | Value | | --- | --- | --- | | `OLAP_String` | `is` `not` `in` `notIn` `like` `notLike` `likeCaseInsensitive` `notLikeCaseInsensitive` `includes` `notIncludes` `includesCaseInsensitive` `notIncludesCaseInsensitive` `startsWith` `startsWithCaseInsensitive` `endsWith` | String | | `OLAP_Float` | `eq` `ne` `ge` `le` `gt` `lt` — **no `in` / `notIn`** | Float | | `OLAP_Integer` | `eq` `ne` `in` `notIn` `ge` `le` `gt` `lt` | Int | | `OLAP_BigInteger` | `eq` `ne` `in` `notIn` `ge` `le` `gt` `lt` | **String** of decimal digits | | `OLAP_DateTime` | `is` `not` `in` `notIn` `since` `till` `after` `before`, plus `_relative` variants | `DateTime` | | `OLAP_Date` | the same set | `YYYY-MM-DD` string | | `Boolean` | none — pass a bare `true` / `false` | Boolean | The three biggest surprises here: **`OLAP_Float` has no `in`**, `OLAP_BigInteger` takes its value as a **string**, and boolean fields take a bare value rather than an operator object. ### Relative time `_relative` variants (`since_relative`, `before_relative`, …) take `seconds_ago`, `minutes_ago`, `hours_ago`, `days_ago`, `weeks_ago`, `months_ago`, `years_ago`, and a `round: { count, offset, in }` for snapping to a boundary. :::note Relative windows move between runs A relative window advances every second, so two identical requests cover different data. When comparing results or testing determinism, use explicit `since` / `till`. ::: ## `any` is the only boolean combinator ```graphql where: { any: [ { ... }, { ... } ] } # OR ``` There is **no `all`, `or`, `and` or `not`**. Sibling keys at any level are AND-ed together — including a sibling of `any`. And `any` **cannot nest**: its element type is the cube filter minus its own `any` field. ## Which branches each cube exposes | Branch | Trades | Pairs | Tokens | Currencies | | --- | :-: | :-: | :-: | :-: | | `Block` · `Price` · `Supply` · `any` | ✅ | ✅ | ✅ | ✅ | | `Currency` | — | ✅ | ✅ | ✅ | | `Interval` · `Volume` | — | ✅ | ✅ | ✅ | | `Token` | — | ✅ | ✅ | — | | `Ranking` | ✅ | ✅ | ✅ | — | | `Market` · `Pool` · `QuoteCurrency` · `QuoteToken` | — | ✅ | — | — | | `Amounts` · `AmountsInUsd` · `PriceInUsd` · `Side` · `Trader` · `TransactionHeader` · `Pair` | ✅ | — | — | — | :::warning Where-clauses are not interchangeable between cubes `Pair` is unknown on Pairs (use top-level `Token` / `QuoteToken` / `Market` / `Pool`); `Interval` is unknown on Trades; `Market` / `Pool` / `QuoteToken` are unknown on Tokens. Copying a clause from one cube to another is the most common cause of `Argument "where" has invalid value`. The network filter differs too: `Pair.Market.Network` on Trades, `Market.Network` on Pairs, `Token.Network` on Tokens. ::: `Price` exists on all four but has **different shapes**: on Trades it is a bare `OLAP_Float` (the trade price); on the other three it is an object with `Ohlc.*`, `Average.*` and `IsQuotedInUsd`. ## `Currency` is not `Token` `Token` is the per-network contract instance. `Currency` is the **cross-chain asset**, so one predicate spans every chain the asset trades on. The practical consequence catches people out: a wrapped token's `Currency` is the underlying native asset. For WSOL, `Currency` is `{ Id: "bid:solana", Symbol: "SOL" }` — so `Currency: { Symbol: { is: "WSOL" } }` matches **nothing**, while `Token: { Symbol: { is: "WSOL" } }` works. Native assets carry `Currency.Id = "bid:"`, whereas `QuoteCurrency.Id` uses a plain slug (`"usdc"`), not the `bid:` form. ## Filtering to a single DEX or protocol On `Trading.Trades`, filter the market rather than the token: ```graphql where: { Pair: { Market: { ProtocolFamily: { is: "..." } } } } ``` `Market.Protocol`, `Market.Program` and `Market.Name` work the same way. Values are **case-sensitive** and use each protocol's own canonical spelling. ## Traps | Trap | What happens | Do instead | | --- | --- | --- | | **Bare `{ count }` on Trades filtered by `Pair.Token.Address` or `Pair.Pool.Address`** | Returns a **wildly inflated** count — an order of magnitude high. Adding any dimension or a second aggregate returns the correct figure. | Filter on `Token.Id` instead, or always project a dimension alongside `count` | | Checksummed EVM address | Returns 0 rows, no error | Addresses are stored **lowercase**; `is` / `in` are exact-match | | Wrong `Network` spelling | Returns 0 rows, no error | Case-sensitive display names — Polygon is `"Matic"`, BSC is `"Binance Smart Chain"` | | `Token.TokenId` / `Token.Did` | Filterable but empty on rows, so any predicate matches nothing | Use `Token.Id` (`bid::
`) or `Token.Address` | | Bare address in `Token.Id` | Returns 0 rows silently | `Token.Id` is chain-qualified; use `Token.Address` for a raw address | | `Interval.VolumeBased` / `TargetVolume` | Parse fine, match nothing | See [Volume-Based Aggregation](/docs/trading/crypto-price-api/crypto-ohlc-candle-k-line-api#volume-based-aggregation) | ## Discovering the full leaf set The leaf paths per cube number in the dozens and change as fields are added. Introspect rather than working from a static list: ```graphql { __type(name: "Trading_Trade_Filter") { inputFields { name type { name kind ofType { name kind ofType { name } } } } } } ``` Swap in `Trading_Pair_Filter`, `Trading_Token_Filter` or `Trading_Currency_Filter`. ## Related - [Screeners with `selectWhere`](/docs/trading/query-operators/selectwhere-screeners/) - [Argmax selectors](/docs/trading/query-operators/argmax-selectors/) - [Trading Data Overview](/docs/trading/trading-data-overview/) --- ## Trading Statistics and Concentration Metrics URL: https://docs.bitquery.io/docs/trading/query-operators/statistics/ Correlation, covariance, quantiles, entropy, Gini and Nakamoto on the Bitquery Trading cube — exact definitions, numerical limits, and which metrics are unsafe on price columns. # Trading Statistics and Concentration Metrics The Trading cubes carry a full statistical suite that runs server-side over the whole filtered population — no pagination, one row back. It is powerful and it has sharp edges: several metrics are **numerically unsafe on price columns**, and two of the most useful ones behave differently from their textbook definitions. Read the limits before building on them. :::warning Query cubes only None of these metrics exist on subscriptions. Streams offer only `average`, `sum`, `count` and `uniq` — see [what does not survive streaming](/docs/subscriptions/what-does-not-survive-streaming/). ::: ## Do not use these on price columns `dispersion`, `standard_deviation`, `skew` and `kurtosis` are computed with a **single-pass Float32 sum of squares**. When the mean is large relative to the spread — which describes every price column — they suffer catastrophic cancellation: - results wrong by tens of percent, - values that change between identical repeat calls, - negative `kurtosis`, - non-zero results for a **constant** sample. They are correct and deterministic on small-magnitude columns such as trade amounts. **For the variance of a price column, use `covariance(of: X, with: X)` instead.** It is the same population variance, computed stably, and returns full Float64 precision where `dispersion` returns about seven significant digits. ## Definitions as implemented | Metric | What it actually computes | | --- | --- | | `covariance` | **Population** covariance (÷ n). `covariance(of: X, with: X)` = population variance | | `dispersion`, `standard_deviation` | Population variance / SD, Float32-truncated — see the warning above | | `correlation` | Pearson. Accuracy is relative-magnitude dependent | | `rank_correlation` | Spearman — exact when neither sample has ties | | `entropy` | Shannon entropy **in bits**, over exact repeated values (not binned) | | `gini`, `theil_index` | Concentration over the values, 0 = even, 1 = fully concentrated | | `nakamoto` | Count of **rows** — not distinct entities — that together reach `ratio` of the total | | `quantile`, `median` | Approximate by default — see below | | `uniq` | Exact to 65,536 distinct values, probabilistic above | ### `nakamoto` counts rows, and its ratio defaults to 0.51 Two things that surprise people. First, on `Trading.Trades` a "row" is a swap leg, so `nakamoto(of: AmountsInUsd_Quote)` answers *how few individual trades make up the majority of volume* — not how few wallets. To get an entity-level coefficient you need one row per entity, which means grouping first. Second, **`ratio` defaults to 0.51**, not 0.5. Pass it explicitly if you mean something else. ### `quantile` and `median` are approximate by default Both use a reservoir sampler capped at 8,192 rows, so above that the same query returns a different answer between runs. `level` defaults to `0.5`, and `median` is the same estimator as default `quantile`. ```graphql p99: quantile(of: Volume_Usd, level: 0.99, method: exact) ``` Below 8,192 rows the approximate result is deterministic but still not equal to exact, because the sampler interpolates between order statistics. ### `uniq` is exact to 65,536, then two-sided Above 2^16 distinct values `uniq` switches to a probabilistic estimator. The error is **two-sided** — it can read high or low by well under a percent — and it is deterministic for a fixed dataset. `uniq(method: exact)` and `count(distinct: )` agree exactly, so `count(distinct:)` is the reference, not an approximation. ## `rank_correlation` will abort your whole query If **any** group in the result has a constant sample, `rank_correlation` fails the entire request — not that group, the whole thing. One constant sample is enough even though the error message mentions both. There is no `if:` workaround at group level. It also runs 2–3× slower than `correlation` and is bounded by a **wall-clock gateway deadline**, not a row cap: it starts timing out intermittently on very large windows (a retry often succeeds) and fails consistently on the largest. Narrow the window rather than hunting for a row limit. ## Silent zeros `correlation` returns **0** — not `null`, not an error — for a single-row group. And at full-cube scale it returns 0 for every column pair, because extreme outliers blow up the Float32 sum of squares. A correlation of exactly 0 is far more likely to mean "degenerate input" than "no relationship". Always check the group's `count` alongside it. ## `if:` support Most metrics accept `if:` for conditional statistics. **`gini`, `theil_index` and `nakamoto` do not.** `entropy` does. ```graphql buySkew: correlation(of: Amounts_Base, with: PriceInUsd, if: { Side: { is: "Buy" } }) ``` ## A concentration recipe Pool concentration for a token — how few pools carry the volume — in one request: ```graphql { Trading { Pairs( where: { Token: { Address: { is: "So11111111111111111111111111111111111111112" } } Market: { Network: { is: "Solana" } } Interval: { Time: { Duration: { eq: 3600 } } } Block: { Time: { since: "...", till: "..." } } } limit: { count: 1 } ) { pools: count(distinct: Market_Address) n51: nakamoto(of: Volume_Usd, ratio: 0.51) gini: gini(of: Volume_Usd) } } } ``` `n51` is the number of pools that together carry a majority of the volume; `gini` near 1 means one venue dominates. This is the closest the Trading cube comes to a liquidity metric — it carries no reserve or TVL data. ## Related - [Statistics metrics (general reference)](/docs/graphql/metrics/statistics/) - [Screeners with `selectWhere`](/docs/trading/query-operators/selectwhere-screeners/) - [Conditions with `if`](/docs/graphql/metrics/if/) --- ## TradingView Charts with Bitquery URL: https://docs.bitquery.io/docs/usecases/tradingview/tradingview/ Build Tradingview: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. Works with WebSocket live subscriptions. # Tutorial to build TradingView chart with real-time blockchain data (Streaming API version) We will be building the demo in React using the [lightweight-charts library](https://tradingview.github.io/lightweight-charts/). The chart is powered by the [Crypto Price API](/docs/trading/crypto-price-api/) (`Trading.Tokens`), which serves pre-aggregated, MEV-filtered OHLC with USD prices for the last ~30 days. > Building with the full **TradingView Advanced Charting library** instead? See the [advanced tutorial series](/docs/usecases/tradingview-subscription-realtime/getting-started). This is how it will look finally. ![Chart](/img/ApplicationExamples/tradingview.png) **Step 1: Set up your React Environment** Ensure you have a React application set up and ready for use. You should have a working React project with the necessary dependencies already installed. Create a project with ``` npx create-react-app demo ``` **Step 2: Import Dependencies** In your React component file, import the required dependencies at the beginning of your file. These include React, useState, useEffect, useRef, and the necessary charting library (in this case, `lightweight-charts`). ```javascript ``` **Step 3: Create the React Component** Create a React functional component for your TradingView chart. You can name it something like `TradingViewChart`. ```javascript export default function TradingViewChart() { // State and Ref Declarations const [resdata, setData] = useState([]); const chartContainerRef = useRef(); const chart = useRef(); // useEffect Hook useEffect(() => { // Initialize the TradingView chart chart.current = createChart(chartContainerRef.current, { // Chart configuration options // ... }); // Fetch and process data using the Streaming API const fetchData = async () => { // Fetch data from the API // ... if (response.status === 200) { // Process and format the data // ... // Create and populate candlestick and volume series // ... } else { console.log("error"); } }; fetchData(); }, []); return (

Trade Data

); } ``` **Step 4: Configure the Chart** In the `useEffect` hook, initialize the TradingView chart with the desired configuration options. Customize the chart layout, appearance, and any other settings based on your requirements. ```javascript chart.current = createChart(chartContainerRef.current, { width: chartContainerRef.current.clientWidth, height: chartContainerRef.current.clientHeight, layout: { backgroundColor: "#253248", textColor: "rgba(255, 255, 255, 0.9)", }, crosshair: { mode: CrosshairMode.Normal, }, rightPriceScale: { visible: false, }, leftPriceScale: { visible: true, }, timeScale: { borderColor: "#485c7b", }, }); ``` **Step 5: Fetch Data from the Streaming API** Create an `async` function named `fetchData` to fetch data from the Streaming API. You should use the `fetch` method to send a POST request to the API and retrieve the data. The query below gets 200 hourly candles of WETH/USD OHLC from the [Crypto Price API](/docs/trading/crypto-price-api/) (`Trading.Tokens`) — **pre-aggregated OHLC with USD prices and volume built in**, MEV/outlier-filtered, so no in-query aggregation or price derivation is needed. Change `Duration` for other intervals (1, 60, 300, 900, 3600 seconds, etc.), and swap the token address/network for any other token. For candles older than the Trading API's ~30-day window, drop to [`DEXTradeByTokens` aggregation](/docs/usecases/ohlcv-complete-guide/). ```javascript const fetchData = async () => { const response = await fetch("https://streaming.bitquery.io/graphql", { method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer YOUR_ACCESS_TOKEN", }, body: JSON.stringify({ query: ` { Trading { Tokens( where: { Token: {Address: {is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}, Network: {is: "Ethereum"}} Interval: {Time: {Duration: {eq: 3600}}} } orderBy: {ascending: Block_Time} limit: {count: 200} ) { Interval { Time { Start Duration } } Price { Ohlc { Open High Low Close } } Volume { Base Usd } } } } `, variables: "{}", }), }); // Process and populate the chart with the retrieved data // ... }; ``` **Step 6: Process and Populate Data** Within the `fetchData` function, process and format the retrieved data according to your needs. This includes extracting relevant information and populating the candlestick and volume series of the chart. ```javascript if (response.status === 200) { // Process and format the data const recddata = await response.json(); const responseData = recddata.data.Trading.Tokens; const extractedData = []; const extractedvol = []; responseData.forEach((record) => { // Extract necessary fields from Object const { Open: open, High: high, Low: low, Close: close } = record.Price.Ohlc; const recvol = parseFloat(record.Volume.Base); // lightweight-charts expects unix seconds for intraday candles const time = Math.floor(new Date(record.Interval.Time.Start).getTime() / 1000); const extractedItem = { open: open, high: high, low: low, close: close, time: time, }; // Push the extracted object to the extractedData array extractedData.push(extractedItem); const extractvol = { value: recvol, time: time, }; extractedvol.push(extractvol); }); // Create candlestick and volume series on the chart // ... const candlestickSeries = chart.current.addCandlestickSeries({ upColor: "#008000", downColor: "#FF0000", borderDownColor: "#FF0000", borderUpColor: "#008000", wickDownColor: "#FF0000", wickUpColor: "#f2e9e9", }); candlestickSeries.setData(extractedData); const volumeSeries = chart.current.addHistogramSeries({ priceFormat: { type: 'volume', }, scaleMargins: { top: 0.8, bottom: 0, }, overlay: true, priceScaleId: '', color:"#f4cccc" }); volumeSeries.setData(extractedvol); } else { console.log("error"); } ``` In this step, we format the data making it suitable for chart creation. The snippet reads the pre-aggregated `Open`, `High`, `Low`, `Close` values straight off `Price.Ohlc` (no aggregation needed) and converts the interval start time to the unix-seconds format lightweight-charts expects for intraday candles. ```javascript const { Open: open, High: high, Low: low, Close: close } = record.Price.Ohlc; const recvol = parseFloat(record.Volume.Base); const time = Math.floor(new Date(record.Interval.Time.Start).getTime() / 1000); const extractedItem = { open: open, high: high, low: low, close: close, time: time, }; ``` **Step 7: Render the Chart** Render the TradingView chart within your React component by returning the chart container `div` inside the component's JSX. ```javascript return (

Trade Data

); ``` **Step 8: Customize Further** Customize the chart appearance, colors, and layout to meet your specific needs by adjusting the configuration options and series settings in the `createChart` and data population sections of your code. That's it! You now have a React component that plots a TradingView chart using the Streaming API. You can find the complete code [here](https://github.com/bitquery/tradingview-react-v2-example). Note: the repo may still show the older `DEXTradeByTokens` aggregation query — the `Trading.Tokens` query above is the current recommended data source; only the query string and the field extraction differ. --- ## TradingView Realtime Starter Guide URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/getting-started/ Build Getting Started: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. Keep queries fast with indexed filters. # TradingView API - Real-Time Crypto OHLC Stream This guide is the **entry point** for the tutorial: how to embed [TradingView Advanced Charts](https://in.tradingview.com/advanced-charts/) in a React app and drive the chart with **Bitquery**—historical OHLC over HTTPS and **live** OHLC over a GraphQL WebSocket subscription. If you want this without building the datafeed yourself, the [TradingView DEX charts](https://bitquery.io/products/tradingview-dex) page covers our ready UDF datafeed for any token. **Complete reference implementation:** [github.com/bitquery/tradingview-subscription-realtime](https://github.com/bitquery/tradingview-subscription-realtime/tree/main) **Prefer a package?** There is also an [**npm SDK** (`@bitquery/tradingview-sdk`)](https://www.npmjs.com/package/@bitquery/tradingview-sdk) if you want a higher-level integration; this documentation still helps you understand how the pieces fit together. The chart loads history first, then extends the last candle as new OHLC arrives: ## If this is your first time here You do **not** need to read everything on this page before coding. Skim the [tutorial order](#tutorial-order-follow-these-pages), complete the [checklist](#checklist-before-you-open-your-editor), then open the first technical page: [Getting Historical Data](/docs/usecases/tradingview-subscription-realtime/historical_OHLC/). Come back here when something is unclear—especially [Key concepts](#key-concepts), [Architecture](#architecture-overview), or [When something goes wrong](#when-something-goes-wrong). ## Checklist before you open your editor {#checklist-before-you-open-your-editor} Before you open [Getting Historical Data](/docs/usecases/tradingview-subscription-realtime/historical_OHLC/), confirm you have: - A **Bitquery** account and **OAuth** token with streaming access ([how to generate a token](/docs/authorization/how-to-generate/)). - **TradingView Advanced Charts** approved and the `charting_library` files available locally (see [Prerequisites](#prerequisites)). - **Node.js 16+** installed. Optional: clone the [reference implementation](#clone-the-reference-repo-fastest) so you can compare your code with a working app. ## What you will build By the end of the series you will have a **small React application** that: - Renders a **TradingView Advanced Chart** (candlesticks, timeframes, drawing tools—whatever your TradingView license includes). - Implements a **custom datafeed**: TradingView calls your code to load history and to subscribe to live updates. - Fetches **historical OHLC** from Bitquery’s **Crypto Price API** (GraphQL over HTTP). - Subscribes to **pre-aggregated OHLC** on a **WebSocket** so the chart updates without refreshing the page. - Optionally normalizes **bar continuity** so adjacent candles meet cleanly on the chart ([why and how](/docs/usecases/tradingview-subscription-realtime/bar-continuity/)). ## Tutorial order (follow these pages) The sidebar matches this sequence. Each page builds on the previous one. | Step | Page | What you do there | | ---- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | 1 | **Getting started** (this page) | Context, setup, links | | 2 | [Getting Historical Data](/docs/usecases/tradingview-subscription-realtime/historical_OHLC/) | HTTP GraphQL query, map rows to TradingView bars, sort, optional padding, `connectBarContinuity` | | 3 | [Bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/) | Conceptual overview of stitching OHLC (historical + live) | | 4 | [Fetching Real-time OHLC](/docs/usecases/tradingview-subscription-realtime/realtime_OHLC/) | `graphql-ws` subscription, live bar updates, continuity across candle boundaries | | 5 | [Custom DataFeed Setup](/docs/usecases/tradingview-subscription-realtime/custom_datafeed/) | Wire history + stream into TradingView’s `getBars` / `subscribeBars` contract | | 6 | [Widget Creation](/docs/usecases/tradingview-subscription-realtime/widget/) | `TVChartContainer`, load the charting library, pass your datafeed | | 7 | [Setting Up `App.js`](/docs/usecases/tradingview-subscription-realtime/final-step/) | Mount the widget and run the app | If you only want the **idea** of continuity, read [Bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/) after historical data; the historical page already shows the code. ## Key concepts ### TradingView: widget + datafeed - **Charting Library (Advanced Charts)** is a **browser JavaScript** product. You host it inside your app (it is not a public CDN script you hotlink without a license). - Your app creates a **widget** and hands it a **datafeed** object. TradingView then calls **your** functions, for example: - “Give me bars from time A to B” → you return OHLCV arrays. - “Subscribe to updates for this symbol/resolution” → you push new or updated bars when the market moves. You are responsible for **fetching** that data; Bitquery is the backend in this tutorial. ### Bitquery: one API, two transports - **Historical:** `POST` a GraphQL query to Bitquery’s HTTP endpoint (this tutorial uses patterns aligned with the [Crypto Price API](/docs/trading/crypto-price-api/)). - **Real-time:** open a **WebSocket** connection and run a GraphQL **subscription**. Bitquery pushes new OHLC as it is finalized (for example per interval). Same schema family conceptually; different mechanics than REST polling. ### OHLC and “bars” Each **bar** (candle) has: - **time** — start of the period in **milliseconds** (Unix epoch), as TradingView expects in the UDF-style APIs used by custom datafeeds. - **open, high, low, close** — prices for the interval. - **volume** — base or quote volume depending on your query (stay consistent). Aggregated OHLC from any provider can have **open ≠ previous close**. This tutorial includes **continuity** helpers so the chart still **looks** continuous; see [Bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/). --- ## Prerequisites ### Must have - **Node.js 16+** (18 LTS is a safe choice). - A **Bitquery account** and an **OAuth token** with access to streaming (see [authorization](/docs/authorization/how-to-generate/)). - **TradingView Advanced Charts** access: you must **apply** and receive their library; it is not optional for this integration path. ## Architecture overview At runtime, the pieces interact like this: ![TradingView integration flowchart](/img/diagrams/tradingview_flowchart.png) 1. **First paint:** the widget asks the datafeed for **history**; your code calls Bitquery over **HTTPS**, maps the result to bars, and returns them to TradingView. 2. **Live:** the widget **subscribes**; your code opens a **WebSocket** subscription, receives each new bar, and forwards it to TradingView’s callback so the chart updates. --- ## Clone the reference repo (fastest) {#clone-the-reference-repo-fastest} 1. Clone [tradingview-subscription-realtime](https://github.com/bitquery/tradingview-subscription-realtime). 2. Add your **TradingView** `charting_library` (and any required folders) where the project expects them—see the repo README and [Widget Creation](/docs/usecases/tradingview-subscription-realtime/widget/). 3. Add your Bitquery token (see configuration below). 4. `npm install` and `npm start`. ## Installation and project layout ### Create the React app ```bash npx create-react-app tradingview-crypto-charts cd tradingview-crypto-charts ``` ### Install npm dependencies The tutorial code uses **HTTP** requests for history and **`graphql-ws`** for subscriptions (same as the reference repo): ```bash npm install axios graphql-ws ``` ### Add TradingView’s library After TradingView grants access, add their **`charting_library`** folder to your project (exact path is up to you, but it must match your `import` in the [widget](/docs/usecases/tradingview-subscription-realtime/widget/) page—typically under `src/` or `public/`). If your bundle includes a **`datafeeds`** folder, add it as well when the TradingView docs for your version require it. ### Configuration: tokens and endpoints The step-by-step pages use a **`configs.json`** file (for example in `src/`) to hold `authtoken`, matching the public GitHub tutorial style: ```json { "authtoken": "YOUR_BITQUERY_OAUTH_TOKEN" } ``` **Important:** - Add `configs.json` to **`.gitignore`** (or use env vars and never commit secrets). - Your organization may use a different host or path (for example Enterprise endpoints). Use the URLs shown in your Bitquery dashboard; the tutorial often shows `https://streaming.bitquery.io/...` and `wss://streaming.bitquery.io/...` patterns. **Alternative:** Create React App supports `REACT_APP_*` variables in a `.env` file if you prefer not to use JSON; you would then read `process.env.REACT_APP_BITQUERY_OAUTH_TOKEN` in your modules instead of importing `configs.json`. --- ## Supported networks (high level) Bitquery’s **Crypto Price** and related trading APIs cover many chains; the exact list changes over time. Examples you will see in docs and IDE: - **EVM:** Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, and other EVM networks exposed in the schema. - **Solana:** Raydium, Orca, Pumpfun, PumpSwap, and other Solana DEXs where the API exposes them. - **Other:** Tron and additional networks as listed in the current schema. Always confirm your **network name** and **token address** format in the [IDE schema explorer](https://ide.bitquery.io) or main [Crypto Price API](/docs/trading/crypto-price-api/) documentation before shipping. --- ## Queries and streams to try first Test these in [Bitquery IDE](https://ide.bitquery.io) **before** you paste them into JavaScript: - [Historical OHLC (example)](https://ide.bitquery.io/Historical-price-data) - [Real-time OHLC stream (example)](https://ide.bitquery.io/1-second-crypto-price-stream) **Suggested workflow:** change **network**, **token address**, and **interval** in the IDE until the shape of the response matches what you expect; only then embed the query in your app. --- ## Key features (summary) ### Real-time streaming - Pre-aggregated OHLC over a **GraphQL subscription** (low-latency updates compared to polling). - You control **which token and interval** you subscribe to in the `where` clause. ### Historical data - Load a window of bars for the user’s visible range; TradingView may request more as they scroll. - Aggregated OHLC reduces the work you would otherwise do from raw trades. ### Data quality notes - **Bar continuity** — optional normalization so candles meet visually ([Bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/)). - **DEX aggregation** — Bitquery’s price products combine liquidity across venues; see the [Crypto Price API](/docs/trading/crypto-price-api/) docs for methodology and limits. --- ## When something goes wrong | Symptom | What to check | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Blank chart, no errors | TradingView library path wrong or widget not mounted; browser console for failed script loads. | | `401` / unauthorized from Bitquery | OAuth token missing, expired, or wrong header; Enterprise vs public endpoint mismatch. | | History loads, live never updates | WebSocket URL or token query param; subscription `where` clause too narrow; firewall blocking `wss://`. | | Gaps between candles | Expected for raw aggregated OHLC; implement [Bar continuity](/docs/usecases/tradingview-subscription-realtime/bar-continuity/) if you want visual stitching. | | “TradingView is undefined” | Import path to `charting_library` does not match where you copied the files. | For Bitquery-specific errors, see [support](#community-and-support). --- ## Community and support {#community-and-support} - **Telegram:** [Bitquery Developers](https://t.me/Bloxy_info) - **Email:** support@bitquery.io --- ## Next step Open **[Getting Historical Data](/docs/usecases/tradingview-subscription-realtime/historical_OHLC/)** and create `histOHLC.js`: that file is the foundation for everything the chart paints before the WebSocket connects. --- ## Train a Wash Trading Model URL: https://docs.bitquery.io/docs/usecases/wash-trading-detector/training/ Build Training: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. Includes filters and field selection tips. # Model Training This script handles the entire training pipeline for detecting wash trades using an XGBoost classifier. It combines real trade data from Bitquery, rule-based labeling, feature preprocessing, model training, evaluation, and model serialization. ## Code Breakdown ### Imports ```py from get_data import get_trades from label import label_trades from xgboost import XGBClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report ``` ### Data Processing The code snippet given below fetch live DEX trade data from Bitquery, apply rule-based labeling and modify the labelled dataframe to prepare datasets for training the model. Here: - `X` = All the features. - `Y` = Binary label indicating whether the trade is suspicious. Finally the model features are stored in a `JSON` list for consistent preprocessing during inference. ```py trade_data = get_trades() df = label_trades(trade_data) for col in df.columns: if df[col].dtype == 'object' and col != 'is_wash_trade': df[col] = df[col].astype('category') X = df.drop(columns=["is_wash_trade"]) y = df["is_wash_trade"] with open("model_features.json", "w") as f: json.dump(X.columns.tolist(), f) ``` ### Split Dataset for Training and Testing This splits the data into `80%` for training and `20%` for testing, using a fixed random seed for reproducibility. ```py X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) ``` ### Train XGBoost Classifier The model is trained on Bitquery DEX trades data in the code given below. ```py model = XGBClassifier(enable_categorical=True, tree_method='hist') model.fit(X_train, y_train) ``` Notes: - `enable_categorical`=True allows XGBoost to natively handle categorical features. - `tree_method`='hist' improves training speed. ### Save the Trained Model The trained model is stored in a pickle file with `.pkl` extension and will be later loaded in app.py for inference. ```py with open("xgb_wash_model.pkl", "wb") as f: pickle.dump(model, f) ``` ### Evaluate Model The code snippet below, prints performance metrics like precision, recall, F1-score for both wash and non-wash trades. ```py y_pred = model.predict(X_test) print(classification_report(y_test, y_pred)) ``` --- ## USDT Stablecoin API URL: https://docs.bitquery.io/docs/stablecoin-APIs/usdt-api/ Query USDT transfers, balances, and supply activity with Bitquery stablecoin APIs using GraphQL examples across major blockchains. # USDT API USDT (Tether) powers a large share of on-chain value transfer, payments, and trading across multiple blockchains. This page curates the most useful USDT APIs—covering price, payments (transfers), trades, reserves, and balances—along with live streams you can use in production. Most USDT volume lives on Tron — the [Tron USDT API](https://bitquery.io/blockchains/tron-blockchain-api) page covers tracing and streaming every TRC-20 transfer. Use the sections below to discover key USDT datasets, with both API and streaming options. Links point to runnable examples, and code blocks are provided as placeholders for your queries. ## USDT Price API Get real-time and historical USDT prices, OHLCV, and moving averages across supported networks and markets. 🔗 [Stream Example](https://ide.bitquery.io/stablecoin-price-stream-of-USDT_2) 🔗 [API Example](https://ide.bitquery.io/stablecoin-price-query-of-USDT_1) ```graphql subscription { Trading { Tokens( where: {Interval: {Time: {Duration: {eq: 1}}}, Currency: {Id: {is: "usdt"}}} ) { Currency{ Id } Token { Address Id IsNative Name Network Name Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } } } } ``` ```graphql query { Trading { Tokens( limit:{count:10} orderBy:{descending:Block_Time} where: {Interval: {Time: {Duration: {eq: 1}}}, Currency: {Id: {is: "usdt"}}} ) { Currency{ Id } Token { Address Id IsNative Name Network Name Symbol TokenId } Block { Date Time Timestamp } Interval { Time { Start Duration End } } Volume { Base Quote Usd } Price { IsQuotedInUsd Ohlc { Close High Low Open } Average { ExponentialMoving Mean SimpleMoving WeightedSimpleMoving } } } } } ``` ## USDT Payments API Track live USDT stablecoin transfers. USDT is ideal for payments, settlements, etc and you can track those in real-time using this API/Stream - 🔗 [Stream Example](https://ide.bitquery.io/USDT-token-Transfers-stream-on-solana) 🔗 [API Example](https://ide.bitquery.io/USDT-token-Transfers-api-on-solana) ```graphql subscription { Solana { Transfers( where: {Transfer: {Currency: {MintAddress: {in: ["Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"]}}}} ) { Transfer { Amount AmountInUSD Sender { Address Owner } Receiver { Address Owner } Currency { Symbol Name MintAddress } } Instruction { Program { Method } } Block { Time Height Slot } Transaction { Signature Signer Fee FeeInUSD FeePayer } } } } ``` ```graphql { Solana { Transfers( orderBy: {descending: Block_Time} limit: {count: 100} where: {Transfer: {Currency: {MintAddress: {is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}} ) { Transfer { Amount AmountInUSD Sender { Address Owner } Receiver { Address Owner } Currency { Symbol Name MintAddress } } Instruction { Program { Method } } Block { Time Height Slot } Transaction { Signature Signer Fee FeeInUSD FeePayer } } } } ``` ## USDT Trades API Analyze USDT trading activity on DEXs. Below example is to track USDT trading activity on Solana. 🔗 [Stream Example](https://ide.bitquery.io/solana-trades-subscription_10_1) 🔗 [API Example](https://ide.bitquery.io/solana-USDT-trades-query) ```graphql subscription { Solana { DEXTrades (where:{any:[{Trade:{Buy:{Currency:{MintAddress:{is:"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}}},{Trade:{Sell:{Currency:{MintAddress:{is:"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}}}]}){ Block{ Time Slot } Transaction{ Signature Index Result{ Success } } Trade { Index Dex { ProgramAddress ProtocolFamily ProtocolName } Buy { Amount Account { Address } Currency { MetadataAddress Key MintAddress IsMutable EditionNonce Decimals CollectionAddress Fungible Symbol Native Name } Price PriceInUSD Order { LimitPrice LimitAmount OrderId } } Market { MarketAddress } Sell { Account { Address } Currency { IsMutable Decimals CollectionAddress Fungible Symbol Native Name } Price PriceInUSD } } } } } ``` ```graphql query { Solana { DEXTrades ( limit:{count:100} orderBy:{descending:Block_Time} where:{any:[{Trade:{Buy:{Currency:{MintAddress:{is:"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}}},{Trade:{Sell:{Currency:{MintAddress:{is:"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}}}]}){ Block{ Time Slot } Transaction{ Signature Index Result{ Success } } Trade { Index Dex { ProgramAddress ProtocolFamily ProtocolName } Buy { Amount Account { Address } Currency { MetadataAddress Key MintAddress IsMutable EditionNonce Decimals CollectionAddress Fungible Symbol Native Name } Price PriceInUSD Order { LimitPrice LimitAmount OrderId } } Market { MarketAddress } Sell { Account { Address } Currency { IsMutable Decimals CollectionAddress Fungible Symbol Native Name } Price PriceInUSD } } } } } ``` ## USDT Reserve API Monitor USDT reserve or get lateast reserve value on Solana using below Stream/API. 🔗 [Stream Example](https://ide.bitquery.io/USDT-Stablecoin-reserves-on-Solana) 🔗 [API Example](https://ide.bitquery.io/USDT-Stablecoin-reserves-on-Solana--query) ```graphql subscription{ Solana { TokenSupplyUpdates( where: {TokenSupplyUpdate: {Currency: {MintAddress: {is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}} ) { TokenSupplyUpdate { Amount Currency { MintAddress Name } PreBalance PostBalance } } } } ``` ```graphql { Solana { TokenSupplyUpdates( limit:{count:1} orderBy:{descending:Block_Time} where: {TokenSupplyUpdate: {Currency: {MintAddress: {is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}} ) { TokenSupplyUpdate { Amount Currency { MintAddress Name } PreBalance PostBalance } } } } ``` ## USDT Balance API Query USDT holders, balances over time, and distribution metrics (e.g., whales, concentration, first-time receivers). Great for compliance, growth, and analytics. ### USDT Balance of an address 🔗 [API Example](https://ide.bitquery.io/USDT-balance-of-an-address) ```graphql query MyQuery { Solana { BalanceUpdates( where: {BalanceUpdate: {Account: {Owner: {is: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"}}, Currency: {MintAddress: {is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}} orderBy: {descendingByField: "BalanceUpdate_Balance_maximum"} ) { BalanceUpdate { Balance: PostBalance(maximum: Block_Slot) Currency { Name Symbol } } } } } ``` ### USDT Top Holders 🔗 [API Example](https://ide.bitquery.io/top-100-holders-of-USDT-token-on-Solana_1) ```graphql query MyQuery { Solana(dataset: realtime, network: solana, aggregates: yes) { BalanceUpdates( limit:{count:100} orderBy: {descendingByField: "BalanceUpdate_Holding_maximum"} where: {BalanceUpdate: {Currency: {MintAddress: {is: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"}}}, Transaction: {Result: {Success: true}}} ) { BalanceUpdate { Currency { Name MintAddress Symbol } Account { Address } Holding: PostBalance(maximum: Block_Slot, selectWhere: {gt: "0"}) } } } } ``` --- ### Notes & Best Practices - Prefer subscriptions (streams) for real-time detection (payments, trades). - Use date ranges and pagination for historical analyses at scale. - Join across entities (holders, transfers, trades) to build richer analytics. - For multi-chain setups, run identical queries across networks and unify downstream. - `Tron` and `EVM` are separate top-level selectors, so you can alias several in **one** request and compare a chain-by-chain breakdown without multiple round trips — see [USDT across chains in one request](/docs/blockchain/Tron/usdt-trc20-api#cross-chain). ### Chain-specific USDT pages - [TRC20 USDT API (Tron)](/docs/blockchain/Tron/usdt-trc20-api) — Tron carries the largest share of USDT transfer activity: live transfers, whale holders, exchange deposit flows and mempool visibility. Need help crafting a query or subscription? Message us on [support](https://t.me/Bloxy_info). --- ## Use Regular Expressions To Search Solana Logs URL: https://docs.bitquery.io/docs/API-Blog/use-regular-expressions-to-search-solana-logs/ Solana log search with regular expressions: filter token mint and burn events, find programs that paid royalties, and track trades on an unknown exchange. # How to Filter Events from Solana Logs Using Bitquery APIs and Regular Expressions In this article, we are going to understand how we can track events like token lock and burn, which are necessary for investors and project teams in the quickly changing crypto space. Token lockups and token burning provide the project with stability and confidence by restricting token sales or transfers for a certain amount of time and reducing the overall availability. The sudden release of a large number of tokens, however, may result in market instability. Tracking these occurrences allows stakeholders to anticipate market movements and make informed decisions. Anyone can follow this guide to understand how to use Bitquery APIs to track vested tokens and receive alerts when unlocking events are about to occur. ## ​Understanding Token Minting and Burning ### What is Token Burn ? Token burning is the process of permanently removing tokens from circulation, reducing the total supply. This mechanism is employed for several reasons. - Supply Control: By burning tokens, projects can manage and control the token supply, potentially increasing the value of remaining tokens. This is akin to a share buyback in traditional finance, aimed at benefiting holders by reducing supply. - Economic Stability: Token burning can help stabilize the token's price by creating scarcity. It can also be used as a method to combat inflation within the ecosystem, ensuring long-term sustainability. ### Why Track Token Mint and Burn?​ Tracking token minting and burning is crucial for: - Investors: Understanding when new tokens are minted or burned helps investors anticipate changes in supply that could impact token prices. This knowledge can guide their investment decisions and trading strategies. - Project Teams: Monitoring these processes ensures transparency and accountability. Clear communication about minting and burning schedules builds trust with the community and stakeholders, showcasing a commitment to sustainable and responsible token management. ## Key Data Points to Track​ When tracking token mint and burn events, the following data points are essential: - Token Name and Address: Identify the token in question. - Method Called: The method called from the token. - Transaction Signer: The address that called the method in question. - TimeStamp: UTC Time at which the method is called. - Transaction Signature: Transaction signature to double check the results. ## Minting Token Method In decentralized finance (DeFi), tokens are minted to reward participants within the network, to encourage the continued participation and support in the network's growth and security. ### Why It's Important​ Projects often mint tokens to raise capital during initial coin offerings (ICOs) or token sales. These funds are typically used for further development, marketing, and expanding the project's reach. ### How to Track​ Use Bitquery APIs to see when methods that include “mint” are called from the Solana Instruction Logs. This can indicate important moves by project teams or big investors. By monitoring these events, you can gain insights into market behavior and anticipate potential price changes. ### Example Query​ To track token minting, you can use a query shared below. Here’s a sample query to track [token minting](https://ide.bitquery.io/MInt-Token-Tracking-on-Solana-Logs) on the Solana network: ```gql subscription{ Solana { Instructions( where: {Instruction: {Program: {Method: {includes: "mint"}}}} ) { Transaction { Signature Signer } Block { Time } Instruction { Logs Program { Method Name Address } } } } } ``` This query helps identify token minting events, providing insights into market dynamics. ## Token Burn Method In decentralized finance (DeFi), tokens are burnt to manage and control the token supply,potentially increasing the value of remaining tokens. ### How to Track​ Use Bitquery APIs to see when methods that include “burn” are called from the Solana Instruction Logs. This can indicate important moves by project teams or big investors. By monitoring these events, you can gain insights into market behavior and anticipate potential price changes. ### Example Query​ To track token liquidity burn, you can use a query shared below. Here’s a sample query to track [token LP burn](https://ide.bitquery.io/Copy-of-Copy-of-Burn-Token-Tracking-on-Solana-Logs) on the Solana network: ```gql { Solana(network: solana) { Instructions( where: {Instruction: {Program: {Method: {includes: "burn"}}}} limit: {count: 10} orderBy: {descending: Block_Time} ) { Transaction { Signature Signer } Block { Time } Instruction { Logs Program { Method Name Address } Accounts { Address Token { Mint } } } } } } ``` This query helps identify token burning events, providing insights about the project team's next move and the progress of the project. ## Check the Programs with Paid Royalty Set up a query to filter out the programs where some non-zero [royalty is paid](https://ide.bitquery.io/Query-solana-logs) with the help of regular expressions like “includes” and “not includes”. For example: ```gql query { Solana { Instructions(where: { Instruction: { Logs: { includes: { includes: "royalty_paid" notIncludes: "\"royalty_paid\":0" } } } }) { Transaction { Signature } Instruction { Logs } } } } ``` This query returns instructions that signal non-zero royalty payment, providing details about transaction hash and instructions logs. ## Tracking trades on an Exchange without knowing anything about it​ If you need to filter out the instructions from Solana logs that involve a particular exchange but you don’t have any information, like address and protocol, then you can use the “includes” keyword on Logs. For example, you can run this query to get the [Solana Zeta Market Logs](https://ide.bitquery.io/Solana-Zeta-Market-logs). ```gql query { Solana { Instructions( where: {Instruction: {Logs: {includes: {includes: "ZETA"}}}} limit: {count: 10} ) { Transaction { Signature } Instruction { Logs } } } } ``` This query will return the Solana Instructions Logs and transaction signature to double check the results and get more info. --- ## Wallet Overlap with array_intersect URL: https://docs.bitquery.io/docs/trading/query-operators/array-intersect-wallet-overlap/ Find the wallets that traded every token in a set, in one request, using array_intersect on the Bitquery Trading Trades cube. # Wallet Overlap with `array_intersect` `array_intersect` answers one question well: **which entities appear against every member of a set?** The common form is "which wallets traded all of these tokens" — a single request, no client-side joins, and the result is exact. ## Trades only `array_intersect` exists on all four Trading cubes, but wallet overlap needs `Trader_Address`, and that field exists **only** in `Trading_Trade_StringFields`. The other three cubes have no trader axis at all: | Enum | Contains `Trader_Address`? | | --- | --- | | `Trading_Trade_StringFields` | **yes** — also the only enum with `TransactionHeader_Sender` / `_FeePayer` / `_To` / `_Hash` | | `Trading_Pair_StringFields` | no | | `Trading_Token_StringFields` | no | | `Trading_Currency_StringFields` | no | `side1` and `side2` are non-null enums, so naming a field the cube does not have is a schema error rather than a silently wrong answer: ``` Argument "side1" has invalid value Trader_Address. Expected type "Trading_Pair_StringFields" ``` ## The query Wallets that traded **both** WSOL and USDC on Solana in a window: ```graphql { Trading { Trades( where: { Pair: { Market: { Network: { is: "Solana" } } Token: { Id: { in: [ "bid:solana:So11111111111111111111111111111111111111112", "bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] } } } Block: { Time: { since: "2026-09-01T12:00:00Z", till: "2026-09-01T12:01:00Z" } } } limit: { count: 1 } ) { overlap: array_intersect( side1: Trader_Address side2: Pair_Token_Id intersectWith: [ "bid:solana:So11111111111111111111111111111111111111112", "bid:solana:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" ] ) n: count } } } ``` You get one row containing an array of the wallet addresses that traded **every** listed token. The same shape works on Ethereum, BSC and Base — swap the network and the token ids. **Verify it yourself**, rather than trusting the array: pull the raw trades for the same window, bucket `Trader.Address` by `Pair.Token.Id`, and intersect the buckets in your client. The result is set-equal. ## How it works Understanding the mechanism explains every surprise below. For each value `v` in `intersectWith`, the engine builds: ```sql groupUniqArrayIf(if(side1 = v, side2, side1), side1 = v OR side2 = v) ``` — that is, **the other side of every row where `v` appears on either side** — and then intersects those per-value sets. Two consequences worth internalising: - It is not "collect `side2` where `side1 = v`". A row matches when `v` is on **either** side, and the value contributed is whichever side `v` is not on. - `side1` and `side2` are therefore **symmetric**: swapping them returns the same set. This follows from the SQL, not just from testing. ## Exact, and not truncated The array is built with `groupUniqArray`, so it is an **exact** distinct set, and it is returned whole in one array rather than paged. :::tip Prefer `array_intersect` over `uniq` when the two disagree `uniq()` switches to an approximate estimator above **65,536 (2^16)** distinct values, so on large windows `uniq(of: Trader_Address)` and a single-member `array_intersect` will differ slightly. `array_intersect` is the exact side. If you need an exact distinct count on a large population, take the array's length. ::: ## Gotchas | Behaviour | What happens | What to do | | --- | --- | --- | | `where` excludes an `intersectWith` member | Silent empty array — populated row, `count` non-zero, HTTP 200, no error | Make the `where` clause span **every** member you intersect on | | Empty or omitted `intersectWith` | Malformed SQL: `Code: 47. DB::Exception: Missing columns: 'AS'` | Always pass a non-empty list; the schema does not enforce it | | `selectWhere` on the result | Every operator fails with `go-sqlbuilder: unsupported args when interpolating` | You **cannot** filter for "intersection is non-empty" server-side — filter in your client | | `orderBy` on the alias | Sorts arrays **elementwise (lexicographic)**, not by length | Sort by length in your client | | Element order | Not stable — reversing the `intersectWith` order returns the same set in a different order | Treat the result as a set and sort client-side | | Used in a subscription | Absent from all four stream cubes | Query only — see [what does not survive streaming](/docs/subscriptions/what-does-not-survive-streaming/) | ## Beyond wallets `side1` / `side2` accept any string field, so the same operator answers other "appears against every member" questions — for example which DEX venues quote every token in a basket, using `Pair_Market_Name` and `Pair_Token_Symbol`. ## Related - [Array Intersection (general reference)](/docs/graphql/capabilities/array-intersect/) - [Crypto Trades API](/docs/trading/crypto-trades-api/trades-api/) - [Traders API](/docs/trading/crypto-trades-api/traders-api/) --- ## Wallet PnL: Compute Realized & Unrealized Profit URL: https://docs.bitquery.io/docs/trading/crypto-trades-api/wallet-pnl/ Compute a wallet's realized and unrealized PnL from Bitquery trade history using the Trades API, with an average-cost walkthrough. # Wallet PnL: Realized & Unrealized Profit Bitquery doesn't return a single "PnL" number — you compute it from a wallet's **trade history** plus the token's **current price**. This recipe shows the average-cost approach that most trackers use. It works on every chain the Trades API covers, including the Robinhood chain. ## 1. Pull the wallet's trades Open the [Bitquery IDE](https://ide.bitquery.io/) and run a Trades query filtered to the wallet, ordered by time. This returns each buy/sell with amount, price, and side — the raw material for PnL. ```graphql { Trading { Trades( where: { Trade: { Account: { Address: { is: "" } } } } orderBy: { ascending: Block_Time } limit: { count: 10000 } ) { Block { Time } Trade { Side { Type } Amount Price Currency { Symbol Address } } } } } ``` The `Side.Type` (buy/sell), `Amount`, and `Price` on each row are what you fold into a running position. Note the [Trades API retention window](/docs/graphql/data-coverage-retention/) — for deep history beyond it, use a [cloud/S3 export](/docs/cloud/). ## 2. Fold trades into a position (average cost) For each token, walk trades in time order: - **Buy:** increase quantity; increase cost basis by `amount × price`. - **Sell:** realized PnL += `amount × (sell price − average cost)`; reduce quantity and cost basis proportionally. - **Average cost** = running cost basis ÷ running quantity. ```python # trades: list of {side, amount, price} in time order, per token qty = 0.0 cost = 0.0 # total cost basis of the open position realized = 0.0 for t in trades: if t["side"] == "buy": qty += t["amount"] cost += t["amount"] * t["price"] else: # sell avg = cost / qty if qty else 0.0 realized += t["amount"] * (t["price"] - avg) cost -= t["amount"] * avg qty -= t["amount"] avg_cost = cost / qty if qty else 0.0 # unrealized needs the current price (see step 3) ``` ## 3. Add unrealized PnL Fetch the token's **current price** from the [Crypto Price API](/docs/trading/crypto-price-api/), then: ```text unrealized = open_quantity × (current_price − average_cost) total_pnl = realized + unrealized ``` ## Caveats - **Price source matters.** Use a consistent price feed (the same one you'd display) — the token-level price index is MEV/outlier-filtered; see [which price to use](/docs/trading/crypto-price-api/price-index-algorithm/). - **Fees & gas** aren't included above — subtract them if you need net PnL. - **Method choice** (average cost vs FIFO) changes realized PnL; pick one and be consistent. ## Next steps - [Trades API](/docs/trading/crypto-trades-api/trades-api/) - [Crypto Price API](/docs/trading/crypto-price-api/) - [Data Coverage & Retention](/docs/graphql/data-coverage-retention/) --- ## Wallet Portfolio API - What an Address Holds URL: https://docs.bitquery.io/docs/usecases/wallet-portfolio-api/ Get every token a wallet holds with USD value, on EVM, Tron and Solana. Covers dormancy metadata, dated snapshots and the aggregation limits of the Balances cube. # Wallet portfolio API "What does this address hold, and what is it worth?" On EVM and Tron that is one query against the `Balances` cube. On Solana it takes a slightly different shape, because Solana has no `Balances` cube. For *realised profit and loss* on trades rather than current holdings, see [Build your own crypto P&L calculator](/docs/usecases/p-l-product/overview/). ## EVM: every token an address holds ```graphql query WalletPortfolio { EVM(network: eth) { Balances( where: { Balance: { Address: { is: "0x28c6c06298d514db089934071355e5743bf21d60" } } } orderBy: { descending: Balance_AmountInUSD } limit: { count: 50 } ) { Balance { Amount AmountInUSD LastChangeTime UpdateCount } Currency { Symbol Name SmartContract Decimals } } } } ``` One row per token, already valued. `Balances` reads from an aggregate-state table, so you are not summing a history of changes to get here. Switch `network` for other EVM chains, and use `Tron { Balances(...) }` for Tron with the same shape. :::caution `Balances` has no `sum`, so total portfolio value is client-side The cube exposes only `count`, `uniq` and `calculate`. There is no `sum`, so this fails: ```graphql Balances(where: {...}) { totalUsd: sum(of: Balance_AmountInUSD) } # Cannot query field "sum" ``` Fetch the rows and add `AmountInUSD` in your own code. The `Holders` cube *does* support the full aggregate set, including `sum` — see [holder concentration](#holder-concentration) below. ::: ### Filter out dust and zero balances Non-zero filtering goes on the **field**, with `selectWhere`, not in the `where` block: ```graphql query NonZeroHoldings { EVM(network: eth) { Balances( where: { Balance: { Address: { is: "0x28c6c06298d514db089934071355e5743bf21d60" } } } orderBy: { descending: Balance_AmountInUSD } limit: { count: 50 } ) { Balance { Amount(selectWhere: { gt: "0" }) AmountInUSD } Currency { Symbol } } } } ``` Putting `Amount: { gt: "0" }` inside `where` silently returns nothing useful. This trips people up often enough to be worth stating twice. To drop dust by value rather than by amount, apply `selectWhere` to `AmountInUSD` instead. ## Position age and dormancy `Balances` carries three fields that turn a portfolio into an activity profile, and they cost nothing extra: | Field | What it tells you | |---|---| | `FirstChangeTime` | when the address first touched this token | | `LastChangeTime` | when it last moved | | `UpdateCount` | how many balance changes there have been | ```graphql query DormantPositions { EVM(network: eth) { Balances( where: { Balance: { Address: { is: "0x28c6c06298d514db089934071355e5743bf21d60" } } } orderBy: { ascending: Balance_LastChangeTime } limit: { count: 25 } ) { Balance { AmountInUSD FirstChangeTime LastChangeTime UpdateCount } Currency { Symbol } } } } ``` Ordering by `LastChangeTime` ascending puts the most dormant positions first. A high `UpdateCount` with a recent `LastChangeTime` is an operational wallet; a single update years ago is an abandoned or airdropped position. ## Solana Solana has no `Balances` cube. Take the most recent balance update per mint instead, using `limitBy` to collapse to one row per token: ```graphql query SolanaWalletPortfolio { Solana { BalanceUpdates( where: { BalanceUpdate: { Account: { Owner: { is: "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" } } } } orderBy: { descending: Block_Time } limitBy: { by: BalanceUpdate_Currency_MintAddress, count: 1 } limit: { count: 50 } ) { BalanceUpdate { PostBalance PostBalanceInUSD Currency { Symbol MintAddress Decimals } } } } } ``` `PostBalance` is the balance after the most recent change, so one row per mint gives you the current portfolio. `limitBy` is doing the real work here — without it you get the full history of every change. :::note `Solana.BalanceUpdates` is the current balance API on Solana. EVM chains and Tron use `Balances` and `Holders` instead. See [Balances & Holders](/docs/cubes/balances-cube/). ::: ## A snapshot at a past date `Holders` takes a `date` argument, which answers "who held this token on this day" and, filtered to one address, "what did this wallet hold then". ```graphql query HoldersOnDate { EVM(network: eth, dataset: archive) { Holders( date: "2026-07-01" where: { Currency: { SmartContract: { is: "0x6982508145454ce325ddbe47a25d4ec3d2311933" } } } orderBy: { descendingByField: "balance" } limit: { count: 25 } ) { Holder { Address } balance: sum(of: Balance_Amount) } } } ``` :::caution Snapshot cost scales with the holder set A dated snapshot of a token with millions of holders can exceed the request timeout. The query above is fine on a token with a normal holder count, and the same query against a major stablecoin may not return. Narrow with `limit`, or filter to the addresses you care about, before widening. ::: ## Holder concentration Unlike `Balances`, the `Holders` cube supports the full aggregate and statistics set, so distribution metrics are computed server-side rather than by pulling every holder: ```graphql query HolderConcentration { EVM(network: eth, dataset: archive) { Holders( date: "2026-07-01" where: { Currency: { SmartContract: { is: "0x6982508145454ce325ddbe47a25d4ec3d2311933" } } } ) { holders: count gini: gini(of: Balance_Amount) nakamoto: nakamoto(of: Balance_Amount) median: median(of: Balance_Amount) total: sum(of: Balance_Amount) } } } ``` Reading the output: - **`nakamoto`** is the smallest number of holders that together control more than half the supply. It is the single most legible concentration number you can publish. - **`gini`** runs 0 (perfectly even) to 1 (one holder owns everything). Real tokens sit high, so compare tokens against each other rather than against an absolute threshold. - **`median`** is often `0`, because most addresses in a large holder set carry dust. That is a property of the token, not a bug in the query, and it is why the mean is misleading here. ## Streaming a portfolio You cannot subscribe to `Balances` or `Holders`. They are aggregate-state tables and never push a message, even though the subscription is accepted. Read the portfolio once, then keep it current from a stream that does fire: `Transfers` or `TransactionBalances` on EVM, `Tron.Transfers` on Tron, `Solana.BalanceUpdates` on Solana. See [which cubes support subscriptions](/docs/subscriptions/which-cubes-stream/). ## Related - [Balances & Holders cubes](/docs/cubes/balances-cube/) - [Build your own crypto P&L calculator](/docs/usecases/p-l-product/overview/) - [Which cubes support subscriptions](/docs/subscriptions/which-cubes-stream/) - [EVM Balance schema](/docs/schema/evm/balances/) · [EVM Token Holders schema](/docs/schema/evm/token-holders/) --- ## Wallet Provenance: First Funding & Deployer History URL: https://docs.bitquery.io/docs/usecases/wallet-provenance/ Find who funded a wallet, its first incoming transfer, and every token a deployer has launched — for provenance and rug-checks. # Wallet Provenance & Deployer History Two related investigation recipes: tracing where a wallet's funds first came from, and listing every token a deployer has launched (a common rug-check). ## First incoming transfer (who funded this wallet) Order the wallet's incoming transfers ascending by time and take the first — that's the initial funding event, and its sender is the funder. ```graphql { EVM(network: eth, dataset: combined) { Transfers( where: { Transfer: { Receiver: { is: "0x..." } } } orderBy: { ascending: Block_Time } limit: { count: 1 } ) { Block { Time } Transfer { Sender Amount Currency { Symbol SmartContract } } Transaction { Hash } } } } ``` Order by `Block_Time` (not a transaction-timestamp field, which can drop rows). Repeat on the funder to walk the chain back a few hops; for deep multi-hop money-flow, connect the [Bitquery MCP server](https://mcp.bitquery.io/) and ask an AI agent to trace it for you ([AI Investigations](https://bitquery.io/solutions/ai-investigations)), or use [Coinpath](https://docs.bitquery.io/v1/docs/Examples/coinpath/money-flow-api) directly. ## Every token a wallet deployed (rug-check) Find contract-creation events by a deployer address to see all tokens/contracts it launched — repeat-deployer patterns are a rug signal. ```graphql { EVM(network: eth, dataset: combined) { Calls( where: { Call: { Create: true, Signer: { is: "0x..." } } } ) { Block { Time } Call { To } Transaction { Hash } } } } ``` `Call.To` on a creation call is the newly-deployed contract address. On Solana, use the launchpad create instructions (e.g. pump.fun) filtered by the creator wallet — see the [Solana launchpad pages](/docs/blockchain/Solana/). ## Next steps - [ERC-20 transfers API](/docs/blockchain/Ethereum/transfers/erc20-token-transfer-api/) - [Tracing MCP guide](/docs/mcp/Tracing/overview) - [AI Investigations](https://bitquery.io/solutions/ai-investigations) - [Coinpath (v1)](https://docs.bitquery.io/v1/docs/Examples/coinpath/money-flow-api) --- ## Wash Trading Signals - Filtering Inorganic DEX Volume URL: https://docs.bitquery.io/docs/usecases/wash-trading-signals/ Queries for the on-chain signals commonly used to flag inorganic DEX volume: self-trades, round-tripping, tiny-notional churn and concentrated counterparties. # Wash trading signals A large share of reported DEX volume on any chain is generated by accounts trading with themselves. This page gives you the queries for the signals people use to flag it. :::caution These are signals, not a verdict There is no on-chain field that says "this was a wash trade", and every heuristic here has legitimate explanations. A market maker quoting both sides, an arbitrageur round-tripping within a block, and a bot rebalancing all look similar to a wash trader in the data. Nothing below classifies a trade. Each query surfaces a pattern; **you choose the thresholds and own the conclusion.** Treat a single signal as weak and a stack of them on the same account as worth investigating. ::: ## Signal 1: both sides of the same trade The cleanest case. Pull both accounts and compare them: ```graphql query TradeCounterparties { Solana { DEXTrades( where: { Transaction: { Result: { Success: true } } } limit: { count: 100 } orderBy: { descending: Block_Time } ) { Block { Time } Transaction { Signature Signer } Trade { Dex { ProtocolName } Buy { Account { Address } Amount AmountInUSD } Sell { Account { Address } Amount } } } } } ``` Compare `Buy.Account.Address` against `Sell.Account.Address` client-side. An exact match is a self-trade. It is rarer than people expect, because anyone doing this at scale uses separate addresses — which is what the next signal is for. ## Signal 2: round-tripping Far more common than literal self-trades: one account buys and sells the same token repeatedly, ending roughly flat. `sum` with an `if` condition splits buy and sell volume in a single pass: ```graphql query RoundTripTraders { Solana { DEXTradeByTokens( where: { Trade: { Currency: { MintAddress: { is: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" } } } Transaction: { Result: { Success: true } } } orderBy: { descendingByField: "trades" } limit: { count: 50 } ) { Trade { Account { Owner } } trades: count bought: sum(of: Trade_Amount, if: { Trade: { Side: { Type: { is: buy } } } }) sold: sum(of: Trade_Amount, if: { Trade: { Side: { Type: { is: sell } } } }) volumeUsd: sum(of: Trade_Side_AmountInUSD) } } } ``` What to compute from the result: - **Imbalance** = `abs(bought - sold) / (bought + sold)`. Near zero with a high trade count means the account cycled inventory rather than taking a position. - **Notional per trade** = `volumeUsd / trades`. Wash volume is usually many trades of trivial size, because the point is trade count and printed volume, not exposure. The two together are the useful test. An account with hundreds of trades, near-zero net position and a few dollars of notional per trade is doing something other than investing. An account with the same imbalance but large notional per trade is more likely a market maker. ## Signal 3: churn concentrated in a few accounts Organic volume comes from many accounts trading a few times each. Inorganic volume is the inverse. Compare the two distributions for a token: ```graphql query TraderConcentration { Solana { DEXTradeByTokens( where: { Trade: { Currency: { MintAddress: { is: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" } } } Transaction: { Result: { Success: true } } } limit: { count: 1 } ) { trades: count traders: count(distinct: Trade_Account_Owner) volumeUsd: sum(of: Trade_Side_AmountInUSD) } } } ``` `trades / traders` is trades per account. A token where that ratio is high while `volumeUsd` is low is printing activity rather than turnover. Run it across several tokens and compare — the absolute number means little on its own, the ranking means a lot. ## Signal 4: same counterparty, over and over Two addresses passing a position back and forth show up as a pair that trades almost exclusively with each other. Group trades by token and account, then look at how many distinct counterparties each account has: ```graphql query CounterpartyDiversity { Solana { DEXTrades( where: { Trade: { Buy: { Currency: { MintAddress: { is: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263" } } } } Transaction: { Result: { Success: true } } } orderBy: { descendingByField: "trades" } limit: { count: 50 } ) { Trade { Buy { Account { Address } } } trades: count counterparties: count(distinct: Trade_Sell_Account_Address) volumeUsd: sum(of: Trade_Buy_AmountInUSD) } } } ``` A high `trades` count against a very low `counterparties` count is the pattern. One or two counterparties across hundreds of trades is a closed loop; genuine flow touches many. ## Putting it together None of these is conclusive alone. A workable approach is to score rather than classify: 1. Pull per-account aggregates for the token (signal 2). 2. Flag accounts that clear **all** of your thresholds — low imbalance, low notional per trade, low counterparty diversity. 3. Report the flagged share of volume as a **range**, not a number, and state your thresholds alongside it. Two things worth being strict about if you publish results: - **Say what you measured.** "Volume from accounts with net position under 2% and under $10 median trade size" is defensible. "Wash volume" is a claim about intent that the data does not support. - **Do not extrapolate from a sample.** Scoring the top 50 accounts and multiplying up is the most common way these numbers end up wrong by an order of magnitude. Score the full account set, or report only the portion you measured. ## Related - [Filtering anomaly prices](/docs/usecases/how-to-filter-anomaly-prices/) - [Solana DEX Trades API](/docs/blockchain/Solana/solana-dextrades/) - [Trading cube — MEV-filtered trades](/docs/trading/trading-data-overview/) --- ## What are Internal Transactions & How to Get Them? URL: https://docs.bitquery.io/docs/API-Blog/what-are-internal-transactions-how-to-get-them/ What internal transactions are, how they differ from internal transfers, and how to trace both on EVM chains using Bitquery Calls and Transfers queries. # What are Internal Transactions & How to Get Them? In blockchain a transaction is the transfer of value between two participants, recorded on a digital ledger. Not all transactions involve the direct sending of funds from one wallet to another, some transactions occur within smart contracts. These transactions are known as internal transactions. Internal transactions are decoded calls — the [Smart Contract API](https://bitquery.io/products/smart-contract-api) page covers decoded events and calls with plans and trial access. Internal transactions are important in smart contract interactions, and having an understanding of how they work and how to trace them is essential for ensuring the transparency and security of smart contracts. This article will explain what internal transactions are, how to trace them using [Bitquery](https://bitquery.io/) APIs, and how to use Bitquery's tools for detailed blockchain analysis. ## What are Internal Transactions? Internal transactions are different from regular transactions as they happen between smart contracts. These transactions are not visible directly as they occur when a smart contract calls another smart contract or sends funds within itself. Some use cases of internal transactions are: - DeFi Operations: Internal transactions can be used in the management of activities such as lending, borrowing, and staking of funds between different contracts in decentralized finance applications. - Batch Processing for Efficiency: With internal transactions, multiple actions can be taken in a single step, thus making the transaction cost-effective and efficient. - Automated Withdrawals: With internal transactions, contracts can be configured to send funds to users based on specific conditions, such as rewards or payouts from a staking contract. - Fee Management: Internal transactions can also assist in fee management, allowing smart contracts to automatically deduct and transfer fees for services or transactions, thereby automating fee collection and distribution. The outcomes of internal transactions are recorded on the blockchain, but the transactions themselves are not directly traceable. Special tools and methods are required to track these outcomes. ## What are Internal Transfers? Internal transfers occur as part of the execution of smart contract functions rather than through external transactions. There is no difference between tokens received via an internal transfer and those received through a standard token transfer. The transfer of funds is reflected in the account's overall balance. ## Tracing Internal Transfer with APIs In this example we are querying all transfers happening as a part of this `0x9c78b80a02c882db9d3d9add2d98243e4aeadb035fe9aacf82d04d51092db7fc` transaction by using the `Transfers` cube and setting `Call-> Index` to greater than 0. Run the query [here](https://ide.bitquery.io/Get-internal-transfers-of-tx) ```graphql query MyQuery { EVM(dataset: archive, network: bsc) { Transfers( where: {Call: {Index: {gt: 0}}, Transaction: {Hash: {is: "0x9c78b80a02c882db9d3d9add2d98243e4aeadb035fe9aacf82d04d51092db7fc"}}} ) { Transfer { Amount Currency { Name } AmountInUSD } } } } ``` ## Tracing Internal Transactions with APIs ### Ethereum ```graphql query MyQuery { EVM(dataset: archive, network: eth) { in: Calls( where: {Call: {Depth: {gt: 0}}, Transaction: {Hash: {is: "0xd70c784ca3000da707d29c662d3a5dbe3d6bbade73686e1c73b4a24979d9e8c4"}}} ) { Transaction { Hash } Call { From Depth Value Success } } } } ``` [Try the above query here.](https://ide.bitquery.io/internal-tx-eth_1) The link above is an example that shows how an internal transaction on Ethereum is tracked. It shows the patterns of internal transactions within a single Ethereum transaction. It shows the layers of internal calls, the Ethereum addresses involved, the status of each call, and the amount of Ether transferred in each case. Query Components - In: This array contains a series of transactions occurring within the bigger transaction. - Call: This shows specific details of the transaction, such as the depth of the call, the address that initiated the call, the value of tokens transferred, and if the entire process was successful or not. - Depth: This simply shows how far we are looking into the transactions. Each Call has a "Depth" value that indicates how far it is away from the original transaction. A depth of 1 means it's a direct call, while Calls higher than 1 shows nested calls. - From: This is the Ethereum address that initiated the call. - Success: Shows if the call was successful (true) or not (false). - Value: This is the amount of Ether (ETH) transferred during the call. - Hash: This identifies the main transaction that contains these internal calls. Explaining the query data - The first part of the query shows a Call from the address [0x2d6adce390953535e02d338dd2998c81170c06e3](https://explorer.bitquery.io/ethereum/smart_contract/0x2d6adce390953535e02d338dd2998c81170c06e3) with a value of 0.000380422043696798 ETH. - This Call has a Depth of 1 meaning it is the primary internal transaction that occurred directly within the main transaction. - The subsequent calls have depths ranging from 2 to 7, which means they are deeply nested internal transactions. - All the internal transactions have the same transaction hash: [0xd70c784ca3000da707d29c662d3a5dbe3d6bbade73686e1c73b4a24979d9e8c4](https://explorer.bitquery.io/ethereum/tx/0xd70c784ca3000da707d29c662d3a5dbe3d6bbade73686e1c73b4a24979d9e8c4/tracing). Which means they are part of a single and larger Ethereum transaction. - The Calls all have different purposes based on the amount of Ether they transfer. For example, some transfer a small amount of Ether like 0.000380422043696798 ETH, while others transfer 0 ETH. This means some calls execute functions without transferring value or performing complex operations that require several steps. ### BNB ```graphql query MyQuery { EVM(dataset: archive, network: bsc) { in: Calls( where: {Call: {Depth: {gt: 0}}, Transaction: {Hash: {is: "0x9c78b80a02c882db9d3d9add2d98243e4aeadb035fe9aacf82d04d51092db7fc"}}} ) { Transaction { Hash } Call { From Depth Value Success } } } } ``` [Try the above query here.](https://ide.bitquery.io/internal-tx-bnb_1) This query is aimed at analyzing the execution flow of a transaction, showing all the internal calls, their success, and the interactions between different addresses in the same transaction. Components - in: This array contains a series of transactions occurring within the bigger transaction. - Call: This shows specific details of the transaction, such as the depth of the call, the address that initiated the call, the value of tokens transferred, and if the entire process was successful or not. - Depth: This simply shows how far we are looking into the transactions. Each call has a "Depth" value that indicates how far it is from the original transaction. A depth of 1 means it's a direct call, while Calls higher than 1 shows nested calls. - From: This is the BNB address that initiated the call. For example, two addresses in the query; [0xa188bd0af8b5f8d5c935d062ddb422bd96dcf65c](https://explorer.bitquery.io/bsc/smart_contract/0xa188bd0af8b5f8d5c935d062ddb422bd96dcf65c) and [0xc844ea097634f43ac7333bd7515eefda8afeec34](https://explorer.bitquery.io/bsc/smart_contract/0xc844ea097634f43ac7333bd7515eefda8afeec34/transactions) are repeatedly making calls. - Success: This shows if the call was successful (true) or not (false). In this query, all calls are labeled true, meaning they were successful. - Value: This is the amount of tokens transferred during the call. - Hash: This identifies the main transaction that contains these internal calls. ### BASE ```graphql query MyQuery { EVM(dataset: archive, network: base) { in: Calls( where: {Call: {Depth: {gt: 0}}, Transaction: {Hash: {is: "0x85dc2c0eac54d090ac7e1b50bd47ec686ba764870b61714937b32524a96ed2b6"}}} ) { Transaction { Hash } Call { From Depth Value Success } } } } ``` [Try the above query here.](https://ide.bitquery.io/Base-internal--transaction_1) This query is an example that shows how an internal transaction on Base is tracked. Details: - In: This array contains a series of transactions occurring within the bigger transaction. - where: This is used to filter the data based on certain conditions - Call: \{Depth: \{gt: 0\}\}: This was used to filter calls having Depth greater than 0. - From: This is the address that initiated the call. - Success: Shows if the call was successful (true) or not (false). - Value: This is the amount of tokens transferred during the call. - Hash: This is the unique identifier of the transaction. - Transaction:\{Hash: \{is: "0x85dc2c0eac54d090ac7e1b50bd47ec686ba764870b61714937b32524a96ed2b6"\}\}:'This filters for transactions with the specific hash mentioned. This means the query will only return data related to this transaction. ## Tracing Internal Transactions on Bitquery Explorer The [Bitquery Explorer](https://explorer.bitquery.io/) allows users to visually explore and analyze blockchain data. We can use this to track internal transactions. To track internal transactions using the Bitquery Explorer, follow these steps: - Visit the [Bitquery Explorer](https://explorer.bitquery.io/) website. - Select the blockchain network. - Enter the transaction hash you want to trace in the search bar. - Click the "Tracing" tab. Details: The internal transactions section provides details such as the sender and receiver addresses, the amount transferred, and the specific contract method called and gas used. ![Internal transactions example](https://lh7-rt.googleusercontent.com/docsz/AD_4nXd2u4PdpEvwZpwemeJ221AT2xGPrvHxjzzCuWdqbTQd64Mz_HJuX-O9ybhLjJmczfvuLkb9JwjBAVLB2lz7BO0b_VAPoeeXfOvIpSeA_uRQJg6Ya5W5oALK0hDPgLPq0uzxd4N_K4vcDUjLLR8rzF03t68g?key=5ttYeo2nskIw9kc9CkTdTA) There is also a graphical view that shows how funds flow between different addresses ![Internal transactions trace example](https://lh7-rt.googleusercontent.com/docsz/AD_4nXfpIcX5dHn8aERff9UJKN5qOzM7mFsuFmlP3b_zvMf2O4z27YRSnjDBUh4g0rXXha3xEaX_2U_cVHE2F6UQO_Da7VjyY5HPu6awAQ2TcofiixcuzMHuzDMs5KBJQ32TSwQU9a09RcOCNsZGOAS8ceMGZI5u?key=5ttYeo2nskIw9kc9CkTdTA) ### Tracing an Internal Transaction Example Now let's explore specific examples to gain a better understanding. We'll examine transactions on the following blockchains: - Ethereum - BNB - ARBITRUM #### Ethereum This is an example of an ETH transaction on the Bitquery Explorer using the tracing feature. [https://explorer.bitquery.io/ethereum/tx/0x26960e8c31dde5d76b69ba68201bfea5186555a7b44383f515d109ded74f3ac8/tracing](https://explorer.bitquery.io/ethereum/tx/0x26960e8c31dde5d76b69ba68201bfea5186555a7b44383f515d109ded74f3ac8/tracing) ![Ethereum internal transaction call tree](/img/ApplicationExamples/eth_tree.png) Below is a graphical view that shows how funds flow between different addresses ![Ethereum transaction trace](/img/ApplicationExamples/eth_trace.png) #### Binance (BNB) Smart Chain This is an example of a BSC transaction on the Bitquery Explorer using the tracing feature. [https://explorer.bitquery.io/bsc/tx/0x9c78b80a02c882db9d3d9add2d98243e4aeadb035fe9aacf82d04d51092db7fc/tracing](https://explorer.bitquery.io/bsc/tx/0x9c78b80a02c882db9d3d9add2d98243e4aeadb035fe9aacf82d04d51092db7fc/tracing) ![BNB Chain internal transaction call tree](/img/ApplicationExamples/bnb_tree.png) Below is a graphical view that shows how funds flow between different addresses ![BNB Chain transaction trace](/img/ApplicationExamples/bnb_trace.png) #### ARBITRUM This is an example of an Arbitrum transaction on the Bitquery Explorer using the tracing feature. [https://explorer.bitquery.io/arbitrum/tx/0x9346cd8afb33598d6ab57c3c83f5267ea96765e63e16b04e8dee7e599151c938/tracing](https://explorer.bitquery.io/arbitrum/tx/0x9346cd8afb33598d6ab57c3c83f5267ea96765e63e16b04e8dee7e599151c938/tracing) ![Arbitrum internal transaction call tree](/img/ApplicationExamples/arb_tree.png) Below is a graphical view that shows how funds flow between different addresses ![Arbitrum transaction trace](/img/ApplicationExamples/arb_trace.png) Here’s a practical example to help people understand the importance of tracing internal transactions: Tracking internal transactions can help people understand the movement of their funds. An example is a situation where a user moved some ETH from Coinbase to a hardware wallet, only to find out that the wallet did not display the second and third transactions, making them doubt if they had the coins in their wallet as expected. In the situation above, the user can use [Bitquery](https://bitquery.io/) APIs to trace these internal transactions to confirm the status of their ETH. They can query the internal transactions associated with the wallet address to ensure that all movements of their funds are accounted for. ## Conclusion Tracking internal transactions is important to ensure the transparency and integrity of blockchain activities. By using tools such as Bitquery to monitor these transactions, users can verify the movement of their funds and also understand the flow of assets within smart contracts, thus ensuring the wallet balances are accurate. This is important for developers and users who need to maintain trust and security in decentralized applications. For more information and related content, visit the [Bitquery blog](https://bitquery.io/blog) or explore the [documentation](https://docs.bitquery.io/). --- ## Whole-Population Sweeps and Pagination URL: https://docs.bitquery.io/docs/trading/query-operators/sweeps-and-pagination/ limitBy returns one row per entity across the entire population, not a top-N. How to page a sweep correctly, and why offset without orderBy silently duplicates rows. # Whole-Population Sweeps and Pagination `limitBy` is the operator that turns a Trading query into a **sweep**: one row per entity across the *entire* filtered population, not a top-N sample. It is how you get every token on a chain, or every wallet that traded in a window, without a query per entity. ```graphql limitBy: { by: Token_Id, count: 1 } ``` That returns one representative row for **every** distinct `Token_Id` the `where` clause admits. ## Paging a sweep Page with the outer `limit`, and always sort: ```graphql orderBy: { ascending: Token_Id } limit: { count: 25000, offset: 0 } # then offset: 25000, 50000, … ``` An empty array is the only stop signal — there is no total count or `hasMore` flag. :::danger `offset` without `orderBy` silently duplicates and drops rows Pagination without a sort order is **incoherent**: successive pages return some rows twice and miss others entirely, with no error. The effect is severe and gets worse as page size shrinks — in testing, a substantial double-digit percentage of rows were duplicated across a full paging run. Adding `orderBy` on the `limitBy` key removes it almost entirely; a residual fraction of a percent remains, which is live data arriving mid-run rather than a paging fault. ::: ## `limitBy.offset` and `limit.offset` are different This catches people out, because the two look alike: | | Skips | Filters? | | --- | --- | --- | | `limit: { offset: N }` | rows in the **final result set** | No | | `limitBy: { offset: N }` | rows **within each group** | **Yes** — groups smaller than the offset disappear entirely | So `limitBy: { by: Token_Id, count: 1, offset: 2 }` is not "skip two pages" — it is "only tokens with at least three rows, and give me their third". That is occasionally exactly what you want, and never what you want by accident. ## Which row does a sweep keep? `orderBy` decides. Without it, the representative row for each group is **arbitrary and can differ between identical requests**. If you are sweeping for "the latest row per token" or "the largest trade per wallet", say so explicitly: ```graphql orderBy: { descending: Block_Time } limitBy: { by: Token_Id, count: 1 } ``` ## Grouping by more than one field `by` accepts a **list**, and groups by the tuple: ```graphql limitBy: { by: [Token_Id, Pair_Market_Address], count: 1 } ``` `count` greater than 1 works too — `count: 3` gives the top three rows per key, ordered by your `orderBy`. ## Exact distinct counts A `limitBy` sweep is **exact**, which makes it a reference for cardinality. `uniq` is exact up to **65,536 (2^16)** distinct values and switches to a probabilistic estimator above that. In practice the two agree exactly well below the threshold, and the drift only becomes visible at high cardinality — where it is well under a percent, and in **either** direction rather than a systematic undercount. For an exact count without materialising the rows, use `count(distinct: )`. ## Cost Deep offsets are cheap on **plain row pagination** — latency stays roughly flat as the offset grows. They are **not** cheap on a `limitBy` sweep, because every group has to be materialised before the offset is applied. Prefer narrowing the `where` clause over paging deep into a sweep. ## Related - [Screeners with `selectWhere`](/docs/trading/query-operators/selectwhere-screeners/) - [Argmax selectors](/docs/trading/query-operators/argmax-selectors/) - [Filters and operators](/docs/trading/query-operators/filters-and-operators/) --- ## Widget Creation URL: https://docs.bitquery.io/docs/usecases/tradingview-subscription-realtime/widget/ Build Widget Creation: a practical Bitquery tutorial with GraphQL examples, streams, and runnable application code. Keep queries fast with indexed filters. # Widget Creation Now that we have setup a custom DataFeed, we need to create the TradingView Widget. The following code explains the `TVChartContainer.js` code file, which is responsible for rendering the TradingView chart and managing its configuration using the TradingView Advanced Charting Library. ```javascript ``` #### Component Overview - **Imports:** - `React`, `useEffect`, and `useRef` from React for managing component state and lifecycle. - `widget` from the TradingView charting library to initialize the chart. - `Datafeed` is a custom data source to fetch data (this is linked to the DEX data fetched via Bitquery API). #### Component Definition ```javascript const TVChartContainer = () => { const chartContainerRef = useRef(null); ``` - **chartContainerRef:** A reference to the chart container's `div`, which will be used as the mounting point for the TradingView widget. ```javascript console.log("TVChartContainer called."); ``` - **Debugging Info:** A simple log to ensure the component is rendered. #### useEffect Hook ```javascript useEffect(() => { console.log("TVChartContainer useEffect called."); ``` - The `useEffect` hook is used to initialize the TradingView widget when the component is mounted. It also includes a cleanup function to remove the widget when the component is unmounted. #### Widget Initialization ```javascript const widgetOptions = { symbol: "WIF", datafeed: Datafeed, interval: ["1"], container: chartContainerRef.current, library_path: "/charting_library/", // Ensure this path is correct locale: "en", disabled_features: ["use_localstorage_for_settings"], enabled_features: ["study_templates"], charts_storage_url: "https://saveload.tradingview.com", charts_storage_api_version: "1.1", client_id: "tradingview.com", user_id: "public_user_id", fullscreen: false, autosize: true, studies_overrides: {}, debug: true, chartType: 1, supports_marks: true, supports_timescale_marks: true, supported_resolutions: ["1", "5", "15", "30", "60", "1D", "1W", "1M"], supported_intervals: ["1", "5", "15", "30", "60", "1D", "1W", "1M"], theme: "dark", pricescale: 1000, data_status: "streaming", overrides: { "mainSeriesProperties.statusViewStyle.showInterval": true, "mainSeriesProperties.statusViewStyle.symbolTextSource": "ticker", "mainSeriesProperties.priceAxisProperties.indexedTo100": true, }, }; ``` - **Widget Options:** - `symbol`: The initial symbol that the chart will display. - `datafeed`: This is the custom datafeed to pull data from Bitquery API. - `interval`: Initial interval for the chart (for example, "1" for 1-minute intervals). - `container`: The DOM element where the chart will be rendered. - `library_path`: Path to the charting library files. - `locale`: Language for the widget (in this case, English). - `disabled_features`: Features disabled in the chart (e.g., storing settings in local storage). - `enabled_features`: Features that are enabled (like study templates). - `fullscreen` and `autosize`: Layout configuration for the chart. - `supports_marks` and `supports_timescale_marks`: Enable support for marks and time marks. - `supported_resolutions` and `supported_intervals`: Supported timeframes for the chart (in minutes, days, weeks, etc.). - `theme`: Dark mode is enabled for the chart. - `pricescale`: Sets the price scale, helpful for very small prices (indexed to one hundred for demo purposes). There are more options, read [here](https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.ChartPropertiesOverrides#properties) - `overrides`: Custom settings for displaying interval and symbol information. ```javascript console.log("widgetOptions:", widgetOptions); ``` - **Debugging Info:** Log the widget options before initializing the widget. #### Initializing the Widget ```javascript const tvWidget = new widget(widgetOptions); console.log("TradingView widget initialized.", tvWidget); ``` - A new instance of the TradingView widget is created using the defined options, and a log statement confirms its initialization. #### Handling Chart Ready Event ```javascript tvWidget.onChartReady(() => { console.log("Chart has loaded!"); const priceScale = tvWidget .activeChart() .getPanes()[0] .getMainSourcePriceScale(); priceScale.setAutoScale(true); }); ``` - **onChartReady:** This method ensures that after the chart is fully loaded, certain actions can be performed (e.g., setting the price scale to auto-scale for better display). #### Cleanup on Unmount ```javascript return () => { if (tvWidget) { console.log("Removing TradingView widget."); tvWidget.remove(); } }; ``` - The cleanup function ensures that the widget is removed when the component is unmounted, freeing up memory. #### Render Method ```javascript }, []); return (
); }; export default TVChartContainer; ``` - The component renders a `div` where the TradingView chart will be displayed, with the specified height and width. --- ## x402 Data APIs URL: https://docs.bitquery.io/docs/examples/x402/x402-data-apis/ x402 payment data: how x402 works, the Bazaar discovery endpoint, payment payload and verification, and queries for the latest payments to a specific server. # x402 Data API Docs - How to Query x402 Payment Data Learn how to query x402 payment data using GraphQL APIs. This comprehensive guide shows you how to access payment transactions, monitor server activity in real-time, and analyze payment analytics across multiple blockchain networks. The examples in this guide use Base network, but x402 protocol supports multiple chains. ## What is x402 API? The x402 API provides programmatic access to x402 protocol payment data through [GraphQL queries](/docs/graphql/query). You can query payment transactions, track server activity, monitor real-time payments, and analyze payment analytics directly from the blockchain using [Bitquery's streaming data platform](/docs/intro). ## x402 Overview x402 is a decentralized payment protocol that enables pay-per-use API access on blockchain networks. It allows developers to monetize APIs and services by accepting cryptocurrency payments directly, eliminating the need for traditional subscription models or credit card processing. The protocol operates on multiple blockchain networks (including Base) and uses smart contracts to facilitate automatic payment verification and settlement between clients, servers, and facilitators. ## How x402 Works? x402 operates on a three-party architecture consisting of clients (API consumers), servers (API providers), and facilitators (payment processors). When a client wants to use a paid API service, they initiate a payment transaction on the blockchain. The facilitator validates the payment and notifies the server, which then processes the API request. The server verifies the payment payload embedded in the request headers before delivering the service. This creates a trustless system where payments are verified on-chain before service delivery, ensuring both parties fulfill their obligations without requiring intermediaries. All payment transactions are recorded on the blockchain and can be queried using [GraphQL transfer queries](/docs/blockchain/Ethereum/transfers/erc20-token-transfer-api) for the respective network. ## Various Actors in x402 The x402 ecosystem consists of three main actors: 1. **Clients**: Users or applications that consume paid API services. They initiate [payment transactions](/docs/blockchain/Ethereum/transactions/transaction-api) and include payment payloads in their API requests. 2. **Servers**: API providers who offer services for payment. They verify payment payloads and deliver services after confirming valid payments on-chain. Server addresses can be tracked using [transfer queries](/docs/blockchain/Ethereum/transfers/erc20-token-transfer-api). 3. **Facilitators**: Payment processors that validate transactions, monitor the blockchain for payments, and notify servers when valid payments are detected. They help bridge the gap between on-chain payments and off-chain service delivery. ## x402 Bazaar - Discover Services and Endpoints The x402 Bazaar is a discovery service that allows you to find available x402-compatible APIs and services. You can query the Bazaar API to discover resources, view service metadata, check pricing, and understand API requirements. ### Discovery API Endpoint The x402 Bazaar discovery endpoint is available at: ``` https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources ``` This endpoint returns a paginated list of available x402 services with detailed information about each resource, including payment requirements, API schemas, performance metrics, and reliability scores. ### Example API Response When you query the x402 Bazaar discovery endpoint, you receive detailed information about each available service. Here's an example response structure: ```json { "accepts": [ { "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "description": "Payment for token analysis service ($0.01)", "extra": { "name": "USD Coin", "version": "2" }, "maxAmountRequired": "10000", "maxTimeoutSeconds": 300, "mimeType": "application/json", "network": "base", "outputSchema": { "input": { "bodyFields": { "chain": { "default": "bsc", "description": "Blockchain name (bsc, solana, ethereum, etc.)", "required": false, "type": "string" }, "language": { "default": "English", "description": "Output language (English, Chinese, Korean, etc.)", "required": false, "type": "string" }, "token": { "description": "Token address or symbol (e.g. BTC, ETH, SOL, etc.)", "required": true, "type": "string" } }, "bodyType": "json", "headerFields": { "X-PAYMENT": { "description": "Base64-encoded JSON PaymentPayload; automatically filled by x402scan", "required": false, "type": "string" } }, "method": "POST", "type": "http" }, "output": { "analysis": "object", "chain": "string", "created_at": "string", "language": "string", "status": "string", "token": "string", "workflow_run_id": "string|null" } }, "payTo": "0x83240485b70e5c820e5f180533fc6156470cfd0e", "resource": "https://x402.lucyos.ai/x402/tools/analyze_token", "scheme": "exact" } ], "lastUpdated": "2025-11-27T12:57:44.059Z", "metadata": { "confidence": { "overallScore": 0.75, "performanceScore": 0.32, "recencyScore": 1, "reliabilityScore": 0.98, "volumeScore": 0.8 }, "errorAnalysis": { "abandonedFlows": 18, "apiErrors": 0, "delayedSettlements": 0, "facilitatorErrors": 0, "requestErrors": 0 }, "paymentAnalytics": { "averageDailyTransactions": 139, "base:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": 9730000, "totalTransactions": 973, "totalUniqueUsers": 942, "transactions24h": 973, "transactionsMonth": 973, "transactionsWeek": 973 }, "performance": { "avgLatencyMs": 2525, "maxLatencyMs": 18221, "minLatencyMs": 1076, "recentAvgLatencyMs": 2359 }, "reliability": { "apiSuccessRate": 0.98, "successfulSettlements": 973, "totalRequests": 991 } }, "resource": "x402.lucyos.ai/x402/tools/analyze_token", "type": "http", "x402Version": 1 } ``` ### Understanding the Response Structure The response contains several key sections: - **`accepts`**: Array of payment configurations, including the asset (token), payment amount, network, and API endpoint details - **`payTo`**: The server's wallet address that receives payments - **`resource`**: The actual API endpoint URL - **`outputSchema`**: Defines the API's input requirements and expected output format - **`metadata`**: Contains performance metrics, reliability scores, payment analytics, and error analysis - **`confidence`**: Overall quality scores based on performance, recency, reliability, and transaction volume ## Payment Payload and Verification The x402 protocol uses a payment payload system to verify payments between clients and servers. When a client makes a payment, they include a Base64-encoded JSON payment payload in the `X-PAYMENT` header of their API request. ### Payment Payload Structure The payment payload typically contains: - Transaction hash of the payment - Payment amount - Recipient address (server) - Timestamp - Nonce or unique identifier ### Verification Process 1. **Client Side**: The client initiates a [payment transaction](/docs/blockchain/Ethereum/transactions/transaction-api) on the supported blockchain network, sending supported tokens (such as USDC) to the server's address. The client then encodes the payment details into a Base64 JSON payload. 2. **Server Side**: The server receives the API request with the `X-PAYMENT` header. It decodes the payload and verifies the payment by checking: - The transaction exists on-chain (verifiable through [transaction queries](/docs/blockchain/Ethereum/transactions/transaction-api)) - The payment amount matches the required fee - The recipient address matches the server's address - The transaction is confirmed and not a double-spend 3. **Facilitator Role**: Facilitators monitor the blockchain for payment transactions using [real-time subscriptions](/docs/category/graphql-subscriptions/). When they detect a valid payment to a registered server, they notify the server, enabling faster service delivery without waiting for full blockchain confirmation. This architecture ensures that servers only deliver services after verifying valid on-chain payments, creating a trustless pay-per-use system. ## x402 Data API Queries The following queries demonstrate how to query x402 payment data using [Bitquery's GraphQL API](/docs/graphql/query). These queries help you monitor payments, track server activity, and analyze payment analytics. For more information on building queries, see our [GraphQL query guide](/docs/graphql/query) and [filtering documentation](/docs/graphql/filters). :::note Multi-Chain Support x402 protocol supports multiple blockchain networks. The examples below use Base network, but you can adapt these queries for other supported chains by changing the `network` parameter. ::: ### Example x402 Server For the following examples, we'll use this x402 server address: **Server Address**: `0x83240485b70e5c820e5f180533fc6156470cfd0e` This server provides token analysis services and accepts USDC payments. The examples below use Base network, but the same query structure applies to other supported chains. ## Listening to Latest Payments to a Specific Server This query retrieves the most recent payments made to a specific x402 server. It's useful for monitoring server activity and tracking payment transactions. You can run this query [here](https://ide.bitquery.io/Latest-payment-to-specific-x402-server). ```graphql query MyQuery { EVM(dataset: realtime, network: base) { Transfers( where: {Transfer: {Receiver: {in: ["0x83240485b70e5c820e5f180533fc6156470cfd0e"]}}} orderBy: {descending: Block_Number} limit: {count: 100} ) { Transaction { Hash } Transfer { Amount Sender Receiver Currency { Symbol Name } } } } } ``` ### Query Explanation - **`dataset: realtime`**: Queries the most recent blockchain data. Learn more about [dataset options](/docs/graphql/dataset/options) - **`network: base`**: Specifies the network (Base in this example). Change this to query other supported chains (e.g., `network: ethereum`, `network: bsc`). See [supported networks](/docs/blockchain/introduction) - **`Receiver: {in: [...]}`**: Filters transfers to the specific server address using [GraphQL filters](/docs/graphql/filters) - **`orderBy: {descending: Block_Number}`**: Returns the most recent payments first. See [sorting documentation](/docs/graphql/sorting) - **`limit: {count: 100}`**: Retrieves up to 100 payment transactions. Check [query limits](/docs/graphql/limits) ## Real-Time Payment Monitoring with GraphQL WebSockets You can monitor payments to a specific x402 server in real-time using GraphQL WebSocket subscriptions. This enables live tracking of payment activity without polling. You can run this subscription [here](https://ide.bitquery.io/Monitoring-the-latest-payment-to-the-specific-X402-server). ```graphql subscription { EVM(network: base) { Transfers( where: {Transfer: {Receiver: {in: ["0x83240485b70e5c820e5f180533fc6156470cfd0e"]}}} ) { Transaction { Hash } Transfer { Amount Sender Receiver Currency { Symbol Name } } } } } ``` ### Subscription Explanation - **`subscription`**: Uses [GraphQL subscription](/docs/subscriptions/subscription) for real-time updates - **`EVM(network: base)`**: Monitors the specified network (Base in this example). Change the network parameter to monitor other supported chains - **`Transfers`**: Listens for new transfer events matching the filter - The subscription will automatically push new payment transactions as they occur on-chain. Learn more about [real-time subscriptions](/docs/category/graphql-subscriptions/) ## Payment Analytics for Specific x402 Server This query provides comprehensive payment analytics for a specific x402 server, including total volume, unique users, transaction counts, and time-based breakdowns. You can run this query [here](https://ide.bitquery.io/Payment-analytics-related-specific-x402-server). ```graphql query MyQuery { EVM(dataset: combined, network: base) { Transfers( where: { Block: {Time: {since_relative: {days_ago: 7}}} Transfer: { Currency: {SmartContract: {is: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"}} Receiver: {in: ["0x83240485b70e5c820e5f180533fc6156470cfd0e"]} } } ) { Transfer { Receiver } amount7days: sum(of: Transfer_Amount) amountInUSD7days: sum(of: Transfer_AmountInUSD) totalUniqueUsers7days: count(distinct: Transfer_Sender) totalTransactions7days: count(distinct: Transaction_Hash) transactions24h: count( distinct: Transaction_Hash if: {Block: {Time: {since_relative: {hours_ago: 24}}}} ) } } } ``` ### Query Explanation - **`dataset: combined`**: Queries both historical and real-time data. See [dataset options](/docs/graphql/dataset/options) - **`since_relative: {days_ago: 7}`**: Analyzes the last 7 days of payments using [datetime filters](/docs/graphql/datetime) - **`Currency: {SmartContract: {is: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"}}`**: Filters for USDC payments (this is the USDC contract address on Base; use the appropriate contract address for other networks). Learn about [token transfer queries](/docs/blockchain/Ethereum/transfers/erc20-token-transfer-api) - **`sum(of: Transfer_Amount)`**: Calculates total payment volume using [GraphQL metrics](/docs/graphql/metrics) - **`sum(of: Transfer_AmountInUSD)`**: Calculates total volume in USD - **`count(distinct: Transfer_Sender)`**: Counts unique users who made payments - **`count(distinct: Transaction_Hash)`**: Counts total payment transactions - **`transactions24h`**: Conditional count for transactions in the last 24 hours using [conditional metrics](/docs/graphql/metrics) ### Analytics Metrics Returned - **`amount7days`**: Total payment amount received in the last 7 days - **`amountInUSD7days`**: Total payment amount in USD equivalent - **`totalUniqueUsers7days`**: Number of unique users who made payments - **`totalTransactions7days`**: Total number of payment transactions - **`transactions24h`**: Number of transactions in the last 24 hours ## Additional Use Cases ### Monitor Multiple x402 Servers You can monitor payments to multiple x402 servers by adding more addresses to the `in` array: ```graphql where: { Transfer: { Receiver: { in: [ "0x83240485b70e5c820e5f180533fc6156470cfd0e", "0x45A33aC6DB4455460b364A1fc1aE8C489Bc644A7" ] } } } ``` ### Filter by Payment Amount To filter payments by minimum amount: ```graphql where: { Transfer: { Receiver: {in: ["0x83240485b70e5c820e5f180533fc6156470cfd0e"]} Amount: {gt: "10000"} // Minimum 0.01 USDC (6 decimals) } } ``` ### Time-Based Analysis Analyze payments over different time periods: ```graphql where: { Block: { Time: { since: "2024-01-01T00:00:00Z" till: "2024-12-31T23:59:59Z" } } } ``` ## x402 Data APIs for Solana x402 protocol also operates on Solana network. The following queries demonstrate how to query x402 payment data on Solana using Bitquery's GraphQL API. Solana uses a different address format and query structure compared to EVM chains. ### Example x402 Server on Solana For the following Solana examples, we'll use this x402 server address: **Server Address**: `DevFFyNWxZPtYLpEjzUnN1PFc9Po6PH7eZCi9f3tTkTw` This is the Dexter • Crypto agent server address that accepts payments on Solana. ## Latest Payment to x402 Server on Solana This query retrieves the most recent payments made to a specific x402 server on Solana. It's useful for monitoring server activity and tracking payment transactions. You can run this query [here](https://ide.bitquery.io/Latest-Payment-to-specific-x402-server-taking-solana-payments). ```graphql { Solana { Transfers( limit: {count: 100} orderBy: {descending: Block_Time} where: {Transfer: {Receiver: {Owner: {is: "DevFFyNWxZPtYLpEjzUnN1PFc9Po6PH7eZCi9f3tTkTw"}}}} ) { Transfer { Amount AmountInUSD Sender { Address Owner } Receiver { Address Owner } Currency { Symbol Name MintAddress } } Instruction { Program { Method } } Block { Time } Transaction { Signature } } } } ``` ### Query Explanation - **`Solana`**: Specifies the Solana network schema - **`Receiver: {Owner: {is: "..."}}`**: Filters transfers to the specific server owner address (Solana uses owner addresses instead of contract addresses) - **`orderBy: {descending: Block_Time}`**: Returns the most recent payments first - **`limit: {count: 100}`**: Retrieves up to 100 payment transactions - **`AmountInUSD`**: Shows the payment amount in USD equivalent - **`Instruction`**: Provides details about the Solana program instruction that executed the transfer ## Real-Time Payment Monitoring on Solana You can monitor payments to a specific x402 server on Solana in real-time using GraphQL WebSocket subscriptions. This enables live tracking of payment activity without polling. You can run this subscription [here](https://ide.bitquery.io/Real-Time---Solana-transfers-stream). ```graphql subscription { Solana { Transfers( where: {Transfer: {Receiver: {Owner: {is: "DevFFyNWxZPtYLpEjzUnN1PFc9Po6PH7eZCi9f3tTkTw"}}}} ) { Transfer { Amount AmountInUSD Sender { Address Owner } Receiver { Address Owner } Currency { Symbol Name MintAddress } } Instruction { Program { Method } } Block { Time } Transaction { Signature } } } } ``` ### Subscription Explanation - **`subscription`**: Uses [GraphQL subscription](/docs/subscriptions/subscription) for real-time updates - **`Solana`**: Monitors the Solana network - **`Transfers`**: Listens for new transfer events matching the filter - The subscription will automatically push new payment transactions as they occur on-chain. Learn more about [real-time subscriptions](/docs/category/graphql-subscriptions/) ## Payment Analytics for x402 Server on Solana This query provides comprehensive payment analytics for a specific x402 server on Solana, including total volume, unique users, and transaction counts. :::note Solana API Version For complete transfer history on Solana, we use the v1 Solana API because v2 Solana API only shows transfers from the last 8 hours. The v1 API provides complete transfer history for comprehensive analytics. ::: You can run this query [here](https://ide.bitquery.io/Payment-analytics-related-specific-x402-server-on-Solana). ```graphql { solana { transfers( date: {since: "2025-11-22"} receiverAddress: {is: "DevFFyNWxZPtYLpEjzUnN1PFc9Po6PH7eZCi9f3tTkTw"} currency: {is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"} ) { totalTransactions7days: count(uniq: signature) amount7days: amount(calculate: sum) totalUniqueUsers7days: count(uniq: sender_address) transactions24h: count(time: {since: "2025-11-28T00:00:00"}) } } } ``` ### Query Explanation - **`solana`**: Uses the v1 Solana API schema for complete historical data - **`date: {since: "2025-11-22"}`**: Analyzes payments since the specified date - **`receiverAddress: {is: "..."}`**: Filters for transfers to the specific server address - **`currency: {is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"}`**: Filters for USDC payments (USDC mint address on Solana) - **`count(uniq: signature)`**: Counts unique transaction signatures - **`amount(calculate: sum)`**: Calculates total payment volume - **`count(uniq: sender_address)`**: Counts unique users who made payments - **`count(time: {since: "..."})`**: Conditional count for transactions in the last 24 hours ### Analytics Metrics Returned - **`totalTransactions7days`**: Total number of payment transactions in the specified period - **`amount7days`**: Total payment amount received - **`totalUniqueUsers7days`**: Number of unique users who made payments - **`transactions24h`**: Number of transactions in the last 24 hours ## Related Documentation - [Blockchain Networks](/docs/blockchain/introduction) - Overview of supported blockchain networks - [Base Network Documentation](/docs/blockchain/Base/) - Complete guide to querying Base blockchain data - [Solana Network Documentation](/docs/blockchain/Solana/) - Complete guide to querying Solana blockchain data - [Ethereum Network Documentation](/docs/blockchain/Ethereum/) - Query Ethereum blockchain data - [BSC Network Documentation](/docs/blockchain/BSC/) - Query BSC blockchain data - [GraphQL Query Guide](/docs/graphql/query) - Learn how to build GraphQL queries - [Real-time Subscriptions](/docs/category/graphql-subscriptions/) - Monitor blockchain data in real-time - [Transfer API Documentation](/docs/blockchain/Ethereum/transfers/erc20-token-transfer-api) - Query ERC-20 token transfers - [Solana Transfers](/docs/blockchain/Solana/solana-transfers) - Query Solana token transfers - [GraphQL Filters](/docs/graphql/filters) - Advanced filtering techniques - [GraphQL Metrics](/docs/graphql/metrics) - Aggregation and calculation functions - [Datetime Queries](/docs/graphql/datetime) - Time-based filtering and analysis - [Getting Started Guide](/docs/start/first-query) - Build your first query - [WebSocket Subscriptions](/docs/subscriptions/websockets/) - Real-time data streaming