Skip to content

feat: support yield plus api#5462

Closed
cuzz-venus wants to merge 13 commits intofeat/yield-plusfrom
feat/yield-plus-api
Closed

feat: support yield plus api#5462
cuzz-venus wants to merge 13 commits intofeat/yield-plusfrom
feat/yield-plus-api

Conversation

@cuzz-venus
Copy link
Copy Markdown
Contributor

@cuzz-venus cuzz-venus commented Mar 18, 2026

Jira ticket(s)

VEN-626

Changes

  • support api for yield plus

Summary by CodeRabbit

  • New Features

    • Integrated DEX K-line candle data with historical fetching and live WebSocket streaming.
    • Real-time price change percentages now display in YieldPlus.
    • K-line chart dynamically updates with live candle data.
    • Enabled YieldPlus feature on BSC mainnet.
  • Tests

    • Updated YieldPlus tests with improved pool data mocking.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 18, 2026

⚠️ No Changeset found

Latest commit: f74e440

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dapp-preview Ready Ready Preview Mar 19, 2026 3:12pm
dapp-testnet Ready Ready Preview Mar 19, 2026 3:12pm
venus.io Ready Ready Preview Mar 19, 2026 3:12pm

Request Review

@cuzz-venus cuzz-venus changed the base branch from main to feat/yield-plus March 18, 2026 15:10
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 18, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 892eb64d-ebf2-4b41-88a5-e62908e89e6f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces the YieldPlus feature for tracking leveraged yield positions, including new API queries for position data and DEX K-line candles, a new YieldPlus page with interactive UI components, refactored transaction and table components, K-line chart visualization, and comprehensive translations across multiple languages.

Changes

