Skip to content

feat: add hyperswap-v2#2285

Merged
0xkr3p merged 1 commit intoDefiLlama:masterfrom
0xkr3p:feat/add-hyperswap-pools
Jan 23, 2026
Merged

feat: add hyperswap-v2#2285
0xkr3p merged 1 commit intoDefiLlama:masterfrom
0xkr3p:feat/add-hyperswap-pools

Conversation

@0xkr3p
Copy link
Contributor

@0xkr3p 0xkr3p commented Jan 23, 2026

Summary

Adds hyperswap-v2 adapter

Resources:

Summary by CodeRabbit

  • New Features
    • Hyperswap V2 liquidity pool APY calculations now available
    • Provides 1-day and 7-day annualized yield estimates
    • Pools quality-filtered to ensure reliable metrics
    • Direct integration with Hyperswap for liquidity provision access

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

A new Hyperswap V2 APY adapter module has been added that queries a subgraph for liquidity pools, aggregates volume data over 7 days, computes APY metrics based on trading fees and TVL, and exports formatted pool information with underlying tokens and metadata.

Changes

Cohort / File(s) Summary
Hyperswap V2 APY Adapter
src/adaptors/hyperswap-v2/index.js
New adapter implementation. Fetches pair data from subgraph with pagination, calculates 1-day and 7-day APY metrics using 0.3% fee assumption, applies MIN_TVL_USD threshold of 1000, and exports apy function with timetravel disabled and project URL. Includes subgraph query logic, volume aggregation, APY calculations, and error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • feat: add hyperswap #2271: Adds identical Hyperswap V2 adapter with matching apy/timetravel/url exports — direct parallel implementation of the same adapter.

Poem

🐰✨ A new adapter hops into sight,
Hyperswap V2 shining bright,
APY flows through subgraph queries deep,
Seven-day volumes we lovingly keep,
Liquidity pools dance in the light! 💰

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add hyperswap-v2' directly matches the main objective of the PR, which is to add a Hyperswap V2 adapter. It is concise, specific, and clearly communicates the primary change.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@llamatester
Copy link

The hyperswap-v2 adapter exports pools:

Test Suites: 1 passed, 1 total
Tests: 970 passed, 970 total
Snapshots: 0 total
Time: 0.482 s
Ran all test suites.

Nb of pools: 161
 

