You are migrating this codebase from Birdeye Data Services (BDS) to Codex (https://docs.codex.io). Birdeye and Codex overlap heavily on token, trade, holder, and wallet data, so most call sites have a direct replacement. A few do not, and those need to be surfaced rather than silently dropped.
## Phase 1: Discovery (do this first, do not edit yet)
Search the codebase for every Birdeye integration point. At minimum, look for:
- HTTP calls to `public-api.birdeye.so` (any path under `/defi`, `/wallet`, `/token`, `/trader`, `/perps`, `/smart-money`, `/utils`, `/holder`, or `/v1/wallet`).
- WebSocket connections to `wss://public-api.birdeye.so/socket/...`.
- Imports of any Birdeye SDK or client library.
- Environment variables and config keys named `BIRDEYE_*` or `BDS_*`.
- Header usage of `X-API-KEY` and `x-chain`.
- Code that switches behavior on Birdeye `chain` strings (`solana`, `ethereum`, `base`, etc.).
- Tests, fixtures, and mocks that reference any of the above.
Produce a Migration Plan with:
1. A grouped list of every call site, organized by Birdeye endpoint.
2. The proposed Codex equivalent for each group (use the mapping below).
3. Any call sites you cannot map cleanly, flagged for human review.
4. The order you intend to make changes (shared client/config first, then leaf call sites, then tests).
5. New dependencies, env vars, and config you will introduce.
Stop and surface the plan before editing any source files. Wait for confirmation.
## Phase 2: Execution (after the plan is approved)
Ground rules:
1. Codex is a GraphQL API at `https://graph.codex.io/graphql`. Auth header is `Authorization: <api-key>` for long-lived keys, or `Authorization: Bearer <jwt>` for short-lived keys.
2. Codex ships an official SDK for TypeScript/JavaScript only (`@codex-data/sdk`). Prefer it over hand-rolled HTTP in TS/JS projects. For Python and every other language there is no official SDK: use raw GraphQL over HTTP against `https://graph.codex.io/graphql`.
3. Network is a parameter (`networkId`), not a header. Convert Birdeye `x-chain` strings to Codex network IDs: `solana` → 1399811149, `ethereum` → 1, `base` → 8453, `bsc` → 56, `polygon` → 137, `arbitrum` → 42161, `optimism` → 10, `avalanche` → 43114, `sui` → 101. For others, call `getNetworks` once and build a lookup.
4. Token IDs in Codex are the string `"<address>:<networkId>"`. Construct them explicitly; never assume an integration relies on bare addresses.
5. Where a Birdeye integration hits two or three endpoints to fill one screen (for example token_overview + token_security + market-data), collapse them into a single GraphQL query.
6. For real-time data, use WebSocket subscriptions when the consumer is a long-lived client (dashboards, trading UIs) and webhooks when the consumer is a server endpoint (alerts, background workers, queues).
7. Preserve existing public function signatures, return shapes, and error semantics wherever possible. Internal helpers can be refactored freely.
8. Update tests as you change code. If a test relied on a Birdeye response fixture, replace the fixture with a Codex equivalent rather than deleting the test.
9. When you hit a gap (perpetuals, non-swap wallet transfers, first-funded-by lookup, CEX liquidity), do not silently drop the feature. Leave the call site intact, add a `TODO(migration):` comment with a one-line note explaining what's missing and what provider could fill it, and list it in your final report. Note that Sui (`networkId: 101`) is supported broadly, but `balances` is not available on Sui: flag any Sui balance lookups specifically.
## Birdeye → Codex endpoint mapping
Prices and OHLCV:
- `GET /defi/price`, `GET|POST /defi/multi_price` → `getTokenPrices(inputs: [...])` (max 25 inputs per call; chunk Birdeye `multi_price` batches larger than 25 or they will be silently truncated)
- `GET /defi/historical_price_unix` → `getTokenPrices(inputs: [{ address, networkId, timestamp }])` (the historical price at a unix timestamp)
- `GET /defi/history_price` → `getTokenBars`
- `GET /defi/ohlcv`, `/defi/v3/ohlcv` → `getTokenBars`
- `GET /defi/ohlcv/pair`, `/defi/v3/ohlcv/pair`, `/defi/ohlcv/base_quote` → `getBars` (use `quoteToken: token0 | token1` to invert the pair)
- `GET /defi/price_volume/*`, `/defi/v3/price/stats/*` → `getDetailedTokenStats(tokenAddress, networkId, durations: [...])` (top-level args, not an `input` object)
Token data:
- `GET /defi/token_overview` → `token` + `getDetailedTokenStats` in one query
- `GET /defi/v3/token/meta-data/single` → `token(input: { address, networkId })`
- `GET /defi/v3/token/meta-data/multiple` → `tokens(ids: [{ address, networkId }, ...])`
- `GET /defi/v3/token/market-data*`, `/defi/v3/token/trade-data/*` → `getDetailedTokenStats`
- `GET /defi/token_security` → safety fields on `token` (`isScam`, `mintable`, `freezable`, `creatorAddress`, `top10HoldersPercent`). `circulatingSupply` and `totalSupply` live under `token.info`, not the top-level token object.
- `GET /defi/token_creation_info` → `createdAt`, `creatorAddress` on `token`
- `GET /defi/v3/token/exit-liquidity`, `/defi/v3/token/exit-liquidity/multiple` → `liquidityMetadata`, `liquidityMetadataByToken`, `liquidityLocks`
Discovery:
- `GET /defi/token_trending` → `filterTokens(rankings: [{ attribute: trendingScore24, direction: DESC }])`
- `GET /defi/v3/token/list`, `/defi/v3/token/list/scroll`, `/defi/tokenlist`, `/defi/v2/tokens/new_listing` → `filterTokens(...)`
- `GET /defi/v3/search` → `filterTokens(phrase: "$SYMBOL", ...)`
- `GET /defi/v3/token/meme/*` → `filterTokens` plus launchpad fields on `token.launchpad`
- `GET /smart-money/v1/token/list` → `filterWallets` + `filterTokens` (see Wallets recipe)
Pairs:
- `GET /defi/v3/pair/overview/single` → `getDetailedPairStats` (pair trade stats are part of this response; Birdeye has no separate pair `trade-data` endpoint)
- `GET /defi/v3/pair/overview/multiple` → `getDetailedPairsStats`
- `GET /defi/v2/markets` → `listPairsForToken` or `listPairsWithMetadataForToken`
Trades:
- `GET /defi/txs/token`, `/defi/txs/token/seek_by_time`, `/defi/v3/token/txs`, `/defi/v3/txs`, `/defi/v3/txs/recent` → `getTokenEvents(query: { address, networkId, ... })`
- `GET /defi/txs/pair`, `/defi/txs/pair/seek_by_time` → `getTokenEvents` (pair address as `query.address`)
- `GET /defi/v3/token/txs-by-volume` → `getTokenEvents` with `priceUsdTotal` filter
- `GET /defi/v3/token/mint-burn-txs` → `getTokenEvents` with `eventDisplayType: [Mint, Burn]` (Solana only)
- `GET /defi/v3/all-time/trades/single`, `POST /defi/v3/all-time/trades/multiple` → `getDetailedTokenStats` (read `statsUsd.volume.currentValue`, `statsNonCurrency.transactions.currentValue`)
Holders:
- `GET /defi/v3/token/holder` → `holders(input: { tokenId, sort: { attribute: BALANCE, direction: DESC } })` (`BALANCE` is the only supported sort attribute; `DATE` exists but is deprecated)
- `GET /holder/v1/distribution`, `/token/v1/holder-profile` → `top10HoldersPercent` on `token` (combine with `holders` for the ranked list)
- `GET /token/v1/holder/chart` → partial via `onHoldersUpdated` (live counts only; no first-class historical timeseries)
- `GET /token/v1/holder-positions` → `filterTokenWallets`
- `POST /token/v1/holder/batch` → `balances` per wallet (use GraphQL aliases to batch)
Wallets:
- `GET /v1/wallet/token_list`, `/v1/wallet/token_balance`, `POST /wallet/v2/token-balance` → `balances(input: { walletAddress, networks: [Int!], removeScams, tokens, limit })` (the network field is plural/array, not `networkId`; max 200 tokens per request)
- `GET /v1/wallet/list_supported_chain` → `getNetworks`
- `GET /v1/wallet/tx_list` → `getTokenEventsForMaker` (swap events; flag if raw transfers are required)
- `GET /wallet/v2/current-net-worth`, `/wallet/v2/pnl`, `/wallet/v2/pnl/summary`, `/wallet/v2/pnl/multiple`, `POST /wallet/v2/pnl/details` → `detailedWalletStats` and `filterWallets`
- `GET /wallet/v2/net-worth`, `/wallet/v2/net-worth-details`, `POST /wallet/v2/net-worth-summary/multiple` → `walletChart` + `detailedWalletStats` (alias multiple wallets in one request)
- `GET /wallet/v2/balance-change` → `onBalanceUpdated` subscription
Traders:
- `GET /defi/v2/tokens/top_traders` → `tokenTopTraders(input: { tokenAddress, networkId, tradingPeriod })` (`tradingPeriod` is `DAY | WEEK | MONTH | YEAR` — no hourly value)
- `GET /trader/txs/seek_by_time` → `getTokenEventsForMaker`
- `GET /trader/gainers-losers` → `filterWallets(input: { rankings: [{ attribute: realizedProfitUsd1d, direction: DESC }] })` (pick `1d` / `1w` / `30d` / `1y` to match the timeframe)
Real-time (WebSocket → Codex subscription):
- `SUBSCRIBE_PRICE` → `onPriceUpdated` / `onPricesUpdated`
- `SUBSCRIBE_BASE_QUOTE_PRICE` → `onBarsUpdated`
- `SUBSCRIBE_TOKEN_STATS` → `onDetailedTokenStatsUpdated`
- `SUBSCRIBE_TXS`, `SUBSCRIBE_LARGE_TRADE_TXS` → `onTokenEventsCreated` / `onEventsCreated` (apply a min-volume filter for the large-trade variant)
- `SUBSCRIBE_WALLET_TXS` → `onEventsCreatedByMaker`
- `SUBSCRIBE_NEW_PAIR`, `SUBSCRIBE_TOKEN_NEW_LISTING`, `SUBSCRIBE_MEME` → `onTokenLifecycleEventsCreated`, `onLatestTokens`, `onLaunchpadTokenEvent`
- `SUBSCRIBE_TRANSFER` → not supported (Codex streams swaps and lifecycle events, not arbitrary transfers)
Utility:
- `GET /defi/networks` → `getNetworks`
- `GET /defi/v3/txs/latest-block` → `blocks(input: { networkId, blockNumbers, timestamps })`
Gaps (flag, do not drop):
- `/perps/v1/*`: not supported
- `/wallet/v2/transfer*`, `/token/v1/transfer*`: Codex is swap-focused; pair with an RPC provider for raw transfers
- `POST /wallet/v2/tx/first-funded`: no direct equivalent
- NFT endpoints: Codex does not expose NFT data
- Sui (`networkId: 101`): broadly supported, but `balances` does not work on Sui. Flag Sui balance call sites specifically.
- EVM native balances via `balances` are only available on networks with traces enabled. If a customer relies on native-token portfolio values on a chain without traces, flag it.
When you need details on any Codex field, fetch the reference page at `https://docs.codex.io/api-reference/queries/<name>` (or `subscriptions`, `mutations`) rather than guessing. Before migrating any real-time code, fetch `https://docs.codex.io/concepts/subscriptions` and `https://docs.codex.io/concepts/webhooks` so you pick the right delivery mechanism.
## Phase 3: Final report
When the migration is done, produce a single report with:
1. Files changed, grouped by area (client/config, call sites, tests, docs).
2. Every `TODO(migration):` you added, with file path, line, and the reason.
3. New env vars and dependencies, with the line to add to `.env.example` and the package manager command to install.
4. Birdeye integrations that were removed entirely, and what replaced them.
5. A short manual-verification checklist the human should run before merging (which features to click through, which endpoints to spot-check, which dashboards to load).
Run the project's linter and test suite before declaring the migration complete. If tests fail, fix the underlying integration, do not weaken the test.