Cohort / File(s) Summary
YieldPlus Page Structure
apps/evm/src/pages/YieldPlus/index.tsx, apps/evm/src/pages/YieldPlus/Banner/*, apps/evm/src/pages/YieldPlus/PairInfo/*, apps/evm/src/pages/YieldPlus/Positions/*, apps/evm/src/pages/YieldPlus/TokenPair/*, apps/evm/src/pages/YieldPlus/useTokenPair/index.ts, apps/evm/src/pages/YieldPlus/constants/index.ts
New YieldPlus page with feature banner, pair information display, K-line chart integration, and positions list with accordion details; includes token selection, liquidity/APY display, and position tracking UI.
API Queries - Yield+ Positions
apps/evm/src/clients/api/queries/getYieldPlusPositions/*, apps/evm/src/clients/api/__mocks__/index.ts, apps/evm/src/clients/api/index.ts
New async API query to fetch yield-plus positions with pool resolution, concurrent data fetching, error handling, and support for partial failures; includes types, hook wrapper, and mock implementations.
API Queries - DEX K-line Candles
apps/evm/src/clients/api/queries/getDexKlineCandles/*, apps/evm/src/clients/api/__mocks__/index.ts, apps/evm/src/clients/api/index.ts
New API query to fetch DEX K-line candle data with configurable parameters (platform, interval, limit, time range); includes type definitions, data transformation logic, and React Query hook with chainId-based platform resolution.
K-line Chart Component
apps/evm/src/components/KLineChart/*
New KLineChart component using klinecharts library with custom styling, resize handling via ResizeObserver, data loader configuration, and support for period and title updates.
Transaction Components Refactoring
apps/evm/src/components/TransactionsList/*, apps/evm/src/pages/Dashboard/Transactions/TransactionsList/, apps/evm/src/pages/Dashboard/Transactions/index.tsx
Moved TransactionsList from pages to components directory; added TransactionRow and TransactionDetails components; refactored transaction pagination into Transactions page; added getTransactionIcon utility.
Table Component Overhaul
apps/evm/src/components/Table/*, apps/evm/src/components/Table/TableCards/*, apps/evm/src/components/Table/Head/index.tsx
Replaced rowControlOnClick with renderRowControl and renderRowFooter callbacks; added new Head component for sortable table headers; removed MarketCard; updated Table styling and layout; supports tableLayout prop.
Pool Query Updates
apps/evm/src/clients/api/queries/getAccountTransactionHistory/*, apps/evm/src/clients/api/queries/useGetPools/*
Made poolLensContractAddress optional in GetPoolsInput; added positionAccountAddress to transaction history queries; updated type signatures and API call parameters.
Input Components Styling
apps/evm/src/components/ButtonGroup/index.tsx, apps/evm/src/components/Cell/index.tsx, apps/evm/src/components/CellGroup/index.tsx, apps/evm/src/components/Apy/PrimeBadge/PrimeApy/index.tsx, apps/evm/src/components/Tabs/index.tsx
Minor styling updates: ButtonGroup event handling, Cell typography class change, CellGroup layout constraints, flex utilities for TokenIcon, event propagation in Tabs.
Component Props Extensions
apps/evm/src/components/LayeredValues/index.tsx, apps/evm/src/components/TokenListWrapper/index.tsx, packages/ui/src/components/Button/index.tsx
Added optional className props to LayeredValues and TokenListWrapper; introduced SelectButton variant to UI Button component.
Placeholder Component
apps/evm/src/containers/Placeholder/index.tsx, apps/evm/src/containers/MarketTable/index.tsx
New Placeholder container component for empty states with optional CTA; updated MarketTable to use renderRowControl pattern instead of rowControlOnClick.
Feature Flags & Routing
apps/evm/src/hooks/useIsFeatureEnabled/index.tsx, apps/evm/src/constants/routing.ts, apps/evm/src/containers/Layout/NavBar/useMenuItems/index.tsx, apps/evm/src/App/Routes/index.tsx
Added yieldPlus feature flag for BSC mainnet/testnet; added YIELD_PLUS route subdirectory; integrated YieldPlus page route with feature flag guard; added YieldPlus menu item.
Configuration & Constants
apps/evm/src/config/envVariables.ts, apps/evm/src/config/index.ts, apps/evm/src/constants/functionKey.ts, apps/evm/src/types/index.ts
Added VITE_DEX_API_URL environment variable and dexApiUrl config field; added GET_YIELD_PLUS_POSITIONS and GET_DEX_KLINE_CANDLES function keys; added YieldPlusPosition type.
Contract ABI
apps/evm/src/libs/contracts/config/externalAbis/RelativePositionManager.json, apps/evm/src/libs/contracts/config/index.ts
Added comprehensive RelativePositionManager contract ABI with constructor, events, errors, and extensive function signatures for position management.
Translations
apps/evm/src/libs/translations/translations/*.json
Added yieldPlus translation blocks across all 7 languages (en, ja, th, tr, vi, zh-Hans, zh-Hant); simplified transaction type filters; updated enterMarket/exitMarket labels; removed obsolete vault-related translations.
Utilities & Mocks
apps/evm/src/utilities/getTransactionName/index.ts, apps/evm/src/utilities/restService.ts, apps/evm/src/utilities/index.ts, apps/evm/src/__mocks__/models/*, apps/evm/src/setupTests.tsx
Added getTransactionName utility; exported createQueryParams; added yieldPlusPositions mock data and tests; updated setupTests configuration.
Package Updates
.changeset/*.md, apps/evm/package.json, packages/ui/src/components/Button/index.stories.tsx
Added changesets documenting minor version bump and input component refactor; added klinecharts ^10.0.0-beta1 dependency; added SelectButton story.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as YieldPlus Page
    participant Hooks as React Hooks
    participant API as API Client
    participant DEX as DEX API

    User->>App: View YieldPlus Page
    App->>Hooks: useGetYieldPlusPositions(accountAddress)
    Hooks->>API: getYieldPlusPositions()
    API->>API: Fetch positions from REST
    API->>Hooks: getPools() for each position
    Hooks->>API: Concurrent pool resolution
    API->>Hooks: Return enriched positions
    
    App->>Hooks: useGetDexKlineCandles(token, interval)
    Hooks->>DEX: Fetch K-line candles
    DEX->>Hooks: Return candle data
    
    Hooks->>App: Positions + Candles
    App->>App: Calculate financial metrics (PnL, liquidation price, APY)
    App->>User: Render interactive positions table with K-line chart
    
    User->>App: Click position row
    App->>App: Toggle accordion / Load RowFooter
    App->>Hooks: useGetAccountTransactionHistory(positionAccountAddress)
    Hooks->>API: Fetch transactions for position
    API->>Hooks: Return transaction history
    Hooks->>App: Transactions data
    App->>User: Display status and transaction tabs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A new yield awaits in the fields so bright,
With positions tracked and K-lines in sight!
Charts dance and tables sing their tale,
Translations bloom to never fail. 🌸

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/yield-plus-api

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 18, 2026

Greptile Summary

This PR wires up real DEX K-line data to the YieldPlus page, replacing hardcoded placeholder values with a REST fetch for historical candles (useGetDexKlineCandles) and a shared WebSocket client for live updates (useDexKlineWebSocket/DexKlineWsClient). It also enables the yieldPlus feature flag on BSC mainnet.

Key changes:

  • New WsClient base class (libs/ws) — generic reconnect logic with exponential backoff (3–30 s), shared across channels via a module-level singleton in useDexKlineWebSocket.
  • changePercentage computation — correctly detects whether the live candle is a same-period update or a new period by comparing timestamps, then calculates the percentage change relative to the appropriate previous close.
  • KLineChart live updatessubscribeBar/unsubscribeBar handlers store the chart library's internal callback ref, which is invoked whenever a new liveCandle prop arrives.
  • staleTime: Infinity on useGetDexKlineCandles means historical candle data is never refreshed. If the page stays open across a daily candle boundary, the changePercentage logic will compare the live candle's close to the last candle in the original REST response (which may be several periods old), silently producing a misleading multi-period change percentage.
  • The reconnectDelay in WsClient is not reset when all channels close, so a reconnect after a quiet period starts with the accumulated backoff delay rather than the 3 s base.

Confidence Score: 3/5

  • Safe to merge for initial testing, but the stale historical data issue should be addressed before the feature is widely used on mainnet.
  • The WebSocket client and data-flow logic are well-structured and the core functionality works correctly for normal usage. The main concern is staleTime: Infinity in useGetDexKlineCandles — when the page is kept open past a daily candle boundary the displayed change percentage will silently become a multi-day figure rather than a 1-day figure. This is not a crash but is a meaningful data-accuracy regression visible to users. Two issues from prior review threads (self-import and unguarded response.json()) also remain open.
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts (staleTime), apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts (unguarded json parse — noted in prior threads)

Important Files Changed

Filename Overview
apps/evm/src/libs/ws/WsClient.ts New abstract WebSocket base class with exponential backoff reconnect (3–30 s). Logic is solid — the this.ws !== ws guard in onclose correctly prevents stale reconnects after disconnect(). Minor: reconnectDelay is never reset to the base value when all channels close, so a fresh subscription after a period of inactivity starts with the accumulated backoff delay.
apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts Concrete WebSocket client for DEX K-line data. Subscribe/unsubscribe management via a Map of Sets is correct; length guard (< 6) before destructuring the OHLCV array prevents runtime errors from malformed messages.
apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts React Query hook for historical K-line REST data. The enabled: !!platform guard correctly disables the query on unsupported chains. However, staleTime: Infinity means data is never refreshed; if the page stays open past a daily candle boundary the changePercentage calculation in the parent will silently compare the live candle's close against a several-periods-old historical close.
apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts Hook for live candle streaming via a module-level shared DexKlineWsClient. Callback-ref pattern correctly avoids stale closures. Module singleton is well-managed — the underlying WebSocket is disconnected when active channels reach zero.
apps/evm/src/pages/YieldPlus/index.tsx Orchestrates historical fetch + live WebSocket streaming; computes changePercentage and passes it down. The liveCandle reset effect when longTokenDexAddress changes is correct. The timestamp-based period detection in the changePercentage memo is logically sound for normal usage but can drift when the page is kept open across candle period boundaries due to staleTime: Infinity.
apps/evm/src/components/KLineChart/index.tsx Adds liveCandle prop and subscribeCallbackRef to forward live updates to the chart library. The subscribeBar/unsubscribeBar handlers are correctly registered in both the init and data-update effects; cleanup nulls the ref before chart disposal.
apps/evm/src/pages/YieldPlus/PairInfo/index.tsx Removes the hardcoded changePercentage = 3.32 TODO and replaces it with a prop. Correctly gates formatting behind undefined and applies red/green colour based on sign.

Last reviewed commit: "feat: add more timer..."

@cuzz-venus
Copy link
Copy Markdown
Contributor Author

@greptile

Copy link
Copy Markdown

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
apps/evm/src/utilities/restService.ts (1)

30-38: ⚠️ Potential issue | 🟡 Minor

Missing URL encoding for query parameter values.

Now that createQueryParams is exported and used across modules, the lack of URL encoding could cause issues with special characters in parameter values.

🐛 Proposed fix
 export const createQueryParams = (params: Record<string, unknown>) => {
   const paramArray = Object.entries(params).map(([key, value]) => {
     if (value !== undefined && value !== null) {
-      return `${key}=${value}`;
+      return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
     }
     return '';
   });
   return paramArray.filter(p => p).join('&');
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/utilities/restService.ts` around lines 30 - 38, The
createQueryParams function does not URL-encode keys or values; update
createQueryParams to call encodeURIComponent on both the key and the stringified
value (e.g., encodeURIComponent(String(value))) before joining, skip
undefined/nulls as before, and continue filtering out empty entries so special
characters are safely encoded; reference createQueryParams to locate the change.
🧹 Nitpick comments (11)
apps/evm/src/utilities/restService.ts (1)

54-64: Duplicate code block.

The Accept-Version header is set twice with identical logic at lines 54-58 and 60-64.

♻️ Remove duplicate block
   if (next) {
     _set(headers, 'Accept-Version', 'next');
   } else {
     _set(headers, 'Accept-Version', 'stable');
   }

-  if (next) {
-    _set(headers, 'Accept-Version', 'next');
-  } else {
-    _set(headers, 'Accept-Version', 'stable');
-  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/utilities/restService.ts` around lines 54 - 64, Remove the
duplicated Accept-Version header logic in restService.ts: the conditional block
that calls _set(headers, 'Accept-Version', next|stable) appears twice; keep a
single instance (referencing the same _set function, headers variable, and the
next flag) and delete the redundant second block so the header is only set once.
apps/evm/src/libs/ws/WsClient.ts (2)

76-81: Consider adding exponential backoff and retry limits for reconnection.

The current implementation uses a fixed 3-second delay and will retry indefinitely while channels remain active. This could cause issues if the server is down for an extended period (battery drain on mobile, excessive logs, etc.).

♻️ Suggested implementation with exponential backoff
 export abstract class WsClient {
   private ws: WebSocket | null = null;
   private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
   private isConnecting = false;
   private readonly activeChannels = new Set<string>();
+  private reconnectAttempts = 0;
+  private static readonly MAX_RECONNECT_ATTEMPTS = 10;
+  private static readonly BASE_DELAY_MS = 1000;
+  private static readonly MAX_DELAY_MS = 30000;

   // ... other methods ...

-  private scheduleReconnect(delayMs = 3000): void {
+  private scheduleReconnect(): void {
+    if (this.reconnectAttempts >= WsClient.MAX_RECONNECT_ATTEMPTS) {
+      console.warn('WebSocket max reconnect attempts reached');
+      return;
+    }
+    const delayMs = Math.min(
+      WsClient.BASE_DELAY_MS * 2 ** this.reconnectAttempts,
+      WsClient.MAX_DELAY_MS,
+    );
+    this.reconnectAttempts++;
     this.reconnectTimer = setTimeout(() => {
       this.reconnectTimer = null;
       this.connect();
     }, delayMs);
   }

Also reset reconnectAttempts = 0 in onopen callback upon successful connection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/libs/ws/WsClient.ts` around lines 76 - 81, The reconnect logic
in scheduleReconnect currently uses a fixed 3s delay and can run indefinitely;
change scheduleReconnect to implement exponential backoff with a baseDelay
(e.g., 3000ms), a backoff multiplier (e.g., 2), and a maxDelay cap, and add a
maxReconnectAttempts limit; increment reconnectAttempts each time
scheduleReconnect schedules a retry and clear/stop scheduling when
reconnectAttempts >= maxReconnectAttempts, and ensure reconnectTimer behavior
remains unchanged. Also reset reconnectAttempts = 0 in the WebSocket onopen
handler (the onopen callback) upon successful connection so future disconnects
start backoff from the baseDelay. Ensure to reference and update the existing
fields/methods: scheduleReconnect, reconnectTimer, reconnectAttempts, connect,
and the onopen handler.

55-55: WebSocket data may not always be a string.

The MessageEvent.data property can be string, Blob, or ArrayBuffer depending on the binaryType setting. If the server ever sends binary data, casting to string will produce unexpected results.

♻️ Suggested validation
-    this.ws.onmessage = ({ data }) => this.onMessage(data as string);
+    this.ws.onmessage = ({ data }) => {
+      if (typeof data === 'string') {
+        this.onMessage(data);
+      }
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/libs/ws/WsClient.ts` at line 55, The assignment that assumes
MessageEvent.data is always a string is unsafe; update the this.ws.onmessage
handler in WsClient so it detects the data type and converts it to a string
before calling onMessage: if data is a string call this.onMessage(data), if data
is a Blob use await data.text() (so make the handler async or dispatch to an
async helper), and if data is an ArrayBuffer decode it (e.g., using TextDecoder)
to a string; ensure onMessage is invoked only with a string and handle/propagate
any conversion errors appropriately.
apps/evm/src/clients/api/__mocks__/index.ts (1)

678-686: Mock queryKey differs from real implementation.

The real useGetDexKlineCandles hook uses queryKey: [FunctionKey.GET_DEX_KLINE_CANDLES, { ...params, chainId }], but this mock uses a simpler key without parameters. This is consistent with other mocks in this file, but could cause issues if tests rely on specific queryKey matching for cache invalidation or query filtering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/__mocks__/index.ts` around lines 678 - 686, The mock
useGetDexKlineCandles uses a simplified queryKey that doesn't match the real
hook; update the mock so useGetDexKlineCandles returns useQuery with queryKey:
[FunctionKey.GET_DEX_KLINE_CANDLES, { ...params, chainId }] (i.e., include the
params object and chainId like the real implementation) and keep queryFn:
getDexKlineCandles so tests that inspect or invalidate queries by key will match
the real behavior.
apps/evm/src/clients/api/index.ts (1)

217-227: Pre-existing duplicate exports detected.

Lines 217-218 and 223-224 both export getBurnedWBnb and useGetBurnedWBnb. Similarly, lines 220-221 and 226-227 both export getImportablePositions and useGetImportablePositions. While this doesn't break functionality (re-exports are idempotent), it adds noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/index.ts` around lines 217 - 227, Duplicate
re-exports for './queries/getBurnedWBnb' (exporting getBurnedWBnb and
useGetBurnedWBnb) and './queries/getImportablePositions' (exporting
getImportablePositions and useGetImportablePositions) should be removed; edit
the export block to keep only one export line pair for each module (leave a
single "export * from './queries/getBurnedWBnb';" and "export * from
'./queries/getBurnedWBnb/useGetBurnedWBnb';" and a single "export * from
'./queries/getImportablePositions';" and "export * from
'./queries/getImportablePositions/useGetImportablePositions';"), deleting the
duplicate occurrences to eliminate noise.
apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts (1)

18-24: Singleton WebSocket client has no teardown mechanism.

The sharedClient singleton is created lazily but never destroyed. This is typically fine for app-lifetime resources, but if dexWsUrl could change at runtime (e.g., config reload), the stale client would persist.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts`
around lines 18 - 24, The singleton sharedClient (variable) created by
getSharedClient can become stale if config.dexWsUrl changes and has no teardown;
add a lifecycle API to allow closing/recreating the client: implement a
closeSharedClient (or resetSharedClient) function that calls the
DexKlineWsClient instance's close/terminate method, awaits cleanup if needed,
and sets sharedClient = null, and modify getSharedClient to detect a changed
config.dexWsUrl by storing the current url (e.g., lastWsUrl) and if it differs,
call the new close/reset function before constructing a new
DexKlineWsClient(config.dexWsUrl); expose the close/reset function where the app
can call it on config reload.
apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts (2)

38-40: Consider including HTTP status in error for debugging.

The error thrown on non-OK responses doesn't include the HTTP status code or response body, making debugging harder. Consider enriching the error context.

♻️ Suggested improvement for error context
   if (!response.ok) {
-    throw new VError({ type: 'unexpected', code: 'somethingWentWrong' });
+    throw new VError({
+      type: 'unexpected',
+      code: 'somethingWentWrong',
+      data: { status: response.status, statusText: response.statusText },
+    });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts` around lines 38
- 40, The error thrown when response.ok is false in the fetch block (the throw
new VError(...) line) should include HTTP status and response body for
debugging: capture response.status and response.statusText and attempt to read a
safe body (await response.text() or json() inside a try/catch), then rethrow a
VError (or augment the existing VError) including those fields (e.g., message
with status/statusText and a property like info: { status, statusText, body })
so callers can see HTTP details; update the code in the function that performs
the fetch (the response handling in index.ts) to build and throw this enriched
VError.

33-36: Consider adding request timeout handling.

The fetch call has no timeout, which could cause the request to hang indefinitely if the server doesn't respond. Consider using AbortController with a timeout.

♻️ Suggested timeout implementation
+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
+
   const response = await fetch(
     `${config.dexApiUrl}/k-line/candles?${queryParams}`,
-    { headers: { Accept: 'application/json' } },
+    { headers: { Accept: 'application/json' }, signal: controller.signal },
   );
+
+  clearTimeout(timeoutId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts` around lines 33
- 36, The fetch call in getDexKlineCandles can hang because it lacks timeout
handling; wrap the fetch call with an AbortController: create an
AbortController, start a setTimeout that calls controller.abort() after a chosen
timeout, pass controller.signal into fetch (the call that constructs
`response`), and clear the timeout after the fetch completes; also handle the
abort case in the surrounding function (e.g., catch and translate an AbortError
to a timed-out error or return a controlled error response). Ensure you
reference `config.dexApiUrl`, `queryParams`, and the `response` assignment when
adding the controller/signal so the change is localized to that fetch
invocation.
apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts (1)

13-20: Platform mapping duplicated across files.

Both this file and useDexKlineWebSocket.ts maintain separate chain-to-platform mappings (DEX_PLATFORM_BY_CHAIN_ID with strings here, PLATFORM_ID_BY_CHAIN_ID with numbers there). Consider consolidating into a shared constants file to ensure consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts`
around lines 13 - 20, DEX_PLATFORM_BY_CHAIN_ID here is duplicated with
PLATFORM_ID_BY_CHAIN_ID in another module; extract a single source of truth by
creating a shared constant (e.g., export const DEX_PLATFORM_BY_CHAIN_ID or
PLATFORM_MAP) and replace the local maps in useGetDexKlineCandles.ts and
useDexKlineWebSocket.ts to import that shared constant; update usage sites to
adapt to the shared shape (string keys/values or numeric IDs) and convert
callers if needed so both getDexKlineCandles and useDexKlineWebSocket reference
the same mapping symbol.
apps/evm/src/components/KLineChart/index.tsx (1)

106-116: Consider extracting duplicate setDataLoader configuration.

The subscribeBar and unsubscribeBar handlers are duplicated between the initial setup (lines 110-115) and the data-change effect (lines 147-152). Extracting a helper could reduce duplication.

♻️ Potential refactor to reduce duplication
+  const createDataLoader = (candleData: KLineData[]) => ({
+    getBars: ({ callback }: { callback: (data: KLineData[]) => void }) => {
+      callback(candleData);
+    },
+    subscribeBar: ({ callback }: { callback: (data: KLineData) => void }) => {
+      subscribeCallbackRef.current = callback;
+    },
+    unsubscribeBar: () => {
+      subscribeCallbackRef.current = null;
+    },
+  });

   // In mount effect:
-  chart.setDataLoader({
-    getBars: ({ callback }) => {
-      callback(latestDataRef.current);
-    },
-    subscribeBar: ({ callback }) => {
-      subscribeCallbackRef.current = callback;
-    },
-    unsubscribeBar: () => {
-      subscribeCallbackRef.current = null;
-    },
-  });
+  chart.setDataLoader(createDataLoader(latestDataRef.current));

   // In data effect:
-  chartRef.current?.setDataLoader({
-    getBars: ({ callback }) => {
-      callback(data);
-    },
-    subscribeBar: ({ callback }) => {
-      subscribeCallbackRef.current = callback;
-    },
-    unsubscribeBar: () => {
-      subscribeCallbackRef.current = null;
-    },
-  });
+  chartRef.current?.setDataLoader(createDataLoader(data));

Also applies to: 140-154

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/components/KLineChart/index.tsx` around lines 106 - 116,
Duplicate setDataLoader subscription logic should be extracted into a small
helper to avoid repeating subscribeBar/unsubscribeBar setup; create a function
(e.g., initChartDataLoader or configureChartSubscriptions) that accepts the
chart instance and wires getBars (using latestDataRef.current), subscribeBar
(assigning subscribeCallbackRef.current), and unsubscribeBar (clearing
subscribeCallbackRef.current), then call that helper both in the initial chart
setup and inside the data-change effect instead of duplicating the
subscribe/unsubscribe blocks.
apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts (1)

59-59: Protect fanout from subscriber exceptions.

A thrown error in one callback can interrupt delivery to the remaining subscribers on the same channel. Wrap each callback invocation defensively.

Suggested refactor
-    this.callbacks.get(channel)?.forEach(cb => cb(candle));
+    for (const cb of this.callbacks.get(channel) ?? []) {
+      try {
+        cb(candle);
+      } catch {
+        // Optional: report error via app logger/telemetry
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts` at
line 59, The fanout loop in DexKlineWsClient that currently does
"this.callbacks.get(channel)?.forEach(cb => cb(candle))" must be defensive:
iterate the subscriber list (this.callbacks.get(channel)) and invoke each
callback inside a try/catch so a thrown error from one cb does not stop delivery
to others; on catch, log or handle the error (use the class logger or console
with context including channel and the cb identity if available) and continue to
the next subscriber. Ensure you update the method that processes incoming
candles (where "callbacks" and "channel" are referenced) to use this
per-callback try/catch pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts`:
- Around line 54-57: The current check only verifies usdOhlcv length; update
DexKlineWsClient handling so before constructing DexKlineCandle you validate
that each element in usdOhlcv (o,h,l,c,v,ts) is a finite number (and timestamp
is an integer/finite number) and not NaN/undefined; if any value fails
validation, bail out (return) or skip the message to avoid poisoning chart
state, otherwise safely use the numeric values to build the candle object;
reference usdOhlcv, DexKlineCandle, and the candle construction site to locate
where to add these checks.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts`:
- Around line 30-39: ApiDexKlineCandle currently documents the 7th element
("ut") as optional but defines it as required (nullable), causing 6-element
payloads to be rejected; update the tuple type ApiDexKlineCandle so the 7th
element is optional (e.g., make the final tuple element have an optional
modifier: (number | null)?), and keep the inline comment describing ut as
optional so 6- and 7-element arrays are both accepted.

---

Outside diff comments:
In `@apps/evm/src/utilities/restService.ts`:
- Around line 30-38: The createQueryParams function does not URL-encode keys or
values; update createQueryParams to call encodeURIComponent on both the key and
the stringified value (e.g., encodeURIComponent(String(value))) before joining,
skip undefined/nulls as before, and continue filtering out empty entries so
special characters are safely encoded; reference createQueryParams to locate the
change.

---

Nitpick comments:
In `@apps/evm/src/clients/api/__mocks__/index.ts`:
- Around line 678-686: The mock useGetDexKlineCandles uses a simplified queryKey
that doesn't match the real hook; update the mock so useGetDexKlineCandles
returns useQuery with queryKey: [FunctionKey.GET_DEX_KLINE_CANDLES, { ...params,
chainId }] (i.e., include the params object and chainId like the real
implementation) and keep queryFn: getDexKlineCandles so tests that inspect or
invalidate queries by key will match the real behavior.

In `@apps/evm/src/clients/api/index.ts`:
- Around line 217-227: Duplicate re-exports for './queries/getBurnedWBnb'
(exporting getBurnedWBnb and useGetBurnedWBnb) and
'./queries/getImportablePositions' (exporting getImportablePositions and
useGetImportablePositions) should be removed; edit the export block to keep only
one export line pair for each module (leave a single "export * from
'./queries/getBurnedWBnb';" and "export * from
'./queries/getBurnedWBnb/useGetBurnedWBnb';" and a single "export * from
'./queries/getImportablePositions';" and "export * from
'./queries/getImportablePositions/useGetImportablePositions';"), deleting the
duplicate occurrences to eliminate noise.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts`:
- Line 59: The fanout loop in DexKlineWsClient that currently does
"this.callbacks.get(channel)?.forEach(cb => cb(candle))" must be defensive:
iterate the subscriber list (this.callbacks.get(channel)) and invoke each
callback inside a try/catch so a thrown error from one cb does not stop delivery
to others; on catch, log or handle the error (use the class logger or console
with context including channel and the cb identity if available) and continue to
the next subscriber. Ensure you update the method that processes incoming
candles (where "callbacks" and "channel" are referenced) to use this
per-callback try/catch pattern.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts`:
- Around line 38-40: The error thrown when response.ok is false in the fetch
block (the throw new VError(...) line) should include HTTP status and response
body for debugging: capture response.status and response.statusText and attempt
to read a safe body (await response.text() or json() inside a try/catch), then
rethrow a VError (or augment the existing VError) including those fields (e.g.,
message with status/statusText and a property like info: { status, statusText,
body }) so callers can see HTTP details; update the code in the function that
performs the fetch (the response handling in index.ts) to build and throw this
enriched VError.
- Around line 33-36: The fetch call in getDexKlineCandles can hang because it
lacks timeout handling; wrap the fetch call with an AbortController: create an
AbortController, start a setTimeout that calls controller.abort() after a chosen
timeout, pass controller.signal into fetch (the call that constructs
`response`), and clear the timeout after the fetch completes; also handle the
abort case in the surrounding function (e.g., catch and translate an AbortError
to a timed-out error or return a controlled error response). Ensure you
reference `config.dexApiUrl`, `queryParams`, and the `response` assignment when
adding the controller/signal so the change is localized to that fetch
invocation.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts`:
- Around line 18-24: The singleton sharedClient (variable) created by
getSharedClient can become stale if config.dexWsUrl changes and has no teardown;
add a lifecycle API to allow closing/recreating the client: implement a
closeSharedClient (or resetSharedClient) function that calls the
DexKlineWsClient instance's close/terminate method, awaits cleanup if needed,
and sets sharedClient = null, and modify getSharedClient to detect a changed
config.dexWsUrl by storing the current url (e.g., lastWsUrl) and if it differs,
call the new close/reset function before constructing a new
DexKlineWsClient(config.dexWsUrl); expose the close/reset function where the app
can call it on config reload.

In
`@apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts`:
- Around line 13-20: DEX_PLATFORM_BY_CHAIN_ID here is duplicated with
PLATFORM_ID_BY_CHAIN_ID in another module; extract a single source of truth by
creating a shared constant (e.g., export const DEX_PLATFORM_BY_CHAIN_ID or
PLATFORM_MAP) and replace the local maps in useGetDexKlineCandles.ts and
useDexKlineWebSocket.ts to import that shared constant; update usage sites to
adapt to the shared shape (string keys/values or numeric IDs) and convert
callers if needed so both getDexKlineCandles and useDexKlineWebSocket reference
the same mapping symbol.

In `@apps/evm/src/components/KLineChart/index.tsx`:
- Around line 106-116: Duplicate setDataLoader subscription logic should be
extracted into a small helper to avoid repeating subscribeBar/unsubscribeBar
setup; create a function (e.g., initChartDataLoader or
configureChartSubscriptions) that accepts the chart instance and wires getBars
(using latestDataRef.current), subscribeBar (assigning
subscribeCallbackRef.current), and unsubscribeBar (clearing
subscribeCallbackRef.current), then call that helper both in the initial chart
setup and inside the data-change effect instead of duplicating the
subscribe/unsubscribe blocks.

In `@apps/evm/src/libs/ws/WsClient.ts`:
- Around line 76-81: The reconnect logic in scheduleReconnect currently uses a
fixed 3s delay and can run indefinitely; change scheduleReconnect to implement
exponential backoff with a baseDelay (e.g., 3000ms), a backoff multiplier (e.g.,
2), and a maxDelay cap, and add a maxReconnectAttempts limit; increment
reconnectAttempts each time scheduleReconnect schedules a retry and clear/stop
scheduling when reconnectAttempts >= maxReconnectAttempts, and ensure
reconnectTimer behavior remains unchanged. Also reset reconnectAttempts = 0 in
the WebSocket onopen handler (the onopen callback) upon successful connection so
future disconnects start backoff from the baseDelay. Ensure to reference and
update the existing fields/methods: scheduleReconnect, reconnectTimer,
reconnectAttempts, connect, and the onopen handler.
- Line 55: The assignment that assumes MessageEvent.data is always a string is
unsafe; update the this.ws.onmessage handler in WsClient so it detects the data
type and converts it to a string before calling onMessage: if data is a string
call this.onMessage(data), if data is a Blob use await data.text() (so make the
handler async or dispatch to an async helper), and if data is an ArrayBuffer
decode it (e.g., using TextDecoder) to a string; ensure onMessage is invoked
only with a string and handle/propagate any conversion errors appropriately.

In `@apps/evm/src/utilities/restService.ts`:
- Around line 54-64: Remove the duplicated Accept-Version header logic in
restService.ts: the conditional block that calls _set(headers, 'Accept-Version',
next|stable) appears twice; keep a single instance (referencing the same _set
function, headers variable, and the next flag) and delete the redundant second
block so the header is only set once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29f7a4b2-ebbe-40f8-be7c-2ee5df2e855a

📥 Commits

Reviewing files that changed from the base of the PR and between c0413b3 and fa45f6b.

📒 Files selected for processing (18)
  • apps/evm/src/clients/api/__mocks__/index.ts
  • apps/evm/src/clients/api/index.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts
  • apps/evm/src/components/KLineChart/index.tsx
  • apps/evm/src/config/envVariables.ts
  • apps/evm/src/config/index.ts
  • apps/evm/src/constants/functionKey.ts
  • apps/evm/src/hooks/useIsFeatureEnabled/index.tsx
  • apps/evm/src/libs/ws/WsClient.ts
  • apps/evm/src/libs/ws/index.ts
  • apps/evm/src/pages/YieldPlus/PairInfo/index.tsx
  • apps/evm/src/pages/YieldPlus/index.tsx
  • apps/evm/src/setupTests.tsx
  • apps/evm/src/utilities/restService.ts

@cuzz-venus
Copy link
Copy Markdown
Contributor Author

@greptile

Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts (1)

30-39: ⚠️ Potential issue | 🟠 Major

ApiDexKlineCandle still contradicts optional ut contract.

Line 30 documents ut? as optional, but Line 38 makes the 7th tuple element required (nullable only). This can mis-type valid 6-element candles.

Suggested fix
 export type ApiDexKlineCandle = [
   number, // o - open price
   number, // h - highest price
   number, // l - lowest price
   number, // c - close price
   number, // v - volume
   number, // ts - opening time (Unix ms)
-  number | null, // ut - unique traders (optional)
+  (number | null)?, // ut - unique traders (optional)
 ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts` around lines 30
- 39, ApiDexKlineCandle’s seventh tuple entry is documented as optional but
currently typed as required (number | null); update the tuple so the seventh
element is optional. In
apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts change the tuple
declaration for ApiDexKlineCandle so the 7th element is typed as (number |
null)? (i.e., make the ut element optional) to allow 6- and 7-element candles.
🧹 Nitpick comments (5)
apps/evm/src/libs/ws/WsClient.ts (1)

80-85: Consider exponential backoff for reconnection.

The fixed 3-second delay works but exponential backoff with a cap would be more resilient under sustained failures (e.g., network outages), reducing unnecessary retry load and improving recovery behavior.

♻️ Proposed exponential backoff pattern
+ private reconnectAttempt = 0;
+ private readonly maxReconnectDelay = 30000;

- private scheduleReconnect(delayMs = 3000): void {
+ private scheduleReconnect(): void {
+   const delayMs = Math.min(1000 * 2 ** this.reconnectAttempt, this.maxReconnectDelay);
+   this.reconnectAttempt++;
    this.reconnectTimer = setTimeout(() => {
      this.reconnectTimer = null;
      this.connect();
    }, delayMs);
  }

  // Reset attempt counter on successful connection:
  this.ws.onopen = () => {
    this.isConnecting = false;
+   this.reconnectAttempt = 0;
    for (const channel of this.activeChannels) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/libs/ws/WsClient.ts` around lines 80 - 85, Replace the fixed
delay in scheduleReconnect with an exponential backoff: add a numeric state
(e.g., this.reconnectAttempts) that increments each time scheduleReconnect runs,
compute delayMs = Math.min(baseDelayMs * 2 ** this.reconnectAttempts,
maxDelayMs) (choose baseDelayMs like 3000 and a max cap), call setTimeout to
invoke this.connect(), store/clear this.reconnectTimer as before, and reset
this.reconnectAttempts to 0 on successful connection inside connect() (or on
open event) so retries start from base again; keep existing reconnectTimer
cancellation behavior unchanged.
apps/evm/src/components/KLineChart/index.tsx (1)

106-116: Consider extracting duplicated setDataLoader configuration.

The setDataLoader configuration with getBars, subscribeBar, and unsubscribeBar is duplicated in both the initial setup effect (lines 106-116) and the data-driven effect (lines 143-153). This creates a maintenance burden where changes need to be synchronized in two places.

♻️ Proposed refactor to extract loader factory
+ const createDataLoader = (candles: KLineData[]) => ({
+   getBars: ({ callback }: { callback: (data: KLineData[]) => void }) => {
+     callback(candles);
+   },
+   subscribeBar: ({ callback }: { callback: (data: KLineData) => void }) => {
+     subscribeCallbackRef.current = callback;
+   },
+   unsubscribeBar: () => {
+     subscribeCallbackRef.current = null;
+   },
+ });

  // In initial setup effect:
- chart.setDataLoader({
-   getBars: ({ callback }) => {
-     callback(latestDataRef.current);
-   },
-   subscribeBar: ({ callback }) => {
-     subscribeCallbackRef.current = callback;
-   },
-   unsubscribeBar: () => {
-     subscribeCallbackRef.current = null;
-   },
- });
+ chart.setDataLoader(createDataLoader(latestDataRef.current));

  // In data effect:
- chartRef.current?.setDataLoader({
-   getBars: ({ callback }) => {
-     callback(data);
-   },
-   subscribeBar: ({ callback }) => {
-     subscribeCallbackRef.current = callback;
-   },
-   unsubscribeBar: () => {
-     subscribeCallbackRef.current = null;
-   },
- });
+ chartRef.current?.setDataLoader(createDataLoader(data));

Also applies to: 143-153

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/components/KLineChart/index.tsx` around lines 106 - 116, Extract
the duplicated setDataLoader configuration into a single factory/helper and use
it in both places: create a function (e.g., buildDataLoader) that returns an
object with getBars, subscribeBar, and unsubscribeBar implementations
referencing latestDataRef and subscribeCallbackRef, then call
chart.setDataLoader(buildDataLoader()) wherever the loader is currently
duplicated (both in the initial setup effect and the data-driven effect); ensure
function uses the same closure variables (latestDataRef, subscribeCallbackRef,
chart) so the behavior remains identical and any future changes are made in one
place.
apps/evm/src/pages/YieldPlus/index.tsx (1)

29-35: Consider edge case when wrapped token lookup fails for native tokens.

When longToken.isNative is true and no wrapped token is found, this falls back to longToken.address. Native token addresses typically can't be used directly in DEX queries. The fallback may cause API errors or return no data.

♻️ Suggested improvement
 const longTokenDexAddress = useMemo(() => {
   if (!longToken.isNative) return longToken.address;
   const wrappedToken = tokens.find(t =>
     areAddressesEqual(t.tokenWrapped?.address ?? '', longToken.address),
   );
-  return wrappedToken?.address ?? longToken.address;
+  return wrappedToken?.address;
 }, [longToken, tokens]);

Then disable the kline queries when longTokenDexAddress is undefined:

 const { data: klineData } = useGetDexKlineCandles({
   address: longTokenDexAddress,
   interval: KLINE_INTERVAL,
   limit: KLINE_LIMIT,
-});
+}, { enabled: !!longTokenDexAddress });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/pages/YieldPlus/index.tsx` around lines 29 - 35, The current
useMemo for longTokenDexAddress returns longToken.address when a native token's
wrapped lookup fails, which can lead to invalid DEX queries; change the logic in
the useMemo (the longTokenDexAddress computation that uses areAddressesEqual,
longToken and tokens) to return undefined (or null) when longToken.isNative and
no wrappedToken is found instead of falling back to longToken.address, and then
ensure any kline queries or callers that depend on longTokenDexAddress are
guarded/disabled when it is undefined (e.g., skip or early-return in the kline
query hook or component that uses longTokenDexAddress).
apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts (1)

33-40: Consider adding a timeout for the fetch request.

The fetch call has no timeout, which could cause the request to hang indefinitely if the DEX API is unresponsive. Consider using AbortController with a timeout.

♻️ Proposed refactor with timeout
+const FETCH_TIMEOUT_MS = 30_000;
+
 export const getDexKlineCandles = async ({
   platform,
   address,
   interval,
   limit,
   from,
   to,
   unit,
   tokenIndex,
 }: GetDexKlineCandlesInput): Promise<GetDexKlineCandlesOutput> => {
   const queryParams = createQueryParams({
     platform,
     address,
     interval,
     limit,
     from,
     to,
     unit,
     tokenIndex,
   });

+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
+
-  const response = await fetch(
-    `${config.dexApiUrl}/k-line/candles?${queryParams}`,
-    { headers: { Accept: 'application/json' } },
-  );
+  const response = await fetch(
+    `${config.dexApiUrl}/k-line/candles?${queryParams}`,
+    { headers: { Accept: 'application/json' }, signal: controller.signal },
+  ).finally(() => clearTimeout(timeoutId));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts` around lines 33
- 40, The fetch call that requests
`${config.dexApiUrl}/k-line/candles?${queryParams}` should use an
AbortController with a timeout to avoid hanging; create an AbortController, pass
its signal into fetch, start a setTimeout that calls controller.abort() after a
configurable ms, clear the timeout once the response arrives, and catch an
aborted/timeout error to throw a distinct VError (e.g., type 'timeout' or code
'requestTimedOut') instead of the generic unexpected VError; update the
fetch/response handling in the getDexKlineCandles flow to include these changes
and ensure the timeout is cleaned up on success or error.
apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts (1)

29-49: Narrow hook options to prevent overriding queryKey/queryFn and add explicit platform guard.

Spreading options typed as Partial<QueryObserverOptions<...>> allows callers to override queryKey or queryFn, which can silently break cache consistency. Additionally, while the enabled flag prevents query execution when platform is undefined, using an explicit error guard in queryFn is more defensive than relying on a non-null assertion.

Suggested change
 type Options = QueryObserverOptions<
   GetDexKlineCandlesOutput,
   Error,
   GetDexKlineCandlesOutput,
   GetDexKlineCandlesOutput,
   UseGetDexKlineCandlesQueryKey
 >;
 
 export const useGetDexKlineCandles = (
   params: TrimmedGetDexKlineCandlesInput,
-  options?: Partial<Options>,
+  options?: Partial<Omit<Options, 'queryKey' | 'queryFn'>>,
 ) => {
   const { chainId } = useChainId();
   const platform = DEX_PLATFORM_BY_CHAIN_ID.get(chainId);
 
   return useQuery({
     queryKey: [FunctionKey.GET_DEX_KLINE_CANDLES, { ...params, chainId }],
-    queryFn: () => getDexKlineCandles({ platform: platform!, ...params }),
+    queryFn: () => {
+      if (!platform) {
+        throw new Error(`Unsupported chainId for DEX kline candles: ${chainId}`);
+      }
+      return getDexKlineCandles({ platform, ...params });
+    },
     staleTime: Number.POSITIVE_INFINITY,
     ...options,
     enabled: !!platform && (options?.enabled ?? true),
   });
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts`
around lines 29 - 49, The hook useGetDexKlineCandles currently spreads a
Partial<Options> into useQuery which lets callers override core fields
(queryKey/queryFn) and also uses a non-null assertion on platform; fix it by
narrowing or omitting queryKey and queryFn from the passed options (e.g.
pick/omit so you spread only safe fields) and build queryKey/queryFn locally,
and change the queryFn to defensively guard against missing platform (throw or
return early) instead of using platform!; update useQuery call in
useGetDexKlineCandles to always supply the internally constructed queryKey and a
queryFn that checks DEX_PLATFORM_BY_CHAIN_ID.get(chainId) and calls
getDexKlineCandles({ platform, ...params }) only when platform is present, while
keeping the enabled flag logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/evm/src/libs/ws/WsClient.ts`:
- Line 59: The onmessage handler in WsClient (this.ws.onmessage) is casting
MessageEvent.data to string which can be Blob or ArrayBuffer in browsers; update
the handler to detect the runtime type and convert non-string payloads to string
before calling onMessage: if typeof data === 'string' call this.onMessage(data),
if data is an ArrayBuffer use TextDecoder to decode it, and if data is a Blob
use FileReader (or blob.text()) to asynchronously obtain text then call
this.onMessage; alternatively ensure ws.binaryType is set and handle accordingly
so onMessage always receives a proper string.

---

Duplicate comments:
In `@apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts`:
- Around line 30-39: ApiDexKlineCandle’s seventh tuple entry is documented as
optional but currently typed as required (number | null); update the tuple so
the seventh element is optional. In
apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts change the tuple
declaration for ApiDexKlineCandle so the 7th element is typed as (number |
null)? (i.e., make the ut element optional) to allow 6- and 7-element candles.

---

Nitpick comments:
In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts`:
- Around line 33-40: The fetch call that requests
`${config.dexApiUrl}/k-line/candles?${queryParams}` should use an
AbortController with a timeout to avoid hanging; create an AbortController, pass
its signal into fetch, start a setTimeout that calls controller.abort() after a
configurable ms, clear the timeout once the response arrives, and catch an
aborted/timeout error to throw a distinct VError (e.g., type 'timeout' or code
'requestTimedOut') instead of the generic unexpected VError; update the
fetch/response handling in the getDexKlineCandles flow to include these changes
and ensure the timeout is cleaned up on success or error.

In
`@apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts`:
- Around line 29-49: The hook useGetDexKlineCandles currently spreads a
Partial<Options> into useQuery which lets callers override core fields
(queryKey/queryFn) and also uses a non-null assertion on platform; fix it by
narrowing or omitting queryKey and queryFn from the passed options (e.g.
pick/omit so you spread only safe fields) and build queryKey/queryFn locally,
and change the queryFn to defensively guard against missing platform (throw or
return early) instead of using platform!; update useQuery call in
useGetDexKlineCandles to always supply the internally constructed queryKey and a
queryFn that checks DEX_PLATFORM_BY_CHAIN_ID.get(chainId) and calls
getDexKlineCandles({ platform, ...params }) only when platform is present, while
keeping the enabled flag logic intact.

In `@apps/evm/src/components/KLineChart/index.tsx`:
- Around line 106-116: Extract the duplicated setDataLoader configuration into a
single factory/helper and use it in both places: create a function (e.g.,
buildDataLoader) that returns an object with getBars, subscribeBar, and
unsubscribeBar implementations referencing latestDataRef and
subscribeCallbackRef, then call chart.setDataLoader(buildDataLoader()) wherever
the loader is currently duplicated (both in the initial setup effect and the
data-driven effect); ensure function uses the same closure variables
(latestDataRef, subscribeCallbackRef, chart) so the behavior remains identical
and any future changes are made in one place.

In `@apps/evm/src/libs/ws/WsClient.ts`:
- Around line 80-85: Replace the fixed delay in scheduleReconnect with an
exponential backoff: add a numeric state (e.g., this.reconnectAttempts) that
increments each time scheduleReconnect runs, compute delayMs =
Math.min(baseDelayMs * 2 ** this.reconnectAttempts, maxDelayMs) (choose
baseDelayMs like 3000 and a max cap), call setTimeout to invoke this.connect(),
store/clear this.reconnectTimer as before, and reset this.reconnectAttempts to 0
on successful connection inside connect() (or on open event) so retries start
from base again; keep existing reconnectTimer cancellation behavior unchanged.

In `@apps/evm/src/pages/YieldPlus/index.tsx`:
- Around line 29-35: The current useMemo for longTokenDexAddress returns
longToken.address when a native token's wrapped lookup fails, which can lead to
invalid DEX queries; change the logic in the useMemo (the longTokenDexAddress
computation that uses areAddressesEqual, longToken and tokens) to return
undefined (or null) when longToken.isNative and no wrappedToken is found instead
of falling back to longToken.address, and then ensure any kline queries or
callers that depend on longTokenDexAddress are guarded/disabled when it is
undefined (e.g., skip or early-return in the kline query hook or component that
uses longTokenDexAddress).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09658a34-b745-487e-b187-89a2e60f5c2b

📥 Commits

Reviewing files that changed from the base of the PR and between c0413b3 and ca89084.

📒 Files selected for processing (18)
  • apps/evm/src/clients/api/__mocks__/index.ts
  • apps/evm/src/clients/api/index.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts
  • apps/evm/src/components/KLineChart/index.tsx
  • apps/evm/src/config/envVariables.ts
  • apps/evm/src/config/index.ts
  • apps/evm/src/constants/functionKey.ts
  • apps/evm/src/hooks/useIsFeatureEnabled/index.tsx
  • apps/evm/src/libs/ws/WsClient.ts
  • apps/evm/src/libs/ws/index.ts
  • apps/evm/src/pages/YieldPlus/PairInfo/index.tsx
  • apps/evm/src/pages/YieldPlus/index.tsx
  • apps/evm/src/setupTests.tsx
  • apps/evm/src/utilities/restService.ts

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

Coverage Report for ./apps/evm

Status Category Percentage Covered / Total
🔵 Lines 75.57% 37619 / 49777
🔵 Statements 75.57% 37619 / 49777
🔵 Functions 58.71% 613 / 1044
🔵 Branches 70.85% 4414 / 6230
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
apps/evm/src/clients/api/index.ts 0% 0% 0% 0% 1-249
apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts 0% 0% 0% 0% 1-67
apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts 0% 0% 0% 0% 1-53
apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts 0% 0% 0% 0% 1-55
apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts 0% 0% 0% 0% 1-47
apps/evm/src/components/KLineChart/index.tsx 31.15% 41.66% 0% 31.15% 29, 39-130, 146, 149, 152-154, 159-160
apps/evm/src/config/envVariables.ts 73.33% 0% 100% 73.33% 5, 9, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47
apps/evm/src/config/index.ts 96.96% 0% 100% 96.96% 3
apps/evm/src/constants/functionKey.ts 100% 50% 100% 100%
apps/evm/src/hooks/useIsFeatureEnabled/index.tsx 99.33% 0% 100% 99.33% 1
apps/evm/src/libs/ws/WsClient.ts 0% 0% 0% 0% 1-114
apps/evm/src/libs/ws/index.ts 0% 0% 0% 0% 1
apps/evm/src/pages/YieldPlus/index.tsx 96.73% 72% 100% 96.73% 65-67
apps/evm/src/pages/YieldPlus/PairInfo/index.tsx 96.06% 71.42% 100% 96.06% 87, 123-126, 155
apps/evm/src/utilities/restService.ts 65.67% 0% 0% 65.67% 1, 32-38, 56, 62, 68-70, 80-81, 87-97
Generated in workflow #12956 for commit f74e440 by the Vitest Coverage Report Action

@cuzz-venus
Copy link
Copy Markdown
Contributor Author

@greptile

Copy link
Copy Markdown
Contributor Author

@cuzz-venus cuzz-venus left a comment

Choose a reason for hiding this comment

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

no need change, it will always string

Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (1)
apps/evm/src/utilities/restService.ts (1)

54-64: ⚠️ Potential issue | 🟠 Major

Duplicate Accept-Version header logic.

Lines 54-58 and 60-64 are identical blocks that both set the Accept-Version header. This appears to be an accidental duplication that should be removed.

🐛 Proposed fix to remove duplicate block
   if (next) {
     _set(headers, 'Accept-Version', 'next');
   } else {
     _set(headers, 'Accept-Version', 'stable');
   }

-  if (next) {
-    _set(headers, 'Accept-Version', 'next');
-  } else {
-    _set(headers, 'Accept-Version', 'stable');
-  }
-
   if (token) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/utilities/restService.ts` around lines 54 - 64, Remove the
duplicated Accept-Version header block: there are two identical if/else blocks
that call _set(headers, 'Accept-Version', next ? 'next' : 'stable'); keep only
one. Locate the duplicate logic that references the variables next and headers
and the _set function in restService.ts and delete the redundant second block so
the header is set exactly once.
♻️ Duplicate comments (1)
apps/evm/src/libs/ws/WsClient.ts (1)

59-59: ⚠️ Potential issue | 🟡 Minor

WebSocket data may not always be a string.

In browser environments, MessageEvent.data can be a Blob or ArrayBuffer depending on the binaryType setting. The cast data as string could cause issues if the server sends binary data. Consider adding a type guard.

🛡️ Proposed fix to handle data type safely
-    this.ws.onmessage = ({ data }) => this.onMessage(data as string);
+    this.ws.onmessage = ({ data }) => {
+      if (typeof data === 'string') {
+        this.onMessage(data);
+      }
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/libs/ws/WsClient.ts` at line 59, The onmessage handler currently
casts MessageEvent.data to string which can break for Blob/ArrayBuffer; update
the this.ws.onmessage assignment in WsClient so it type-guards
MessageEvent.data, handling string directly, calling blob.text() for Blob, and
using TextDecoder for ArrayBuffer (or Uint8Array) before passing the resulting
string into onMessage; ensure any asynchronous blob.text() promise is awaited or
handled so onMessage receives a string consistently.
🧹 Nitpick comments (9)
apps/evm/src/pages/YieldPlus/PairInfo/index.tsx (1)

151-158: Consider neutral styling when changePercentage is undefined.

When changePercentage is undefined, the placeholder text displays in green, which may misleadingly suggest positive data. Consider using a neutral color (e.g., text-grey) for the undefined case.

♻️ Suggested change
             <p
               className={cn(
                 'text-b1s',
-                changePercentage === undefined || changePercentage >= 0 ? 'text-green' : 'text-red',
+                changePercentage === undefined
+                  ? 'text-grey'
+                  : changePercentage >= 0
+                    ? 'text-green'
+                    : 'text-red',
               )}
             >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/pages/YieldPlus/PairInfo/index.tsx` around lines 151 - 158, The
text color currently uses changePercentage === undefined || changePercentage >=
0 to choose 'text-green', which makes undefined values appear positive; update
the conditional in the JSX that builds the className (the cn call rendering
readableChangePercentage) to handle undefined separately so undefined uses a
neutral class like 'text-grey' and only values >= 0 use 'text-green' while
negatives use 'text-red' (refer to changePercentage and readableChangePercentage
in the component to locate the JSX).
apps/evm/src/utilities/restService.ts (1)

31-37: Consider URL-encoding query parameter values.

Values containing special characters (e.g., &, =, spaces) will corrupt the query string. Use encodeURIComponent for robustness.

♻️ Proposed fix
 export const createQueryParams = (params: Record<string, unknown>) => {
   const paramArray = Object.entries(params).map(([key, value]) => {
     if (value !== undefined && value !== null) {
-      return `${key}=${value}`;
+      return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;
     }
     return '';
   });
   return paramArray.filter(p => p).join('&');
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/utilities/restService.ts` around lines 31 - 37, The
query-building logic maps Object.entries(params) into paramArray but doesn't
URL-encode keys or values, so special characters can break the query string;
update the code that constructs paramArray (the mapping over params / the
paramArray variable) to call encodeURIComponent on both key and value (e.g.,
encodeURIComponent(key) and encodeURIComponent(value)) while preserving the
existing checks for undefined/null, then continue filtering and joining as
before.
apps/evm/src/components/KLineChart/index.tsx (1)

143-153: Consider extracting the setDataLoader configuration.

The subscribeBar/unsubscribeBar handlers at lines 110-115 and 147-152 are duplicated. Extracting a helper would reduce repetition, though this is optional given the small scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/components/KLineChart/index.tsx` around lines 143 - 153, The
setDataLoader configuration is duplicated for subscribeBar/unsubscribeBar;
refactor by extracting a helper that returns the handlers and reuse it in both
places: create a function (e.g., buildBarSubscriptionHandlers) that captures
subscribeCallbackRef and returns an object with subscribeBar(callbackSetter) and
unsubscribeBar() or directly returns { subscribeBar, unsubscribeBar } to pass
into chartRef.current?.setDataLoader and elsewhere; ensure getBars remains
unchanged and that subscribeCallbackRef is assigned/cleared inside the helper so
both occurrences call chartRef.current?.setDataLoader({ getBars,
...buildBarSubscriptionHandlers() }).
apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts (1)

33-39: Consider adding a timeout to prevent indefinite hangs.

The fetch call has no timeout, which could cause the request to hang indefinitely if the DEX API is unresponsive. This could degrade user experience.

♻️ Proposed fix using AbortController
 export const getDexKlineCandles = async ({
   platform,
   address,
   interval,
   limit,
   from,
   to,
   unit,
   tokenIndex,
 }: GetDexKlineCandlesInput): Promise<GetDexKlineCandlesOutput> => {
   const queryParams = createQueryParams({
     platform,
     address,
     interval,
     limit,
     from,
     to,
     unit,
     tokenIndex,
   });

+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), 30000);
+
-  const response = await fetch(`${config.dexApiUrl}/k-line/candles?${queryParams}`, {
-    headers: { Accept: 'application/json' },
-  });
+  const response = await fetch(`${config.dexApiUrl}/k-line/candles?${queryParams}`, {
+    headers: { Accept: 'application/json' },
+    signal: controller.signal,
+  }).finally(() => clearTimeout(timeoutId));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts` around lines 33
- 39, The fetch to `${config.dexApiUrl}/k-line/candles` in getDexKlineCandles
can hang indefinitely; fix it by using an AbortController: create an
AbortController, start a setTimeout (e.g., 5s) that calls controller.abort(),
pass controller.signal to fetch, and clear the timeout after fetch completes;
also catch the abort case and throw a VError with a clear timeout-type/code
instead of the generic error so callers can distinguish timeouts from other
failures (reference: getDexKlineCandles, response, config.dexApiUrl).
apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts (1)

18-24: Module-level singleton WebSocket client is never cleaned up.

The sharedClient is lazily created but never destroyed. While this is likely intentional for performance (keeping the connection alive across page navigations), the WebSocket connection will persist until the browser tab is closed, even if the user navigates away from the YieldPlus page entirely.

Consider whether this is the desired behavior, or if cleanup logic should be added for when no subscriptions remain.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts`
around lines 18 - 24, The module-level singleton sharedClient (created by
getSharedClient) is never cleaned up, so the DexKlineWsClient WebSocket stays
open indefinitely; add lifecycle/cleanup logic: implement a dispose/close method
on DexKlineWsClient (if not present), track active subscriptions count in the
module (increment when subscribing, decrement when unsubscribing), and when
count reaches zero call sharedClient.close()/dispose() and set sharedClient =
null to fully tear down the connection; update subscription management code to
use these counters and ensure getSharedClient still lazily creates a client when
needed.
apps/evm/src/pages/YieldPlus/index.tsx (1)

56-61: Consider edge case when live candle updates during initial fetch.

The changePercentage calculation compares the latest candle (live or fetched) against klineData?.candles?.at(-2). If the live candle arrives before/during the initial fetch or there's only one historical candle, prevCandle will be undefined and the percentage returns undefined (handled correctly).

However, when comparing liveCandle against the second-to-last fetched candle, you may skip showing the change from the last fetched candle to the live candle's predecessor. This might be intentional since the live candle updates the last bar, but worth confirming the UX is as expected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/pages/YieldPlus/index.tsx` around lines 56 - 61, The
changePercentage useMemo can miss a valid previous candle when liveCandle exists
because prevCandle is always taken as klineData?.candles?.at(-2); update the
logic in changePercentage (referencing changePercentage, liveCandle, and
klineData?.candles?.at) so that if liveCandle is present and there is at least
one fetched candle you treat the last fetched candle (at(-1)) as prevCandle,
otherwise fall back to at(-2); keep the existing guard for undefined/zero and
return undefined when no valid prevCandle exists.
apps/evm/src/libs/ws/WsClient.ts (2)

60-60: Silent error handling may hinder debugging.

The error handler closes the socket without logging. Consider adding minimal logging to aid debugging connection issues in production.

♻️ Proposed enhancement
-    this.ws.onerror = () => this.ws?.close();
+    this.ws.onerror = (event) => {
+      console.warn('[WsClient] WebSocket error, closing connection', event);
+      this.ws?.close();
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/libs/ws/WsClient.ts` at line 60, The onerror handler on WsClient
currently closes the socket silently; modify the this.ws.onerror callback to
capture the error/event (accept the event param) and log a concise message
including the event or error details before calling this.ws.close(); use the
class logger if available (e.g., this.logger.error) or console.error as a
fallback, and preserve the existing close behavior so the socket still shuts
down after logging.

80-85: Consider exponential backoff for reconnection.

The fixed 3-second delay works, but exponential backoff with jitter is more resilient against server overload during outages and reduces thundering-herd effects.

♻️ Proposed enhancement
+  private reconnectAttempts = 0;
+  private readonly maxReconnectDelay = 30000;
+
-  private scheduleReconnect(delayMs = 3000): void {
+  private scheduleReconnect(): void {
+    const baseDelay = 1000;
+    const delay = Math.min(baseDelay * 2 ** this.reconnectAttempts, this.maxReconnectDelay);
+    const jitter = Math.random() * 1000;
     this.reconnectTimer = setTimeout(() => {
       this.reconnectTimer = null;
+      this.reconnectAttempts++;
       this.connect();
-    }, delayMs);
+    }, delay + jitter);
   }

Reset reconnectAttempts to 0 in onopen handler.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/libs/ws/WsClient.ts` around lines 80 - 85, Replace the fixed 3s
reconnect delay in scheduleReconnect with an exponential-backoff plus jitter
calculation based on a reconnectAttempts counter: increment reconnectAttempts
before scheduling, compute delay = min(maxDelayMs, baseDelayMs *
2**reconnectAttempts) and add some random jitter (± up to e.g. 20% of delay),
then set reconnectTimer and call this.connect() after that delay; ensure you
reference scheduleReconnect and reconnectTimer when changing behavior and reset
reconnectAttempts = 0 in the WebSocket onopen handler so successful connections
clear the backoff.
apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts (1)

50-62: Consider removing the index signature if not needed.

The [key: string]: unknown index signature on DexKlineCandle allows arbitrary additional properties. If the candle shape is well-defined and no extra fields are expected, removing this provides stricter type safety and better autocompletion.

♻️ Proposed fix
 export interface DexKlineCandle {
   timestamp: number;
   open: number;
   high: number;
   low: number;
   close: number;
   volume: number;
-  [key: string]: unknown;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts` around lines 50
- 62, The DexKlineCandle interface currently includes an open index signature
(`[key: string]: unknown`) which permits arbitrary properties; if the candle
shape is fixed, remove that index signature from DexKlineCandle to enforce
stricter typing, keep the explicit fields (timestamp, open, high, low, close,
volume), and then update any places that depended on extra keys (casts, spreads,
or dynamic property access) to either add explicit fields or use a separate
record type where truly needed; ensure GetDexKlineCandlesOutput remains
unchanged and run type checks to find the usages to adjust.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/evm/src/config/envVariables.ts`:
- Around line 43-48: Add VITE_DEX_API_URL and VITE_DEX_WS_URL to the project's
example environment file and/or documentation so developers know the required
endpoints and expected format; update .env.example (or relevant docs) to include
entries for VITE_DEX_API_URL and VITE_DEX_WS_URL with brief placeholder values
and a short comment, referencing the variables used in
apps/evm/src/config/envVariables.ts (VITE_DEX_API_URL, VITE_DEX_WS_URL) and the
fallback behavior in apps/evm/src/config/index.ts so the example aligns with the
code.

---

Outside diff comments:
In `@apps/evm/src/utilities/restService.ts`:
- Around line 54-64: Remove the duplicated Accept-Version header block: there
are two identical if/else blocks that call _set(headers, 'Accept-Version', next
? 'next' : 'stable'); keep only one. Locate the duplicate logic that references
the variables next and headers and the _set function in restService.ts and
delete the redundant second block so the header is set exactly once.

---

Duplicate comments:
In `@apps/evm/src/libs/ws/WsClient.ts`:
- Line 59: The onmessage handler currently casts MessageEvent.data to string
which can break for Blob/ArrayBuffer; update the this.ws.onmessage assignment in
WsClient so it type-guards MessageEvent.data, handling string directly, calling
blob.text() for Blob, and using TextDecoder for ArrayBuffer (or Uint8Array)
before passing the resulting string into onMessage; ensure any asynchronous
blob.text() promise is awaited or handled so onMessage receives a string
consistently.

---

Nitpick comments:
In `@apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts`:
- Around line 33-39: The fetch to `${config.dexApiUrl}/k-line/candles` in
getDexKlineCandles can hang indefinitely; fix it by using an AbortController:
create an AbortController, start a setTimeout (e.g., 5s) that calls
controller.abort(), pass controller.signal to fetch, and clear the timeout after
fetch completes; also catch the abort case and throw a VError with a clear
timeout-type/code instead of the generic error so callers can distinguish
timeouts from other failures (reference: getDexKlineCandles, response,
config.dexApiUrl).

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts`:
- Around line 50-62: The DexKlineCandle interface currently includes an open
index signature (`[key: string]: unknown`) which permits arbitrary properties;
if the candle shape is fixed, remove that index signature from DexKlineCandle to
enforce stricter typing, keep the explicit fields (timestamp, open, high, low,
close, volume), and then update any places that depended on extra keys (casts,
spreads, or dynamic property access) to either add explicit fields or use a
separate record type where truly needed; ensure GetDexKlineCandlesOutput remains
unchanged and run type checks to find the usages to adjust.

In `@apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts`:
- Around line 18-24: The module-level singleton sharedClient (created by
getSharedClient) is never cleaned up, so the DexKlineWsClient WebSocket stays
open indefinitely; add lifecycle/cleanup logic: implement a dispose/close method
on DexKlineWsClient (if not present), track active subscriptions count in the
module (increment when subscribing, decrement when unsubscribing), and when
count reaches zero call sharedClient.close()/dispose() and set sharedClient =
null to fully tear down the connection; update subscription management code to
use these counters and ensure getSharedClient still lazily creates a client when
needed.

In `@apps/evm/src/components/KLineChart/index.tsx`:
- Around line 143-153: The setDataLoader configuration is duplicated for
subscribeBar/unsubscribeBar; refactor by extracting a helper that returns the
handlers and reuse it in both places: create a function (e.g.,
buildBarSubscriptionHandlers) that captures subscribeCallbackRef and returns an
object with subscribeBar(callbackSetter) and unsubscribeBar() or directly
returns { subscribeBar, unsubscribeBar } to pass into
chartRef.current?.setDataLoader and elsewhere; ensure getBars remains unchanged
and that subscribeCallbackRef is assigned/cleared inside the helper so both
occurrences call chartRef.current?.setDataLoader({ getBars,
...buildBarSubscriptionHandlers() }).

In `@apps/evm/src/libs/ws/WsClient.ts`:
- Line 60: The onerror handler on WsClient currently closes the socket silently;
modify the this.ws.onerror callback to capture the error/event (accept the event
param) and log a concise message including the event or error details before
calling this.ws.close(); use the class logger if available (e.g.,
this.logger.error) or console.error as a fallback, and preserve the existing
close behavior so the socket still shuts down after logging.
- Around line 80-85: Replace the fixed 3s reconnect delay in scheduleReconnect
with an exponential-backoff plus jitter calculation based on a reconnectAttempts
counter: increment reconnectAttempts before scheduling, compute delay =
min(maxDelayMs, baseDelayMs * 2**reconnectAttempts) and add some random jitter
(± up to e.g. 20% of delay), then set reconnectTimer and call this.connect()
after that delay; ensure you reference scheduleReconnect and reconnectTimer when
changing behavior and reset reconnectAttempts = 0 in the WebSocket onopen
handler so successful connections clear the backoff.

In `@apps/evm/src/pages/YieldPlus/index.tsx`:
- Around line 56-61: The changePercentage useMemo can miss a valid previous
candle when liveCandle exists because prevCandle is always taken as
klineData?.candles?.at(-2); update the logic in changePercentage (referencing
changePercentage, liveCandle, and klineData?.candles?.at) so that if liveCandle
is present and there is at least one fetched candle you treat the last fetched
candle (at(-1)) as prevCandle, otherwise fall back to at(-2); keep the existing
guard for undefined/zero and return undefined when no valid prevCandle exists.

In `@apps/evm/src/pages/YieldPlus/PairInfo/index.tsx`:
- Around line 151-158: The text color currently uses changePercentage ===
undefined || changePercentage >= 0 to choose 'text-green', which makes undefined
values appear positive; update the conditional in the JSX that builds the
className (the cn call rendering readableChangePercentage) to handle undefined
separately so undefined uses a neutral class like 'text-grey' and only values >=
0 use 'text-green' while negatives use 'text-red' (refer to changePercentage and
readableChangePercentage in the component to locate the JSX).

In `@apps/evm/src/utilities/restService.ts`:
- Around line 31-37: The query-building logic maps Object.entries(params) into
paramArray but doesn't URL-encode keys or values, so special characters can
break the query string; update the code that constructs paramArray (the mapping
over params / the paramArray variable) to call encodeURIComponent on both key
and value (e.g., encodeURIComponent(key) and encodeURIComponent(value)) while
preserving the existing checks for undefined/null, then continue filtering and
joining as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc227676-66fa-4017-9ef0-0b105f43fceb

📥 Commits

Reviewing files that changed from the base of the PR and between c0413b3 and 12438b8.

📒 Files selected for processing (19)
  • apps/evm/src/clients/api/__mocks__/index.ts
  • apps/evm/src/clients/api/index.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/DexKlineWsClient.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/index.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/types.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useDexKlineWebSocket.ts
  • apps/evm/src/clients/api/queries/getDexKlineCandles/useGetDexKlineCandles.ts
  • apps/evm/src/components/KLineChart/index.tsx
  • apps/evm/src/config/envVariables.ts
  • apps/evm/src/config/index.ts
  • apps/evm/src/constants/functionKey.ts
  • apps/evm/src/hooks/useIsFeatureEnabled/index.tsx
  • apps/evm/src/libs/ws/WsClient.ts
  • apps/evm/src/libs/ws/index.ts
  • apps/evm/src/pages/YieldPlus/PairInfo/index.tsx
  • apps/evm/src/pages/YieldPlus/__tests__/index.spec.tsx
  • apps/evm/src/pages/YieldPlus/index.tsx
  • apps/evm/src/setupTests.tsx
  • apps/evm/src/utilities/restService.ts

Comment on lines +43 to +48
VITE_DEX_API_URL:
typeof process !== 'undefined'
? process.env.VITE_DEX_API_URL
: import.meta.env.VITE_DEX_API_URL,
VITE_DEX_WS_URL:
typeof process !== 'undefined' ? process.env.VITE_DEX_WS_URL : import.meta.env.VITE_DEX_WS_URL,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if new env variables are documented and have fallback handling.

# Check for .env.example or similar documentation
fd -t f -e example -e sample | xargs rg -l 'DEX_API_URL\|DEX_WS_URL' 2>/dev/null || echo "No .env example files found with DEX vars"

# Check config/index.ts for fallback handling
rg -n 'dexApiUrl|dexWsUrl' apps/evm/src/config/

Repository: VenusProtocol/venus-protocol-interface

Length of output: 390


Add these environment variables to .env.example or documentation.

Code pattern is correct and follows established conventions. Fallback handling in apps/evm/src/config/index.ts (lines 50-51) is properly implemented with empty string defaults. However, VITE_DEX_API_URL and VITE_DEX_WS_URL are missing from example configuration files, making it unclear to developers how to configure these required endpoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/evm/src/config/envVariables.ts` around lines 43 - 48, Add
VITE_DEX_API_URL and VITE_DEX_WS_URL to the project's example environment file
and/or documentation so developers know the required endpoints and expected
format; update .env.example (or relevant docs) to include entries for
VITE_DEX_API_URL and VITE_DEX_WS_URL with brief placeholder values and a short
comment, referencing the variables used in apps/evm/src/config/envVariables.ts
(VITE_DEX_API_URL, VITE_DEX_WS_URL) and the fallback behavior in
apps/evm/src/config/index.ts so the example aligns with the code.

@cuzz-venus
Copy link
Copy Markdown
Contributor Author

@greptile

@cuzz-venus
Copy link
Copy Markdown
Contributor Author

@greptile

@cuzz-venus
Copy link
Copy Markdown
Contributor Author

@greptile

@therealemjy
Copy link
Copy Markdown
Member

Work merged with #5463

@therealemjy therealemjy deleted the feat/yield-plus-api branch April 8, 2026 12:02
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.

3 participants