Sample pools:
┌─────────┬──────────────────────────────────────────────┬────────────┬────────────────┬────────────────┬────────────────────┬─────────────────────────┬────────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────────────────┬─────────────────────┐
│ (index) │ pool                                         │ chain      │ project        │ symbol         │ tvlUsd             │ apyBase                 │ apyBase7d              │ underlyingTokens                                                                               │ url                                                                                                                             │ volumeUsd1d          │ volumeUsd7d         │
├─────────┼──────────────────────────────────────────────┼────────────┼────────────────┼────────────────┼────────────────────┼─────────────────────────┼────────────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────────────────┼─────────────────────┤
│ 0       │ '0xa19ea099afed76d1cf5f84c6c863365e5798a7ca' │ 'Hyperevm' │ 'hyperswap-v2' │ 'WHYPE-HFUN'   │ 248760.49082470822 │ 1.7865778157316559      │ 6.757838120963757      │ [ '0x5555555555555555555555555555555555555555', '0xa320d9f65ec992eff38622c63627856382db726c' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x5555555555555555555555555555555555555555/0xa320d9f65ec992eff38622c63627856382db726c' │ 4058.7212268305184   │ 107761.73896697872  │
│ 1       │ '0x056f0975f104cb5318ecc55f0c82b33a756d29c6' │ 'Hyperevm' │ 'hyperswap-v2' │ 'BUDDY-WHYPE'  │ 161461.41679929243 │ 0.021307343101070176    │ 2.3022073907297136     │ [ '0x47bb061c0204af921f43dc73c7d7768d2672ddee', '0x5555555555555555555555555555555555555555' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x47bb061c0204af921f43dc73c7d7768d2672ddee/0x5555555555555555555555555555555555555555' │ 31.418390916232145   │ 23828.05558160396   │
│ 2       │ '0x5a77b0d96ad87defee407ed5244c942ff7fc5779' │ 'Hyperevm' │ 'hyperswap-v2' │ 'WHYPE-JOFF'   │ 78643.38518154512  │ 0.000016424648606735334 │ 0.16177279465456362    │ [ '0x5555555555555555555555555555555555555555', '0x62d5dd0190376c444a4b2e2e860aa392ec83ed80' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x5555555555555555555555555555555555555555/0x62d5dd0190376c444a4b2e2e860aa392ec83ed80' │ 0.011796255405032105 │ 815.5359103790929   │
│ 3       │ '0x29e813b394673c285e1d347bf28290ceaeba61b9' │ 'Hyperevm' │ 'hyperswap-v2' │ 'THAW-WHYPE'   │ 77409.76479108357  │ 0                       │ null                   │ [ '0x1d6e7e8b14af35cb5e4141e680b493aba0f7f92c', '0x5555555555555555555555555555555555555555' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x1d6e7e8b14af35cb5e4141e680b493aba0f7f92c/0x5555555555555555555555555555555555555555' │ 0                    │ 0                   │
│ 4       │ '0x5352ed32aeac9fe0e474a599def11d7057d5442e' │ 'Hyperevm' │ 'hyperswap-v2' │ 'PEG-USDC'     │ 58462.830191034634 │ 0                       │ null                   │ [ '0x28245ab01298eaef7933bc90d35bd9dbca5c89db', '0xb88339cb7199b77e23db6e890353e22632ba630f' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x28245ab01298eaef7933bc90d35bd9dbca5c89db/0xb88339cb7199b77e23db6e890353e22632ba630f' │ 0                    │ 0                   │
│ 5       │ '0x0237b44bbd51e665d61fefec5f97bf88687ed9ca' │ 'Hyperevm' │ 'hyperswap-v2' │ 'JEFF-WHYPE'   │ 49108.488482573535 │ 0.6080071269424204      │ 2.2685838625528776     │ [ '0x52e444545fbe9e5972a7a371299522f7871aec1f', '0x5555555555555555555555555555555555555555' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x52e444545fbe9e5972a7a371299522f7871aec1f/0x5555555555555555555555555555555555555555' │ 272.6786391851551    │ 7141.456697816036   │
│ 6       │ '0x981f145a71da6df4a7cbe892807782c9cc9a5515' │ 'Hyperevm' │ 'hyperswap-v2' │ 'WHYPE-PRFI'   │ 40016.05671503226  │ 1.358995125232193       │ 2.6503582718657306     │ [ '0x5555555555555555555555555555555555555555', '0x7bbcf1b600565ae023a1806ef637af4739de3255' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x5555555555555555555555555555555555555555/0x7bbcf1b600565ae023a1806ef637af4739de3255' │ 496.6358539428658    │ 6798.518392444484   │
│ 7       │ '0x0c278010bf21bae386b6ce4440535970a4ea5238' │ 'Hyperevm' │ 'hyperswap-v2' │ 'WHYPE-HL'     │ 36566.67866780104  │ 0.00012966335345428962  │ 0.7899351080663475     │ [ '0x5555555555555555555555555555555555555555', '0x738dd55c272b0b686382f62dd4a590056839f4f6' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x5555555555555555555555555555555555555555/0x738dd55c272b0b686382f62dd4a590056839f4f6' │ 0.04330007471006866  │ 1851.6220041715908  │
│ 8       │ '0x97b550396163b48d947b5cc24fd957b14c23f82b' │ 'Hyperevm' │ 'hyperswap-v2' │ 'WHYPE-MILK'   │ 36550.94768238835  │ 0.0003081438512710644   │ 0.00004389995206122397 │ [ '0x5555555555555555555555555555555555555555', '0xfe69bc93b936b34d371defa873686c116c8488c2' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x5555555555555555555555555555555555555555/0xfe69bc93b936b34d371defa873686c116c8488c2' │ 0.10285798891742769  │ 0.10285800327238166 │
│ 9       │ '0xe107ed81e16dd31099839c98a8575c41907d9a68' │ 'Hyperevm' │ 'hyperswap-v2' │ 'WHYPE-HYPSTR' │ 36300.64985193453  │ 0                       │ null                   │ [ '0x5555555555555555555555555555555555555555', '0x92eb91ac8bc3abe1812c0064e05e667335440d8e' ] │ 'https://app.hyperswap.exchange/#/add/v2/0x5555555555555555555555555555555555555555/0x92eb91ac8bc3abe1812c0064e05e667335440d8e' │ 0                    │ 0                   │
└─────────┴──────────────────────────────────────────────┴────────────┴────────────────┴────────────────┴────────────────────┴─────────────────────────┴────────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────────────────┴─────────────────────┘
This adapter contains some pools with <10k TVL, these pools won't be shown in DefiLlama

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/adaptors/hyperswap-v2/index.js`:
- Around line 41-54: The pairDayDatas query currently uses a single large
pairAddress_in and first:1000 which silently truncates results and risks
timeouts; update the code that invokes pairDayDataQuery to (1) split
pairAddresses into batches of ~50–200 addresses, (2) perform keyset pagination
using the date field (do NOT use skip)—i.e. request pages in a loop passing a
date cursor (use date_gt when ordering asc, or use date_lt when ordering desc)
and continue fetching while the returned array length == pageSize, and (3)
accumulate results per batch before moving to the next address batch; reference
the pairDayDataQuery GraphQL constant and the caller function (the function that
builds/executes this query) to implement batching and date-based pagination.

Comment on lines +41 to +54
const pairDayDataQuery = gql`
query getPairDayData($pairAddresses: [String!], $startTime: Int!) {
pairDayDatas(
first: 1000
orderBy: date
orderDirection: desc
where: { pairAddress_in: $pairAddresses, date_gt: $startTime }
) {
pairAddress
dailyVolumeUSD
date
}
}
`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's see the actual code at the specified lines
cat -n src/adaptors/hyperswap-v2/index.js | sed -n '35,135p'

Repository: DefiLlama/yield-server

Length of output: 3074


🌐 Web query:

Goldsky Hyperswap v2 subgraph pairDayDatas query skip pagination limits

💡 Result:

On Goldsky, Hyperswap v2 subgraphs use Graph Node query semantics, so you’ll hit the usual pagination constraints:

  • Per-request page size cap: first is effectively capped at 1000 entities per query (common across The Graph / Graph Node deployments). You must paginate in multiple requests. [1], [2]
  • skip hard limit: many Graph Node deployments enforce a maximum skip (commonly you’ll see errors like “skip must be between 0 and 5000” when you go past it). This limit is enforced specifically because large offsets perform badly; Graph Node even added a configurable global skip limit (GRAPH_GRAPHQL_MAX_SKIP) for this purpose. [3], [4]

What to do instead of skip for pairDayDatas

Use keyset pagination (cursor-like) with orderBy + where:

Option A (recommended): paginate by date

query PairDayDatas($pair: String!, $lastDate: Int!) {
  pairDayDatas(
    first: 1000
    orderBy: date
    orderDirection: asc
    where: { pair: $pair, date_gt: $lastDate }
  ) {
    id
    date
    dailyVolumeUSD
    reserveUSD
  }
}

Loop: start with lastDate = 0, then set lastDate to the last returned date each page.

Option B: paginate by id
Same idea, but orderBy: id and where: { id_gt: $lastId }.

This avoids the skip ceiling and is the approach Graph Node recommends for large result sets. [1], [4]

Sources: [1] https://thegraph.com/docs/en/subgraphs/querying/graphql-api/ • [2] https://docs.uniswap.org/contracts/v2/reference/API/queries • [3] https://www.reddit.com/r/thegraph/comments/nx97nk • [4] graphprotocol/graph-node#1912


🏁 Script executed:

# Check for other GraphQL queries in the file to see how they handle pagination
cat -n src/adaptors/hyperswap-v2/index.js | head -40

Repository: DefiLlama/yield-server

Length of output: 1106


🏁 Script executed:

# Search for other uses of date_gt or similar keyset patterns in hyperswap-v2
rg -A 5 "date_gt|id_gt|orderBy" src/adaptors/hyperswap-v2/

Repository: DefiLlama/yield-server

Length of output: 860


🌐 Web query:

Graph Node pairAddress_in list size limits Uniswap subgraph batch size

💡 Result:

  • Hard limit you will hit first: on The Graph (including Uniswap subgraphs), the maximum first per entity query is 1000 (default is often 100 if you omit first). So you can’t fetch more than 1000 pairs (or swaps, etc.) in a single request—use pagination (skip, or preferably cursor-style where available). [1] [2]
  • pairAddress_in / _in filter list size: The Graph’s GraphQL API supports _in / _not_in filters, but there isn’t a clearly documented fixed maximum number of values you can put in an _in list; in practice you’ll be constrained by request size + query complexity/timeouts rather than an advertised “N addresses max”. [3]
  • Practical batching guidance (for Uniswap subgraphs): keep pairAddress_in batches small enough to avoid gateway/node timeouts—typically ~50–200 addresses per query, then loop batches; separately, keep first1000 for the returned entity list. (If you need “all pairs”, page in chunks of 1000.) [1]

Sources
[1] Uniswap docs noting The Graph limits returns to 1000 entities per query: https://docs.uniswap.org/contracts/v2/reference/API/queries
[2] Ethereum StackExchange answer summarizing default first=100 and max first=1000 on The Graph: https://ethereum.stackexchange.com/questions/114818/what-is-the-default-query-limit-on-the-graph
[3] The Graph GraphQL API docs listing _in / _not_in filters (but no stated list-size max): https://thegraph.com/docs/en/subgraphs/querying/graphql-api/


Batch pairDayDatas requests to avoid silent volume truncation; use date-based keyset pagination, not skip.

pairDayDatas(first: 1000) with pairAddress_in: $pairAddresses silently truncates results in two ways:

  1. Results truncation: only first 1000 records per batch are returned; no pagination fetches the rest.
  2. Query timeout risk: passing many addresses in pairAddress_in without batching can exceed query complexity limits.

Graph Node subgraphs (Goldsky included) discourage skip pagination due to hard limits (commonly ~5000) and poor performance on large offsets. Instead, use keyset pagination via date_gt, which is already available in the query. Batch addresses into groups of 50–200 per request to stay within timeout/complexity budgets.

🔧 Suggested fix (batching + date-based pagination)
 const pairDayDataQuery = gql`
-  query getPairDayData($pairAddresses: [String!], $startTime: Int!) {
+  query getPairDayData($pairAddresses: [String!], $startTime: Int!, $first: Int!) {
     pairDayDatas(
-      first: 1000
+      first: $first
       orderBy: date
       orderDirection: asc
       where: { pairAddress_in: $pairAddresses, date_gt: $startTime }
     ) {
       pairAddress
       dailyVolumeUSD
       date
     }
   }
 `;
 
 async function fetchPairDayData(pairAddresses) {
   const startTime = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60;
+  const first = 1000;
+  const batchSize = 150; // tune to API limits
+  const volumesByPair = {};
+  const lastDayVolumeByPair = {};
 
+  const batches = [];
+  for (let i = 0; i < pairAddresses.length; i += batchSize) {
+    batches.push(pairAddresses.slice(i, i + batchSize));
+  }
 
   try {
-    const result = await request(SUBGRAPH_URL, pairDayDataQuery, {
-      pairAddresses,
-      startTime,
-    });
-
-    const volumesByPair = {};
-    const lastDayVolumeByPair = {};
-
-    const pairDayDatas = result.pairDayDatas || [];
-    
-    for (const dayData of pairDayDatas) {
-      const pairAddr = dayData.pairAddress.toLowerCase();
-      const volume = parseFloat(dayData.dailyVolumeUSD) || 0;
-
-      if (!volumesByPair[pairAddr]) {
-        volumesByPair[pairAddr] = 0;
-      }
-      volumesByPair[pairAddr] += volume;
-
-      if (
-        !lastDayVolumeByPair[pairAddr] ||
-        dayData.date > lastDayVolumeByPair[pairAddr].date
-      ) {
-        lastDayVolumeByPair[pairAddr] = {
-          date: dayData.date,
-          volume: volume,
-        };
-      }
+    for (const batch of batches) {
+      let lastDate = startTime;
+      while (true) {
+        const result = await request(SUBGRAPH_URL, pairDayDataQuery, {
+          pairAddresses: batch,
+          startTime: lastDate,
+          first,
+        });
+
+        const pairDayDatas = result.pairDayDatas || [];
+        if (pairDayDatas.length === 0) break;
+
+        for (const dayData of pairDayDatas) {
+          const pairAddr = dayData.pairAddress.toLowerCase();
+          const volume = parseFloat(dayData.dailyVolumeUSD) || 0;
+
+          if (!volumesByPair[pairAddr]) volumesByPair[pairAddr] = 0;
+          volumesByPair[pairAddr] += volume;
+
+          if (
+            !lastDayVolumeByPair[pairAddr] ||
+            dayData.date > lastDayVolumeByPair[pairAddr].date
+          ) {
+            lastDayVolumeByPair[pairAddr] = {
+              date: dayData.date,
+              volume,
+            };
+          }
+          lastDate = Math.max(lastDate, dayData.date);
+        }
+
+        if (pairDayDatas.length < first) break;
+      }
     }
 
     return {
       weeklyVolumes: volumesByPair,
       lastDayVolumes: Object.fromEntries(
         Object.entries(lastDayVolumeByPair).map(([k, v]) => [k, v.volume])
       ),
     };
   } catch (error) {
     console.error('Error fetching pair day data:', error);
     return { weeklyVolumes: {}, lastDayVolumes: {} };
   }
 }
🤖 Prompt for AI Agents
In `@src/adaptors/hyperswap-v2/index.js` around lines 41 - 54, The pairDayDatas
query currently uses a single large pairAddress_in and first:1000 which silently
truncates results and risks timeouts; update the code that invokes
pairDayDataQuery to (1) split pairAddresses into batches of ~50–200 addresses,
(2) perform keyset pagination using the date field (do NOT use skip)—i.e.
request pages in a loop passing a date cursor (use date_gt when ordering asc, or
use date_lt when ordering desc) and continue fetching while the returned array
length == pageSize, and (3) accumulate results per batch before moving to the
next address batch; reference the pairDayDataQuery GraphQL constant and the
caller function (the function that builds/executes this query) to implement
batching and date-based pagination.

@0xkr3p 0xkr3p merged commit 17952c0 into DefiLlama:master Jan 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants