Skip to content

Harbor#2698

Open
fabiaz84 wants to merge 6 commits into
DefiLlama:masterfrom
fabiaz84:harborfi
Open

Harbor#2698
fabiaz84 wants to merge 6 commits into
DefiLlama:masterfrom
fabiaz84:harborfi

Conversation

@fabiaz84
Copy link
Copy Markdown
Contributor

@fabiaz84 fabiaz84 commented May 20, 2026

Added new markets and additional chain support MegaETH

Summary by CodeRabbit

  • New Features

    • Multi-chain Harbor support with explicit chain identifiers, richer market labels, and multiple per-market listings (including new haUSD markets).
    • New underlying-assets mapping for cross-chain asset identifiers and improved display names.
  • Refactor

    • Enhanced price feeds including composite collateral pricing and expanded feed coverage.
    • More robust TVL/APR calculations with tolerant reward/price failures, chain-aware pool IDs, and market-level metadata.

Review Change Stack

fabiaz84 and others added 3 commits May 20, 2026 23:16
Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c7d90f0-8bec-4b85-87b1-0f87ebe4c477

📥 Commits

Reviewing files that changed from the base of the PR and between dd2a9b1 and c2934af.

📒 Files selected for processing (1)
  • src/adaptors/harbor/index.js

📝 Walkthrough

Walkthrough

This PR converts Harbor to multi-chain (ethereum, megaeth) config, adds chain-aware Chainlink pricing and composite collateral feed handling, updates reward APR calculation, and rewrites pool discovery to aggregate and emit chain-scoped pools with labeled metadata.

Changes

Harbor Multi-Chain Pricing Refactor

Layer / File(s) Summary
Multi-chain config structure
src/adaptors/harbor/config.js
CHAINLINK_FEEDS restructured to per-chain mapping; TOKEN_CHAINLINK_FEED_MAP adds haUSD and updates haMCAP; UNDERLYING_ASSET_DISPLAY updated; MARKETS now includes chain, marketLabel, and optional collateralPriceFeed; UNDERLYING_ASSETS added and exported.
Config imports and ABI setup
src/adaptors/harbor/index.js
Imports MARKETS and feed/token mappings from config; adds Chainlink decimals ABI constant and derives activeMarkets.
Chainlink pricing helpers and reward APR updates
src/adaptors/harbor/index.js
Adds getChainlinkFeedPriceUsd() with decimals fallback and getCollateralUsdPrice() with composite handling; updates calculateAPRFromRewards() to new index mapping, finish-time checks, BigInt→BigNumber conversion, and tolerant per-token failures when summing APRs.
Pegged price resolution and memoization
src/adaptors/harbor/index.js
Adds resolveMarketPeggedPriceUsd() that uses minter peggedTokenPrice fallback and underlying USD price multiplication, with memoization and marketLabel-guarded logging.
Multi-chain pool discovery and aggregation
src/adaptors/harbor/index.js
Rewrites fetchPoolsFromChain() to be chain-aware: groups enabled markets by chain:peggedTokenSymbol, computes pegged token USD price (with peggedTokenPrice fallback), derives collateral USD via helpers, fetches TVL via totalAssets/totalAssetSupply fallback, computes per-market APRs and selects minimum, aggregates TVL, and emits pools keyed by ${peggedTokenAddress}-${chain} with market labels in poolMeta.

Sequence Diagram(s)

sequenceDiagram
    participant Pool as Pool Discovery
    participant Config as Multi-Chain Config
    participant Pricing as Pricing Helpers
    participant Rewards as Reward Data

    Pool->>Config: lookup activeMarkets by chain
    Config-->>Pool: markets with chain, label, collateralPriceFeed
    Pool->>Pricing: getChainlinkFeedPriceUsd(peggedFeed, chain)
    Pricing-->>Pool: pegged token USD price + decimals
    Pool->>Pricing: getCollateralUsdPrice(collateralFeed, chain)
    Pricing-->>Pool: collateral USD price (composite if needed)
    Pool->>Rewards: query rewardData (minter)
    Rewards-->>Pool: reward tokens, rates, finishAt
    Pool->>Pricing: calculateAPRFromRewards(rewards)
    Pricing-->>Pool: aggregated APR contributions
    Pool-->>Pool: group TVL/APR by chain:pegged token and emit pools
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • 0xkr3p

Poem

🐰 I hopped through feeds on ethereum and megaeth,

haUSD found its place, a bright new clef,
Collateral whispers stitched, composite and neat,
APRs align where markets meet,
A rabbit pats the multi-chain beat.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Harbor' is overly vague and does not clearly summarize the main changes in the pull request. Revise the title to be more specific, such as 'Add MegaETH support and multi-chain configuration for Harbor' or 'Refactor Harbor adapter for multi-chain pool discovery'
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions
Copy link
Copy Markdown

The harbor adapter exports pools:

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

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬──────────┬─────────┬───────────────────┬───────────────────┬──────────────────────────────────────────────────┬───────────────────────────────────────────────┬───────────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project  │ symbol  │ tvlUsd            │ apyBase           │ underlyingTokens                                 │ poolMeta                                      │ url                                   │
├─────────┼───────────────────────────────────────────────────────┼────────────┼──────────┼─────────┼───────────────────┼───────────────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────┼───────────────────────────────────────┤
│ 0       │ '0x25ba4a826e1a1346dca2ab530831dbff9c08bea7-ethereum' │ 'Ethereum' │ 'harbor' │ 'haBTC' │ 52753.78906042619 │ 5.321897373221072 │ [ '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' ] │ 'haBTC Stability Pool (BTC/fxUSD, BTC/stETH)' │ 'https://app.harborfinance.io/anchor' │
│ 1       │ '0x83fd69e0ff5767972b46e61c6833408361bf7346-ethereum' │ 'Ethereum' │ 'harbor' │ 'haEUR' │ 12310.67044180205 │ 6.186431137606886 │ [ '0xdb25f211ab05b1c97d595516f45794528a807ad8' ] │ 'haEUR Stability Pool (EUR/fxUSD, EUR/stETH)' │ 'https://app.harborfinance.io/anchor' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴──────────┴─────────┴───────────────────┴───────────────────┴──────────────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────────────────────────────┘

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (1)
src/adaptors/harbor/index.js (1)

272-314: 💤 Low value

Consider making minter and price feed selection explicit for the same market.

The code selects peggedTokenPriceInUnderlying from the first minter that succeeds (lines 272-291) and priceFeedKey from the first market with collateralPriceFeed (lines 300-301). For the USD pricing to be correct, both must reference the same collateral type.

Currently this works because the MARKETS array happens to be ordered such that the first haUSD market (USD/PAXG) provides both values. However, if the array order changes or new markets are inserted, the minter's exchange rate could be multiplied by a mismatched collateral price.

💡 Suggestion: couple minter and price feed explicitly
-      for (const market of tokenMarkets) {
-        if (market.minterAddress && peggedTokenPriceInUnderlying === 0) {
+      let selectedMarket = null;
+      for (const market of tokenMarkets) {
+        if (market.minterAddress && peggedTokenPriceInUnderlying === 0) {
           try {
             const minterPriceResult = await sdk.api.abi.call({
               target: market.minterAddress,
               abi: MINTER_ABI.find((m) => m.name === 'peggedTokenPrice'),
               chain,
             });
             if (minterPriceResult?.output) {
               peggedTokenPriceInUnderlying = Number(minterPriceResult.output) / 1e18;
+              selectedMarket = market;
               // ... logging ...
               break;
             }
           } catch (error) {
             continue;
           }
         }
       }
       // ...
-      const priceFeedKey =
-        tokenMarkets.find((m) => m.collateralPriceFeed)?.collateralPriceFeed || defaultFeedKey;
+      const priceFeedKey =
+        selectedMarket?.collateralPriceFeed || defaultFeedKey;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adaptors/harbor/index.js` around lines 272 - 314, The pegged token USD
price can mismatch because peggedTokenPriceInUnderlying is taken from the first
successful minter while priceFeedKey is taken from the first market with
collateralPriceFeed; fix by selecting a single market that provides both values
(e.g., find a market where m.minterAddress && m.collateralPriceFeed) and use
that market to call the minter (MINTER_ABI 'peggedTokenPrice') and to set
priceFeedKey, falling back to the existing separate-search logic only if no such
paired market exists; ensure you still handle errors from the SDK call and the
getCollateralUsdPrice(chain, priceFeedKey) call and preserve the existing
fallbacks for peggedTokenPriceInUnderlying and underlyingAssetPriceUSD.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adaptors/harbor/config.js`:
- Around line 215-227: UNDERLYING_ASSETS currently maps haUSD to Chainlink feed
contracts instead of the ERC20 token contracts; update the haUSD values in the
UNDERLYING_ASSETS object so ethereum.haUSD is
0x2536A8636A99466173229AB15fdb37Fcaa05BA1A and megaeth.haUSD is
0xbEd2c24Cf10d7aC58350364aF8d3AbC0ce0D626f (these are the actual haUSD token
contract addresses) so that code paths using UNDERLYING_ASSETS (e.g.,
underlyingTokens) receive real token addresses rather than price feed addresses;
keep all other entries unchanged.

---

Nitpick comments:
In `@src/adaptors/harbor/index.js`:
- Around line 272-314: The pegged token USD price can mismatch because
peggedTokenPriceInUnderlying is taken from the first successful minter while
priceFeedKey is taken from the first market with collateralPriceFeed; fix by
selecting a single market that provides both values (e.g., find a market where
m.minterAddress && m.collateralPriceFeed) and use that market to call the minter
(MINTER_ABI 'peggedTokenPrice') and to set priceFeedKey, falling back to the
existing separate-search logic only if no such paired market exists; ensure you
still handle errors from the SDK call and the getCollateralUsdPrice(chain,
priceFeedKey) call and preserve the existing fallbacks for
peggedTokenPriceInUnderlying and underlyingAssetPriceUSD.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10c67dd8-eced-4d2c-801c-75e8f61e55d6

📥 Commits

Reviewing files that changed from the base of the PR and between 3ef6bad and 5e2b6fc.

📒 Files selected for processing (2)
  • src/adaptors/harbor/config.js
  • src/adaptors/harbor/index.js

Comment thread src/adaptors/harbor/config.js
…price feed.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/adaptors/harbor/index.js (1)

272-327: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Potential pricing mismatch in fallback path for haUSD markets.

The preferred market selection (lines 272-274) correctly requires both minterAddress and collateralPriceFeed. However, the fallback loop (lines 295-315) sets pricingMarket to any market that returns a price, regardless of whether it has collateralPriceFeed.

If the fallback market lacks collateralPriceFeed, lines 325-327 will use a different market's collateralPriceFeed, causing a unit mismatch:

  • peggedTokenPriceInUnderlying from USD/stETH minter = "1 haUSD = X stETH"
  • Using PAXG_USD or TBTC_USD feed would multiply X (in stETH terms) by the wrong USD price
🛠️ Suggested fix: ensure fallback also couples minter with its collateralPriceFeed
      if (peggedTokenPriceInUnderlying === 0) {
        for (const market of tokenMarkets) {
-         if (!market.minterAddress) continue;
+         if (!market.minterAddress || !market.collateralPriceFeed) continue;
          try {
            const minterPriceResult = await sdk.api.abi.call({
              target: market.minterAddress,
              abi: MINTER_ABI.find((m) => m.name === 'peggedTokenPrice'),
              chain,
            });
            if (minterPriceResult?.output) {
              peggedTokenPriceInUnderlying = Number(minterPriceResult.output) / 1e18;
              pricingMarket = market;
              console.log(
                `  [${chain}] peggedTokenPrice from ${market.marketLabel || 'market'} minter: ${peggedTokenPriceInUnderlying.toFixed(6)} ${UNDERLYING_ASSET_DISPLAY[peggedTokenSymbol] || 'collateral units'}`
              );
              break;
            }
          } catch (error) {
            continue;
          }
        }
      }
+
+     // Final fallback for tokens without collateralPriceFeed (e.g., haETH, haBTC)
+     if (peggedTokenPriceInUnderlying === 0) {
+       for (const market of tokenMarkets) {
+         if (!market.minterAddress) continue;
+         try {
+           const minterPriceResult = await sdk.api.abi.call({
+             target: market.minterAddress,
+             abi: MINTER_ABI.find((m) => m.name === 'peggedTokenPrice'),
+             chain,
+           });
+           if (minterPriceResult?.output) {
+             peggedTokenPriceInUnderlying = Number(minterPriceResult.output) / 1e18;
+             pricingMarket = market;
+             break;
+           }
+         } catch (error) {
+           continue;
+         }
+       }
+     }

Alternatively, a simpler approach is to only enter the fallback loop if the preferred selection failed and none of the markets have collateralPriceFeed:

      if (peggedTokenPriceInUnderlying === 0) {
+       const hasAnyFeedMarket = tokenMarkets.some((m) => m.collateralPriceFeed);
        for (const market of tokenMarkets) {
-         if (!market.minterAddress) continue;
+         // For tokens with collateral-specific feeds, require the feed; otherwise allow any minter
+         if (!market.minterAddress || (hasAnyFeedMarket && !market.collateralPriceFeed)) continue;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adaptors/harbor/index.js` around lines 272 - 327, The fallback loop can
choose a market with a minterAddress but no collateralPriceFeed, causing
peggedTokenPriceInUnderlying (from that minter) to be paired with an unrelated
collateralPriceFeed later when computing priceFeedKey; modify the fallback logic
in the block that iterates tokenMarkets (the loop that sets
peggedTokenPriceInUnderlying and pricingMarket) to only accept and assign a
market if it has both market.minterAddress and market.collateralPriceFeed (or,
alternatively, when setting pricingMarket also attach/validate
collateralPriceFeed exists), so that the subsequent priceFeedKey resolution
(which reads pricingMarket.collateralPriceFeed) remains consistent with the
minter used; ensure references to pricingMarket, tokenMarkets,
peggedTokenPriceInUnderlying, market.minterAddress and
market.collateralPriceFeed are updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/adaptors/harbor/index.js`:
- Around line 272-327: The fallback loop can choose a market with a
minterAddress but no collateralPriceFeed, causing peggedTokenPriceInUnderlying
(from that minter) to be paired with an unrelated collateralPriceFeed later when
computing priceFeedKey; modify the fallback logic in the block that iterates
tokenMarkets (the loop that sets peggedTokenPriceInUnderlying and pricingMarket)
to only accept and assign a market if it has both market.minterAddress and
market.collateralPriceFeed (or, alternatively, when setting pricingMarket also
attach/validate collateralPriceFeed exists), so that the subsequent priceFeedKey
resolution (which reads pricingMarket.collateralPriceFeed) remains consistent
with the minter used; ensure references to pricingMarket, tokenMarkets,
peggedTokenPriceInUnderlying, market.minterAddress and
market.collateralPriceFeed are updated accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc12a235-3ee1-402f-bdf8-5405c62e1e9a

📥 Commits

Reviewing files that changed from the base of the PR and between 5e2b6fc and dd2a9b1.

📒 Files selected for processing (2)
  • src/adaptors/harbor/config.js
  • src/adaptors/harbor/index.js

@github-actions
Copy link
Copy Markdown

The harbor adapter exports pools:

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

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬──────────┬─────────┬───────────────────┬──────────────────┬──────────────────────────────────────────────────┬───────────────────────────────────────────────┬───────────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project  │ symbol  │ tvlUsd            │ apyBase          │ underlyingTokens                                 │ poolMeta                                      │ url                                   │
├─────────┼───────────────────────────────────────────────────────┼────────────┼──────────┼─────────┼───────────────────┼──────────────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────┼───────────────────────────────────────┤
│ 0       │ '0x25ba4a826e1a1346dca2ab530831dbff9c08bea7-ethereum' │ 'Ethereum' │ 'harbor' │ 'haBTC' │ 52845.12585787536 │ 5.34200242051403 │ [ '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' ] │ 'haBTC Stability Pool (BTC/fxUSD, BTC/stETH)' │ 'https://app.harborfinance.io/anchor' │
│ 1       │ '0x83fd69e0ff5767972b46e61c6833408361bf7346-ethereum' │ 'Ethereum' │ 'harbor' │ 'haEUR' │ 12310.67044180205 │ 6.18955902688851 │ [ '0xdb25f211ab05b1c97d595516f45794528a807ad8' ] │ 'haEUR Stability Pool (EUR/fxUSD, EUR/stETH)' │ 'https://app.harborfinance.io/anchor' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴──────────┴─────────┴───────────────────┴──────────────────┴──────────────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────────────────────────────┘

fabiaz84 and others added 2 commits May 21, 2026 23:01
Only use fallback minters on markets that also have collateralPriceFeed when any market in the group has a feed, so peg ratio and Chainlink feed stay paired.

Co-authored-by: Cursor <cursoragent@cursor.com>
Resolve pegged token USD inside the market loop with caching so multi-collateral haUSD pools use matching minter and Chainlink feed pairs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions
Copy link
Copy Markdown

The harbor adapter exports pools:

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

Nb of pools: 2
 

Sample pools:
┌─────────┬───────────────────────────────────────────────────────┬────────────┬──────────┬─────────┬───────────────────┬───────────────────┬──────────────────────────────────────────────────┬───────────────────────────────────────────────┬───────────────────────────────────────┐
│ (index) │ pool                                                  │ chain      │ project  │ symbol  │ tvlUsd            │ apyBase           │ underlyingTokens                                 │ poolMeta                                      │ url                                   │
├─────────┼───────────────────────────────────────────────────────┼────────────┼──────────┼─────────┼───────────────────┼───────────────────┼──────────────────────────────────────────────────┼───────────────────────────────────────────────┼───────────────────────────────────────┤
│ 0       │ '0x25ba4a826e1a1346dca2ab530831dbff9c08bea7-ethereum' │ 'Ethereum' │ 'harbor' │ 'haBTC' │ 52769.66699399028 │ 5.322758257269386 │ [ '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599' ] │ 'haBTC Stability Pool (BTC/fxUSD, BTC/stETH)' │ 'https://app.harborfinance.io/anchor' │
│ 1       │ '0x83fd69e0ff5767972b46e61c6833408361bf7346-ethereum' │ 'Ethereum' │ 'harbor' │ 'haEUR' │ 12310.67044180205 │ 6.18955902688851  │ [ '0xdb25f211ab05b1c97d595516f45794528a807ad8' ] │ 'haEUR Stability Pool (EUR/fxUSD, EUR/stETH)' │ 'https://app.harborfinance.io/anchor' │
└─────────┴───────────────────────────────────────────────────────┴────────────┴──────────┴─────────┴───────────────────┴───────────────────┴──────────────────────────────────────────────────┴───────────────────────────────────────────────┴───────────────────────────────────────┘

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.

1 participant