Skip to content

Conversation

@premiumjibles
Copy link

@premiumjibles premiumjibles commented Dec 17, 2025

This adds graceful error handling to the affiliate revenue endpoint. Ensuring that if one swapper api fails we at least get partial data. It also will indicate in the API response which failed, ensuring we can show some kind of indicator in the UI.

Testing:

  • Make sure unchained ain't dead

If you want to properly test this locally:

  1. Install dependencies (from repo root)
    cd /home/sean/Repos/shapeshift-unchained
    yarn install

  2. Copy and configure env
    cp node/proxy/sample.env node/proxy/.env
    // Edit node/proxy/.env with real API keys (BEBOP_API_KEY, ZRX_API_KEY, etc.)

  3. Start reverse proxy (from repo root)
    docker-compose up -d reverse-proxy

  4. Start proxy API
    cd node/proxy
    docker-compose up

  5. Test endpoint (in another terminal)

curl "http://api.proxy.localhost/api/v1/affiliate/revenue?startTimestamp=$(( $(date +%s) - 604800 ))&endTimestamp=$(date +%s)"

Screenshots

image

Summary by CodeRabbit

  • New Features

    • Response now includes daily breakdown (byDate), per-service totals (byService), and a list of failedProviders.
    • Added support for additional affiliate providers.
  • Bug Fixes

    • Improved resilience: partial results are returned when individual providers fail, with per-provider failure tracking and normalized error reporting.
  • API

    • Query parameters changed: startDate/endDate now expect YYYY-MM-DD.

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

@premiumjibles premiumjibles requested a review from a team as a code owner December 17, 2025 00:01
@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

📝 Walkthrough

Walkthrough

Reworks affiliate revenue aggregation: uses Promise.allSettled to collect provider results, records per-provider failures in failedProviders, adds per-day (byDate) and per-service breakdowns, extends Fees and AffiliateRevenueResponse types, and introduces per-provider modules, caching, and new service entries.

Changes

Cohort / File(s) Summary
Aggregator + API surface
node/proxy/api/src/affiliateRevenue/index.ts, node/proxy/api/src/controller.ts, node/proxy/api/src/models.ts, node/proxy/api/src/swagger.json
Switched to Promise.allSettled with per-provider failedProviders; added byDate and updated byService/totalUsd aggregation; extended Fees and AffiliateRevenueResponse types; API params changed from timestamps to startDate/endDate (YYYY-MM-DD) in controller and OpenAPI.
New / Reworked provider implementations
node/proxy/api/src/affiliateRevenue/*/{bebop, butterswap, chainflip, mayachain, nearIntents, portals, relay, thorchain, zrx}/*
Added per-service directories implementing getFees(startTimestamp,endTimestamp) for each provider, with paging, caching, API interaction, and per-date grouping. Several legacy single-file provider modules were removed and replaced by these new per-service modules.
Caching & shared utilities
node/proxy/api/src/affiliateRevenue/cache.ts, .../*/utils.ts (butterswap, portals, relay, nearIntents)
New LRU caches for fees/token transfers/decimals; date helpers, cache key helpers, splitDateRange/grouping functions; provider-specific utility helpers (RPC, token/asset parsing, price/decimals, block estimation, retries).
Provider constants & types
node/proxy/api/src/affiliateRevenue/*/constants.ts, .../*/types.ts
Added many provider-specific constant modules (API URLs, selectors, addresses, mappings) and typed response shapes for Bebop/Chainflip/ButterSwap/Mayachain/NearIntents/Portals/Relay/Thorchain/ZRX.
Portals & complex helpers
node/proxy/api/src/affiliateRevenue/portals/{portals.ts,utils.ts,types.ts,constants.ts}
New multi-chain portals implementation: event decoding, transfer resolution, price lookup, asset id construction, caching, per-chain orchestration.
Index barrels & re-exports
node/proxy/api/src/affiliateRevenue/*/index.ts
Added re-exports for each provider's getFees to expose standardized public surface.
Build / deps / gitignore
node/proxy/api/package.json, .gitignore, node/proxy/sample.env
Added dependencies lru-cache and viem; added docker-compose.override.yml to .gitignore; newline normalization in sample.env.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰
I scurried through code with a twitchy nose,
Grouped providers in tidy little rows,
If one hops away, I still know who fell—
Failed or not, I report it well! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: adding graceful error handling to the affiliate revenue endpoint to return partial data when some APIs fail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 11435_graceful_errors

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
node/proxy/api/src/swagger.json (1)

131-145: Consider adding maxItems constraint to the array.

The failedProviders field is correctly defined and marked as required. The array is naturally bounded by the number of services (7), but adding an explicit maxItems constraint would make this clear in the API contract.

Based on the static analysis hint, you could apply this diff:

 					"failedProviders": {
 						"items": {
 							"$ref": "#/components/schemas/Service"
 						},
-						"type": "array"
+						"type": "array",
+						"maxItems": 7
 					},
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 026c8f1 and 579ccea.

📒 Files selected for processing (3)
  • node/proxy/api/src/affiliateRevenue/index.ts (4 hunks)
  • node/proxy/api/src/models.ts (1 hunks)
  • node/proxy/api/src/swagger.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
node/proxy/api/src/affiliateRevenue/index.ts (1)
node/proxy/api/src/models.ts (1)
  • Service (6-6)
🪛 Checkov (3.2.334)
node/proxy/api/src/swagger.json

[medium] 131-135: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check
🔇 Additional comments (4)
node/proxy/api/src/models.ts (1)

10-10: LGTM!

The addition of failedProviders field correctly tracks which services failed, enabling the UI to display appropriate indicators.

node/proxy/api/src/affiliateRevenue/index.ts (2)

24-43: LGTM!

The switch to Promise.allSettled correctly implements graceful error handling. Failed providers are properly tracked and logged, while successful results are aggregated.


57-61: LGTM!

The return object correctly includes failedProviders, matching the updated AffiliateRevenueResponse interface.

node/proxy/api/src/swagger.json (1)

114-125: LGTM!

The Service enum schema correctly reflects all defined services and aligns with the TypeScript type definition.

@premiumjibles premiumjibles changed the title feat: add graceful error handling to affiliate revenue endpoint feat: finish implementing affiliate revenue endpoint Dec 29, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

♻️ Duplicate comments (1)
node/proxy/api/src/affiliateRevenue/index.ts (1)

14-24: Order-dependent mapping between providerNames and Promise.allSettled is fragile.

The providerNames array must be kept in sync with the order of promises in Promise.allSettled. If a developer adds or reorders providers, failures could be attributed to the wrong provider. This was flagged in a previous review.

Consider using a provider registry pattern as suggested previously.

Also applies to: 54-64

🧹 Nitpick comments (23)
node/proxy/api/src/affiliateRevenue/mayachain/types.ts (1)

1-11: Type definition is correct, but duplicates thorchain's FeesResponse.

This type is identical to FeesResponse in node/proxy/api/src/affiliateRevenue/thorchain/types.ts. Consider extracting a shared type to a common location to reduce duplication, unless the APIs are expected to diverge in the future.

node/proxy/api/src/affiliateRevenue/zrx/zrx.ts (1)

22-29: Consider adding timeout and error handling for the API request.

The axios request lacks a timeout configuration. Long-running or hanging requests could block the entire fetch operation. Given the PR's goal of graceful error handling, consider:

  1. Adding a request timeout
  2. Adding try-catch with meaningful error context
🔎 Proposed improvement
       const { data } = await axios.get<TradesResponse>(`${ZRX_API_URL}/${service}`, {
         params: { cursor, startTimestamp, endTimestamp },
         headers: {
           '0x-api-key': ZRX_API_KEY,
           '0x-version': 'v2',
         },
+        timeout: 30000, // 30 second timeout
       })
node/proxy/api/src/affiliateRevenue/thorchain/types.ts (1)

1-11: Consider extracting the duplicated type to a shared location.

The FeesResponse type is identical to the one in node/proxy/api/src/affiliateRevenue/mayachain/types.ts. Consider extracting this shared type definition to a common location (e.g., node/proxy/api/src/affiliateRevenue/types.ts or node/proxy/api/src/affiliateRevenue/common/types.ts) to follow the DRY principle and maintain consistency.

🔎 Suggested refactor

Create a shared types file and import from both modules:

// In a shared location like node/proxy/api/src/affiliateRevenue/common/types.ts
export type ChainFeesResponse = {
  fees: Array<{
    address: string
    amount: string
    asset: string
    blockHash: string
    blockHeight: number
    timestamp: number
    txId: string
  }>
}

Then import and re-export or alias in both thorchain and mayachain modules.

node/proxy/api/src/affiliateRevenue/relay/constants.ts (1)

4-4: Consider centralizing shared constants.

SHAPESHIFT_REFERRER is duplicated in multiple files (relay and bebop constants). Consider moving shared constants like this to the main affiliateRevenue/constants.ts file to maintain a single source of truth.

node/proxy/api/src/affiliateRevenue/mayachain/constants.ts (1)

1-4: Consider centralizing duplicate constants.

PRICE_API_URL and MILLISECONDS_PER_SECOND are duplicated in both mayachain and thorchain constants files with identical values. Consider moving these shared constants to the main affiliateRevenue/constants.ts file to reduce duplication and ensure consistency.

node/proxy/api/src/affiliateRevenue/bebop/constants.ts (1)

6-8: Consider centralizing duplicate constants.

Both SHAPESHIFT_REFERRER and FEE_BPS_DENOMINATOR are duplicated across multiple modules:

  • SHAPESHIFT_REFERRER also in relay/constants.ts
  • FEE_BPS_DENOMINATOR also in nearIntents and portals constants

Consider moving these to the main affiliateRevenue/constants.ts file to maintain a single source of truth.

node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (2)

21-24: Top-level throw may crash application on startup.

The guard at line 24 executes at module load time. If NEAR_INTENTS_API_KEY is unset, the entire application will crash during startup, preventing other providers from functioning. Given the PR's goal of graceful error handling, consider deferring this check to runtime when getFees is called.

🔎 Proposed fix
-export const NEAR_INTENTS_API_KEY = process.env.NEAR_INTENTS_API_KEY
+export const NEAR_INTENTS_API_KEY = process.env.NEAR_INTENTS_API_KEY ?? ''
 export const FEE_BPS_DENOMINATOR = 10000
-
-if (!NEAR_INTENTS_API_KEY) throw new Error('NEAR_INTENTS_API_KEY env var not set')

Then validate in the getFees function:

if (!NEAR_INTENTS_API_KEY) {
  throw new Error('NEAR_INTENTS_API_KEY env var not set')
}

43-43: Hardcoded chain ID for monad.

The monad mapping uses a hardcoded value 'eip155:143' while all other chains use imported constants. Consider adding a MONAD_CHAIN_ID constant to ../constants for consistency and maintainability.

node/proxy/api/src/affiliateRevenue/relay/relay.ts (1)

22-30: Consider adding request timeout.

The axios request has no timeout configured. If the Relay API becomes unresponsive, this could block indefinitely. Other network-related issues could also cause hanging requests.

🔎 Proposed fix
     const { data } = await axios.get<RelayResponse>(`${RELAY_API_URL}/requests/v2`, {
       params: {
         referrer: SHAPESHIFT_REFERRER,
         startTimestamp,
         endTimestamp,
         status: 'success',
         continuation,
       },
+      timeout: 30000, // 30 second timeout
     })
node/proxy/api/src/affiliateRevenue/thorchain/thorchain.ts (1)

42-53: Price fetched per API call may cause inconsistent USD values.

getRunePriceUsd() is called inside fetchFeesFromAPI, which can be invoked multiple times (for cached date ranges and recent data). If the RUNE price changes between calls, fees from the same query will have inconsistent USD valuations.

Consider fetching the price once in getFees and passing it to fetchFeesFromAPI.

🔎 Proposed fix
-const fetchFeesFromAPI = async (startTimestamp: number, endTimestamp: number): Promise<Fees[]> => {
+const fetchFeesFromAPI = async (startTimestamp: number, endTimestamp: number, runePriceUsd: number): Promise<Fees[]> => {
   const start = startTimestamp * MILLISECONDS_PER_SECOND
   const end = endTimestamp * MILLISECONDS_PER_SECOND

   const { data } = await axios.get<FeesResponse>(THORCHAIN_API_URL, {
     params: { start, end },
   })

-  const runePriceUsd = await getRunePriceUsd()
-
   return data.fees.map(fee => transformFee(fee, runePriceUsd))
 }

 export const getFees = async (startTimestamp: number, endTimestamp: number): Promise<Fees[]> => {
+  const runePriceUsd = await getRunePriceUsd()
   const threshold = getCacheableThreshold()
   // ... update calls to fetchFeesFromAPI to pass runePriceUsd
node/proxy/api/src/affiliateRevenue/portals/portals.ts (1)

322-326: Silent failure handling for per-chain fetching.

Failed chain fetches are silently dropped without logging. This could make debugging difficult when a specific chain's data is missing. Consider adding logging for rejected results.

🔎 Proposed fix
   for (const result of results) {
     if (result.status === 'fulfilled') {
       allFees.push(...result.value)
+    } else {
+      console.error(`[portals] Chain fetch failed:`, result.reason)
     }
   }
node/proxy/api/src/affiliateRevenue/chainflip/chainflip.ts (1)

38-53: Consider using swapRequestNativeId as the transaction identifier.

The txHash is set to an empty string, but the GraphQL response includes swapRequestNativeId which could serve as a unique identifier for each swap. This would improve traceability and debugging.

🔎 Proposed fix
       fees.push({
         chainId,
         assetId,
         service: 'chainflip',
-        txHash: '',
+        txHash: swap.swapRequestNativeId,
         timestamp: Math.floor(new Date(swap.completedBlockTimestamp).getTime() / 1000),
         amount: '0',
         amountUsd: swap.affiliateBroker1FeeValueUsd,
       })
node/proxy/api/src/affiliateRevenue/butterswap/butterswap.ts (1)

98-99: Potential precision loss with large fee values.

Converting BigInt to Number via Number(feesPerDay) can lose precision for values exceeding Number.MAX_SAFE_INTEGER (~9 quadrillion). For USDT with 6 decimals, this threshold is ~9 billion USD per day, which is unlikely but worth noting.

Additionally, integer division on line 98 discards any remainder, which could result in small undercounting. Consider distributing the remainder across some days or using the total for USD calculation.

node/proxy/api/src/affiliateRevenue/nearIntents/nearIntents.ts (1)

23-25: Consider moving the API URL to constants.

The API URL is hardcoded inline. For consistency with other modules (e.g., CHAINFLIP_API_URL, BEBOP_API_URL), consider extracting this to a constant in ./constants.ts.

🔎 Proposed change

In constants.ts:

export const NEAR_INTENTS_API_URL = 'https://explorer.near-intents.org/api/v0/transactions-pages'

In nearIntents.ts:

+import { FEE_BPS_DENOMINATOR, NEAR_INTENTS_API_KEY, NEAR_INTENTS_API_URL } from './constants'

     const { data } = await axios.get<TransactionsResponse>(
-      'https://explorer.near-intents.org/api/v0/transactions-pages',
+      NEAR_INTENTS_API_URL,
node/proxy/api/src/affiliateRevenue/portals/constants.ts (1)

21-22: FEE_BPS_DENOMINATOR is duplicated across multiple modules.

This constant is defined in bebop/constants.ts, nearIntents/constants.ts, and here. Consider extracting it to the shared ../constants.ts to avoid duplication.

node/proxy/api/src/affiliateRevenue/butterswap/constants.ts (1)

16-25: Consider adding inline documentation for fallback tokens.

These hardcoded addresses lack context. A brief comment explaining what these tokens represent (e.g., "common ButterSwap tokens on MAP chain") would aid maintainability.

Suggested improvement
+// Fallback token addresses on MAP chain used when token list API is unavailable
 export const FALLBACK_TOKENS = [
   '0x05ab928d446d8ce6761e368c8e7be03c3168a9ec',
   // ... rest of tokens
 ]
node/proxy/api/src/affiliateRevenue/zrx/types.ts (1)

1-5: Consider exporting the Fee type.

The Fee type is used in TradesResponse but isn't exported. If consumers need to reference this type independently, it should be exported.

Suggested fix
-type Fee = {
+export type Fee = {
   token?: string
   amount?: string
   amountUsd?: string
 }
node/proxy/api/src/affiliateRevenue/portals/utils.ts (4)

8-12: Add timeout to external API call.

The axios.get call to the explorer API lacks a timeout, which could cause the request to hang indefinitely if the explorer is unresponsive.

Suggested fix
 export const getTransactionTimestamp = async (explorerUrl: string, txHash: string): Promise<number> => {
   const url = `${explorerUrl}/api/v2/transactions/${txHash}`
-  const { data } = await axios.get<BlockscoutTransaction>(url)
+  const { data } = await axios.get<BlockscoutTransaction>(url, { timeout: 10000 })
   return Math.floor(new Date(data.timestamp).getTime() / 1000)
 }

14-28: Add a comment explaining the magic number 258.

The minimum data length check of 258 should be documented to explain its derivation (e.g., ABI-encoded 5 parameters × 32 bytes + 2 byte prefix + overhead).

Suggested improvement
 export const decodePortalEventData = (data: string): DecodedPortalEvent | null => {
+  // Minimum length: 0x prefix (2) + 5 ABI-encoded params (5 × 64 hex chars = 320) = 322
+  // Using 258 as conservative minimum for valid portal event data
   if (!data || data.length < 258) return null

47-64: Add timeout to token decimals API calls.

Both Blockscout and Etherscan API calls lack timeouts. This is especially important since these calls are made during fee calculation flows.

Suggested fix
   try {
     if (explorerType === 'blockscout') {
-      const { data } = await axios.get<{ decimals?: string }>(`${explorerUrl}/api/v2/tokens/${tokenAddress}`)
+      const { data } = await axios.get<{ decimals?: string }>(`${explorerUrl}/api/v2/tokens/${tokenAddress}`, { timeout: 10000 })
       const decimals = parseInt(data.decimals ?? '18')
       saveCachedDecimals(cacheKey, decimals)
       return decimals
     }

-    const { data } = await axios.get<{ result?: Array<{ divisor?: string }> }>(`${explorerUrl}/api`, {
+    const { data } = await axios.get<{ result?: Array<{ divisor?: string }> }>(`${explorerUrl}/api`, {
+      timeout: 10000,
       params: { module: 'token', action: 'tokeninfo', contractaddress: tokenAddress },
     })

73-110: Module-level priceCache object can grow unbounded.

Unlike feeCache, tokenTransferCache, and decimalsCache which use LRUCache with size limits, this priceCache is a plain object that can grow indefinitely. Consider using LRUCache for consistency and memory safety.

Suggested fix
+import { LRUCache } from 'lru-cache'
+
-const priceCache: Record<string, { price: number | null; timestamp: number }> = {}
-const PRICE_CACHE_TTL = 1000 * 60 * 5 // 5 minutes
+const priceCache = new LRUCache<string, number | null>({
+  max: 1000,
+  ttl: 1000 * 60 * 5, // 5 minutes
+})

 export const getTokenPrice = async (chainId: string, tokenAddress: string): Promise<number | null> => {
   const cacheKey = `${chainId}:${tokenAddress.toLowerCase()}`
-  const cached = priceCache[cacheKey]
-  if (cached && Date.now() - cached.timestamp < PRICE_CACHE_TTL) {
-    return cached.price
-  }
+  const cached = priceCache.get(cacheKey)
+  if (cached !== undefined) return cached
   // ... rest of function, replace priceCache[cacheKey] = { price, timestamp: Date.now() } with priceCache.set(cacheKey, price)

Also add timeouts to the CoinGecko API calls on lines 92 and 101.

node/proxy/api/src/affiliateRevenue/cache.ts (2)

24-27: Potential edge case with toISOString().split('T')[0].

The return type annotation guarantees string, but split('T')[0] could theoretically return undefined if the ISO string format changes. TypeScript won't catch this. Consider adding a non-null assertion or fallback for defensive coding.

Suggested fix
 export const timestampToDate = (timestamp: number): string => {
   const date = new Date(timestamp * 1000)
-  return date.toISOString().split('T')[0]
+  return date.toISOString().split('T')[0] ?? ''
 }

50-52: Off-by-one second in getDateEndTimestamp.

23:59:59 is 1 second before midnight. This could cause edge cases where transactions at exactly 23:59:59.5 might be included or excluded unexpectedly. Consider using T23:59:59.999Z or adjusting the comparison logic.

Suggested fix
 export const getDateEndTimestamp = (date: string): number => {
-  return Math.floor(new Date(date + 'T23:59:59Z').getTime() / 1000)
+  return Math.floor(new Date(date + 'T23:59:59.999Z').getTime() / 1000)
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 579ccea and e93a7bc.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (56)
  • .gitignore
  • node/proxy/api/package.json
  • node/proxy/api/src/affiliateRevenue/bebop.ts
  • node/proxy/api/src/affiliateRevenue/bebop/bebop.ts
  • node/proxy/api/src/affiliateRevenue/bebop/constants.ts
  • node/proxy/api/src/affiliateRevenue/bebop/index.ts
  • node/proxy/api/src/affiliateRevenue/bebop/types.ts
  • node/proxy/api/src/affiliateRevenue/butterswap/butterswap.ts
  • node/proxy/api/src/affiliateRevenue/butterswap/constants.ts
  • node/proxy/api/src/affiliateRevenue/butterswap/index.ts
  • node/proxy/api/src/affiliateRevenue/butterswap/types.ts
  • node/proxy/api/src/affiliateRevenue/butterswap/utils.ts
  • node/proxy/api/src/affiliateRevenue/cache.ts
  • node/proxy/api/src/affiliateRevenue/chainflip.ts
  • node/proxy/api/src/affiliateRevenue/chainflip/chainflip.ts
  • node/proxy/api/src/affiliateRevenue/chainflip/constants.ts
  • node/proxy/api/src/affiliateRevenue/chainflip/index.ts
  • node/proxy/api/src/affiliateRevenue/chainflip/types.ts
  • node/proxy/api/src/affiliateRevenue/constants.ts
  • node/proxy/api/src/affiliateRevenue/index.ts
  • node/proxy/api/src/affiliateRevenue/mayachain.ts
  • node/proxy/api/src/affiliateRevenue/mayachain/constants.ts
  • node/proxy/api/src/affiliateRevenue/mayachain/index.ts
  • node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts
  • node/proxy/api/src/affiliateRevenue/mayachain/types.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents/index.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents/nearIntents.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents/types.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents/utils.ts
  • node/proxy/api/src/affiliateRevenue/portals.ts
  • node/proxy/api/src/affiliateRevenue/portals/constants.ts
  • node/proxy/api/src/affiliateRevenue/portals/index.ts
  • node/proxy/api/src/affiliateRevenue/portals/portals.ts
  • node/proxy/api/src/affiliateRevenue/portals/types.ts
  • node/proxy/api/src/affiliateRevenue/portals/utils.ts
  • node/proxy/api/src/affiliateRevenue/relay/constants.ts
  • node/proxy/api/src/affiliateRevenue/relay/index.ts
  • node/proxy/api/src/affiliateRevenue/relay/relay.ts
  • node/proxy/api/src/affiliateRevenue/relay/types.ts
  • node/proxy/api/src/affiliateRevenue/relay/utils.ts
  • node/proxy/api/src/affiliateRevenue/thorchain.ts
  • node/proxy/api/src/affiliateRevenue/thorchain/constants.ts
  • node/proxy/api/src/affiliateRevenue/thorchain/index.ts
  • node/proxy/api/src/affiliateRevenue/thorchain/thorchain.ts
  • node/proxy/api/src/affiliateRevenue/thorchain/types.ts
  • node/proxy/api/src/affiliateRevenue/zrx.ts
  • node/proxy/api/src/affiliateRevenue/zrx/constants.ts
  • node/proxy/api/src/affiliateRevenue/zrx/index.ts
  • node/proxy/api/src/affiliateRevenue/zrx/types.ts
  • node/proxy/api/src/affiliateRevenue/zrx/zrx.ts
  • node/proxy/api/src/controller.ts
  • node/proxy/api/src/models.ts
  • node/proxy/api/src/swagger.json
  • node/proxy/sample.env
💤 Files with no reviewable changes (7)
  • node/proxy/api/src/affiliateRevenue/bebop.ts
  • node/proxy/api/src/affiliateRevenue/nearIntents.ts
  • node/proxy/api/src/affiliateRevenue/portals.ts
  • node/proxy/api/src/affiliateRevenue/chainflip.ts
  • node/proxy/api/src/affiliateRevenue/mayachain.ts
  • node/proxy/api/src/affiliateRevenue/zrx.ts
  • node/proxy/api/src/affiliateRevenue/thorchain.ts
✅ Files skipped from review due to trivial changes (2)
  • node/proxy/sample.env
  • .gitignore
🧰 Additional context used
🧬 Code graph analysis (22)
node/proxy/api/src/affiliateRevenue/butterswap/utils.ts (3)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
  • MAP_RPC_URL (37-37)
node/proxy/api/src/affiliateRevenue/butterswap/constants.ts (2)
  • MAP_RPC_URL (3-3)
  • BLOCK_TIME_SECONDS (5-5)
node/proxy/api/src/affiliateRevenue/butterswap/types.ts (1)
  • RpcResponse (1-6)
node/proxy/api/src/affiliateRevenue/thorchain/constants.ts (1)
node/proxy/api/src/affiliateRevenue/mayachain/constants.ts (2)
  • PRICE_API_URL (2-2)
  • MILLISECONDS_PER_SECOND (3-3)
node/proxy/api/src/affiliateRevenue/relay/relay.ts (8)
node/proxy/api/src/affiliateRevenue/index.ts (1)
  • Fees (39-47)
node/proxy/api/src/affiliateRevenue/relay/types.ts (1)
  • RelayResponse (45-48)
node/proxy/api/src/affiliateRevenue/relay/constants.ts (2)
  • RELAY_API_URL (3-3)
  • SHAPESHIFT_REFERRER (4-4)
node/proxy/api/src/affiliateRevenue/bebop/constants.ts (1)
  • SHAPESHIFT_REFERRER (6-6)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
  • DAO_TREASURY_BASE (9-9)
node/proxy/api/src/affiliateRevenue/relay/utils.ts (2)
  • getChainConfig (7-26)
  • buildAssetId (28-48)
node/proxy/api/src/affiliateRevenue/relay/index.ts (1)
  • getFees (1-1)
node/proxy/api/src/affiliateRevenue/cache.ts (7)
  • getCacheableThreshold (54-58)
  • splitDateRange (74-93)
  • tryGetCachedFees (64-67)
  • getDateStartTimestamp (46-48)
  • getDateEndTimestamp (50-52)
  • groupFeesByDate (95-107)
  • saveCachedFees (69-72)
node/proxy/api/src/affiliateRevenue/thorchain/types.ts (1)
node/proxy/api/src/affiliateRevenue/mayachain/types.ts (1)
  • FeesResponse (1-11)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
node/proxy/api/src/affiliateRevenue/butterswap/constants.ts (5)
  • MAP_CHAIN_ID (3-3)
  • BUTTERSWAP_CONTRACT (3-3)
  • MAP_USDT_ADDRESS (3-3)
  • MAP_RPC_URL (3-3)
  • BUTTERSWAP_AFFILIATE_ID (3-3)
node/proxy/api/src/affiliateRevenue/zrx/zrx.ts (12)
node/proxy/api/src/affiliateRevenue/index.ts (1)
  • Fees (39-47)
node/proxy/api/src/affiliateRevenue/zrx/constants.ts (3)
  • SERVICES (10-10)
  • ZRX_API_URL (9-9)
  • NATIVE_TOKEN_ADDRESS (3-3)
node/proxy/api/src/affiliateRevenue/zrx/types.ts (1)
  • TradesResponse (7-37)
node/proxy/api/src/affiliateRevenue/constants.ts (2)
  • NATIVE_TOKEN_ADDRESS (1-1)
  • SLIP44 (41-53)
node/proxy/api/src/affiliateRevenue/bebop/bebop.ts (1)
  • getFees (48-89)
node/proxy/api/src/affiliateRevenue/butterswap/butterswap.ts (1)
  • getFees (72-110)
node/proxy/api/src/affiliateRevenue/chainflip/chainflip.ts (1)
  • getFees (62-103)
node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts (1)
  • getFees (55-96)
node/proxy/api/src/affiliateRevenue/nearIntents/nearIntents.ts (1)
  • getFees (89-130)
node/proxy/api/src/affiliateRevenue/portals/portals.ts (1)
  • getFees (281-329)
node/proxy/api/src/affiliateRevenue/relay/relay.ts (1)
  • getFees (70-111)
node/proxy/api/src/affiliateRevenue/cache.ts (7)
  • getCacheableThreshold (54-58)
  • splitDateRange (74-93)
  • tryGetCachedFees (64-67)
  • getDateStartTimestamp (46-48)
  • getDateEndTimestamp (50-52)
  • groupFeesByDate (95-107)
  • saveCachedFees (69-72)
node/proxy/api/src/affiliateRevenue/mayachain/types.ts (1)
node/proxy/api/src/affiliateRevenue/thorchain/types.ts (1)
  • FeesResponse (1-11)
node/proxy/api/src/controller.ts (2)
node/proxy/api/src/models.ts (1)
  • AffiliateRevenueResponse (23-28)
go/pkg/api/api.go (1)
  • Error (7-10)
node/proxy/api/src/affiliateRevenue/thorchain/thorchain.ts (5)
node/proxy/api/src/affiliateRevenue/thorchain/constants.ts (4)
  • PRICE_API_URL (2-2)
  • RUNE_DECIMALS (4-4)
  • MILLISECONDS_PER_SECOND (3-3)
  • THORCHAIN_API_URL (1-1)
node/proxy/api/src/affiliateRevenue/thorchain/types.ts (1)
  • FeesResponse (1-11)
node/proxy/api/src/affiliateRevenue/index.ts (1)
  • Fees (39-47)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
  • THORCHAIN_CHAIN_ID (18-18)
node/proxy/api/src/affiliateRevenue/cache.ts (7)
  • getCacheableThreshold (54-58)
  • splitDateRange (74-93)
  • tryGetCachedFees (64-67)
  • getDateStartTimestamp (46-48)
  • getDateEndTimestamp (50-52)
  • groupFeesByDate (95-107)
  • saveCachedFees (69-72)
node/proxy/api/src/affiliateRevenue/nearIntents/nearIntents.ts (5)
node/proxy/api/src/affiliateRevenue/nearIntents/types.ts (1)
  • TransactionsResponse (3-35)
node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (2)
  • NEAR_INTENTS_API_KEY (21-21)
  • FEE_BPS_DENOMINATOR (22-22)
node/proxy/api/src/affiliateRevenue/nearIntents/utils.ts (2)
  • sleep (62-62)
  • parseNearIntentsAsset (26-60)
node/proxy/api/src/affiliateRevenue/index.ts (1)
  • Fees (39-47)
node/proxy/api/src/affiliateRevenue/cache.ts (7)
  • getCacheableThreshold (54-58)
  • splitDateRange (74-93)
  • tryGetCachedFees (64-67)
  • getDateStartTimestamp (46-48)
  • getDateEndTimestamp (50-52)
  • groupFeesByDate (95-107)
  • saveCachedFees (69-72)
node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (1)
node/proxy/api/src/affiliateRevenue/constants.ts (17)
  • ETHEREUM_CHAIN_ID (24-24)
  • ARBITRUM_CHAIN_ID (31-31)
  • BASE_CHAIN_ID (29-29)
  • GNOSIS_CHAIN_ID (27-27)
  • BSC_CHAIN_ID (26-26)
  • POLYGON_CHAIN_ID (28-28)
  • AVALANCHE_CHAIN_ID (32-32)
  • OPTIMISM_CHAIN_ID (25-25)
  • BITCOIN_CHAIN_ID (12-12)
  • DOGECOIN_CHAIN_ID (13-13)
  • ZCASH_CHAIN_ID (14-14)
  • SOLANA_CHAIN_ID (15-15)
  • TRON_CHAIN_ID (16-16)
  • SUI_CHAIN_ID (17-17)
  • NEAR_CHAIN_ID (20-20)
  • STARKNET_CHAIN_ID (21-21)
  • SLIP44 (41-53)
node/proxy/api/src/affiliateRevenue/portals/constants.ts (4)
node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (1)
  • FEE_BPS_DENOMINATOR (22-22)
node/proxy/api/src/affiliateRevenue/bebop/constants.ts (1)
  • FEE_BPS_DENOMINATOR (8-8)
node/proxy/api/src/affiliateRevenue/portals/types.ts (1)
  • ChainConfig (3-10)
node/proxy/api/src/affiliateRevenue/constants.ts (14)
  • ETHEREUM_CHAIN_ID (24-24)
  • DAO_TREASURY_ETHEREUM (2-2)
  • ARBITRUM_CHAIN_ID (31-31)
  • DAO_TREASURY_ARBITRUM (8-8)
  • OPTIMISM_CHAIN_ID (25-25)
  • DAO_TREASURY_OPTIMISM (3-3)
  • BASE_CHAIN_ID (29-29)
  • DAO_TREASURY_BASE (9-9)
  • POLYGON_CHAIN_ID (28-28)
  • DAO_TREASURY_POLYGON (5-5)
  • GNOSIS_CHAIN_ID (27-27)
  • DAO_TREASURY_GNOSIS (6-6)
  • BSC_CHAIN_ID (26-26)
  • AVALANCHE_CHAIN_ID (32-32)
node/proxy/api/src/affiliateRevenue/bebop/types.ts (1)
node/proxy/api/src/affiliateRevenue/zrx/types.ts (1)
  • TradesResponse (7-37)
node/proxy/api/src/affiliateRevenue/bebop/constants.ts (4)
go/pkg/api/api.go (1)
  • Error (7-10)
node/proxy/api/src/affiliateRevenue/relay/constants.ts (1)
  • SHAPESHIFT_REFERRER (4-4)
node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (1)
  • FEE_BPS_DENOMINATOR (22-22)
node/proxy/api/src/affiliateRevenue/portals/constants.ts (1)
  • FEE_BPS_DENOMINATOR (22-22)
node/proxy/api/src/affiliateRevenue/relay/constants.ts (2)
node/proxy/api/src/affiliateRevenue/bebop/constants.ts (1)
  • SHAPESHIFT_REFERRER (6-6)
node/proxy/api/src/affiliateRevenue/constants.ts (4)
  • SOLANA_CHAIN_ID (15-15)
  • SLIP44 (41-53)
  • BITCOIN_CHAIN_ID (12-12)
  • TRON_CHAIN_ID (16-16)
node/proxy/api/src/affiliateRevenue/index.ts (2)
node/proxy/api/src/models.ts (3)
  • Service (16-16)
  • AffiliateRevenueResponse (23-28)
  • services (5-15)
node/proxy/api/src/affiliateRevenue/cache.ts (1)
  • timestampToDate (24-27)
node/proxy/api/src/affiliateRevenue/relay/utils.ts (1)
node/proxy/api/src/affiliateRevenue/relay/constants.ts (1)
  • NON_EVM_CHAINS (6-12)
node/proxy/api/src/affiliateRevenue/portals/utils.ts (5)
node/proxy/api/src/affiliateRevenue/portals/types.ts (2)
  • BlockscoutTransaction (40-43)
  • DecodedPortalEvent (90-95)
node/proxy/api/src/affiliateRevenue/portals/constants.ts (5)
  • PORTAL_EVENT_ABI (25-31)
  • AFFILIATE_FEE_BPS (21-21)
  • FEE_BPS_DENOMINATOR (22-22)
  • COINGECKO_CHAINS (100-109)
  • COINGECKO_API_BASE (111-111)
node/proxy/api/src/affiliateRevenue/cache.ts (2)
  • getCachedDecimals (118-120)
  • saveCachedDecimals (122-124)
node/proxy/api/src/affiliateRevenue/nearIntents/utils.ts (1)
  • buildAssetId (13-24)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
  • SLIP44 (41-53)
node/proxy/api/src/affiliateRevenue/zrx/types.ts (2)
go/pkg/cosmos/tx.go (1)
  • Fee (281-294)
node/proxy/api/src/affiliateRevenue/bebop/types.ts (1)
  • TradesResponse (1-37)
node/proxy/api/src/affiliateRevenue/chainflip/chainflip.ts (5)
node/proxy/api/src/affiliateRevenue/index.ts (1)
  • Fees (39-47)
node/proxy/api/src/affiliateRevenue/chainflip/types.ts (1)
  • GraphQLResponse (1-17)
node/proxy/api/src/affiliateRevenue/chainflip/constants.ts (4)
  • CHAINFLIP_API_URL (1-1)
  • GET_AFFILIATE_SWAPS_QUERY (5-38)
  • SHAPESHIFT_BROKER_ID (3-3)
  • PAGE_SIZE (2-2)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
  • ETHEREUM_CHAIN_ID (24-24)
node/proxy/api/src/affiliateRevenue/cache.ts (7)
  • getCacheableThreshold (54-58)
  • splitDateRange (74-93)
  • tryGetCachedFees (64-67)
  • getDateStartTimestamp (46-48)
  • getDateEndTimestamp (50-52)
  • groupFeesByDate (95-107)
  • saveCachedFees (69-72)
node/proxy/api/src/affiliateRevenue/cache.ts (1)
node/proxy/api/src/affiliateRevenue/index.ts (1)
  • Fees (39-47)
node/proxy/api/src/affiliateRevenue/nearIntents/utils.ts (2)
node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (2)
  • NEAR_INTENTS_TO_CHAIN_ID (26-44)
  • SLIP44_BY_NETWORK (46-55)
node/proxy/api/src/affiliateRevenue/constants.ts (1)
  • SLIP44 (41-53)
🪛 Checkov (3.2.334)
node/proxy/api/src/swagger.json

[medium] 175-180: Ensure that arrays have a maximum number of items

(CKV_OPENAPI_21)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check
🔇 Additional comments (57)
node/proxy/api/src/affiliateRevenue/zrx/zrx.ts (1)

58-98: Caching implementation is well-structured.

The date-range splitting, batch fetching of missing dates, and per-day caching approach is efficient and follows the established pattern across other affiliate revenue modules.

node/proxy/api/src/affiliateRevenue/zrx/index.ts (1)

1-1: Clean barrel export pattern.

Consistent with other affiliate revenue module structures.

node/proxy/api/src/affiliateRevenue/nearIntents/index.ts (1)

1-1: LGTM!

Standard barrel export, consistent with the module structure pattern.

node/proxy/api/src/affiliateRevenue/mayachain/index.ts (1)

1-1: LGTM!

Consistent barrel export pattern for the Mayachain module.

node/proxy/api/src/affiliateRevenue/relay/index.ts (1)

1-1: LGTM!

Consistent barrel export pattern for the Relay module.

node/proxy/api/src/affiliateRevenue/chainflip/index.ts (1)

1-1: LGTM!

The barrel export follows the standard pattern and correctly exposes the getFees function from the chainflip module.

node/proxy/api/src/affiliateRevenue/thorchain/index.ts (1)

1-1: LGTM!

The barrel export follows the standard pattern and correctly exposes the getFees function from the thorchain module.

node/proxy/api/src/affiliateRevenue/portals/index.ts (1)

1-1: LGTM!

The barrel export follows the standard pattern and correctly exposes the getFees function from the portals module.

node/proxy/api/src/affiliateRevenue/bebop/index.ts (1)

1-1: LGTM!

The barrel export follows the standard pattern and correctly exposes the getFees function from the bebop module.

node/proxy/api/src/affiliateRevenue/butterswap/index.ts (1)

1-1: LGTM!

The barrel export follows the standard pattern and correctly exposes the getFees function from the butterswap module.

node/proxy/api/src/affiliateRevenue/butterswap/types.ts (2)

8-15: LGTM!

The TokenListResponse type structure is clear and appropriate for modeling the butterswap token list API response.


1-6: Consumer code properly handles JSON-RPC error semantics—no action needed.

The rpcCall function in utils.ts correctly checks for the presence of error (line 18) and throws before accessing result (line 22). This prevents processing invalid response states where both fields might theoretically exist according to the type definition.

While the type definition is permissive in allowing both result and error fields simultaneously, the runtime behavior enforces the correct JSON-RPC 2.0 semantics where they are mutually exclusive in practice.

node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts (1)

27-40: LGTM!

The fee transformation logic correctly maps external API fields to the internal Fees format, and the timestamp conversion from milliseconds to seconds (line 36) is accurate.

node/proxy/api/src/affiliateRevenue/constants.ts (1)

10-56: LGTM!

The constants are well-organized and follow standard formats (CAIP-2 for chain IDs, SLIP44 for coin types). The use of as const on the SLIP44 object provides proper type narrowing.

node/proxy/api/src/affiliateRevenue/butterswap/utils.ts (1)

25-33: LGTM!

The block estimation logic is correct: it calculates the time difference, divides by block time, and properly clamps the result to valid bounds.

node/proxy/api/src/affiliateRevenue/bebop/types.ts (1)

1-37: LGTM!

The type definition is well-structured with appropriate use of optional fields and union types.

node/proxy/api/src/affiliateRevenue/chainflip/types.ts (1)

1-17: LGTM!

The GraphQL response type is properly structured and appropriately models the expected API response shape.

node/proxy/api/src/affiliateRevenue/thorchain/constants.ts (1)

1-4: LGTM!

The constants are correctly defined. Note that the duplication of PRICE_API_URL and MILLISECONDS_PER_SECOND with mayachain constants has been flagged for potential centralization in the mayachain file review.

node/proxy/api/src/affiliateRevenue/bebop/constants.ts (1)

1-3: Verify the fail-fast behavior for missing API key.

The module throws immediately if BEBOP_API_KEY is not set, which will happen at module load time. This means:

  • Any import of this module will fail if the key is missing
  • The application cannot start without the key, even if Bebop functionality isn't used

Please confirm this fail-fast behavior is intentional. If the key is only needed when Bebop is actually called, consider lazy evaluation or moving the check to the function that uses it.

node/proxy/api/src/affiliateRevenue/nearIntents/constants.ts (1)

46-55: LGTM!

The SLIP44 mapping correctly covers non-EVM networks. EVM networks can default to SLIP44.ETHEREUM (60) in the consuming code.

node/proxy/api/src/affiliateRevenue/relay/relay.ts (1)

70-111: LGTM!

The caching implementation follows a consistent pattern with other provider modules, properly splitting cacheable historical dates from recent data, and logging cache statistics.

node/proxy/api/src/affiliateRevenue/thorchain/thorchain.ts (1)

55-96: LGTM!

The caching implementation correctly handles date-based splitting and follows the established pattern used by other provider modules.

node/proxy/api/src/affiliateRevenue/relay/types.ts (1)

1-34: LGTM!

The type definitions properly model the Relay API response structure with appropriate optional fields.

node/proxy/api/src/affiliateRevenue/chainflip/constants.ts (1)

1-38: LGTM!

The constants are well-defined with appropriate naming. The GraphQL query is properly parameterized and includes necessary pagination and filtering.

node/proxy/api/src/affiliateRevenue/portals/portals.ts (1)

125-171: LGTM!

The Etherscan implementation efficiently uses topic filtering and implements early exit when reaching the start timestamp boundary.

node/proxy/api/src/affiliateRevenue/index.ts (2)

26-37: LGTM!

The formatError helper provides good error context for debugging, handling axios errors with status codes and generic errors appropriately.


76-101: LGTM!

The aggregation logic correctly builds per-date and per-service breakdowns, and properly handles the new byDate and failedProviders fields in the response.

node/proxy/api/src/affiliateRevenue/nearIntents/types.ts (1)

1-35: LGTM!

The type definitions are well-structured, accurately model the Near Intents API response, and properly support pagination with optional nextPage/prevPage fields.

node/proxy/api/src/affiliateRevenue/chainflip/chainflip.ts (1)

62-103: LGTM!

The caching logic is well-implemented with efficient batching of cache misses into a single API call, proper date-based grouping, and separate handling for recent (non-cacheable) data.

node/proxy/api/src/affiliateRevenue/butterswap/butterswap.ts (3)

26-46: LGTM!

The token list fetching with TTL-based caching and graceful fallback to static tokens provides good resilience against API failures.


48-66: LGTM!

The RPC calls for block number and balance queries are properly implemented with correct ABI encoding via viem.


81-87: LGTM!

Good error handling pattern that wraps balance query failures with a descriptive error message while preserving the original error context.

node/proxy/api/src/affiliateRevenue/nearIntents/nearIntents.ts (2)

63-75: LGTM!

The fee calculation correctly uses basis points, and the multi-source txHash fallback chain provides good resilience. The 1-second delay between page fetches is a reasonable rate-limiting precaution.


89-130: LGTM!

The caching logic follows the established pattern across provider modules and correctly uses 'all' as the chain identifier since Near Intents transactions span multiple chains.

node/proxy/api/src/models.ts (1)

5-28: LGTM!

The type definitions properly support the graceful error handling feature:

  • services array includes all providers
  • DailyRevenue enables per-day breakdowns
  • failedProviders: Service[] allows the API to indicate which providers failed, enabling the UI to show appropriate indicators
node/proxy/api/src/affiliateRevenue/relay/utils.ts (2)

3-26: LGTM!

The chain detection logic is well-structured with explicit mappings for known non-EVM chains in NON_EVM_CHAINS, a reasonable heuristic fallback (chain ID > 1M), and sensible defaults for EVM chains.


43-47: Non-EVM token addresses are discarded for non-native assets.

For non-EVM chains with non-native tokens, the function returns ${chainId}/slip44:${slip44}, effectively discarding the token address. If Relay supports non-native tokens on chains like Solana, this could conflate different SPL tokens under the same asset ID.

Is this intentional because Relay only handles native assets on non-EVM chains?

node/proxy/api/src/affiliateRevenue/bebop/bebop.ts (1)

48-89: LGTM!

The caching logic follows the established pattern and is consistent with other provider modules.

node/proxy/api/src/affiliateRevenue/portals/constants.ts (2)

100-111: LGTM!

The CoinGecko chain mappings correctly map chain IDs to platform identifiers and native coin IDs, and appropriately use the ShapeShift proxy endpoint.


82-89: The import is already present. DAO_TREASURY_BSC is correctly imported in lines 1-18 from ../constants. No action is needed.

Likely an incorrect or invalid review comment.

node/proxy/api/src/affiliateRevenue/nearIntents/utils.ts (4)

5-11: LGTM - Clean chain ID resolution with appropriate warning.

The function correctly handles unknown networks by logging a warning and returning undefined, allowing callers to provide fallbacks.


13-24: LGTM - Asset ID construction handles all chain types.

The function correctly builds asset IDs for unknown, EIP-155, and SLIP44-based chains with appropriate fallback logic.


26-60: LGTM - Comprehensive asset parsing with graceful fallbacks.

The parser handles multiple NEP formats correctly:

  • nep141:...-0x....omft.near for EVM tokens
  • nep141:....near for native NEP-141 tokens
  • nep245:v2_1.omni.hot.tg:... for omni tokens
  • Fallback with warning for unrecognized formats

Good defensive coding with the fallback on line 47 ('near:mainnet') if resolveChainId('near') unexpectedly returns undefined.


62-62: LGTM - Simple utility function.

node/proxy/api/src/affiliateRevenue/butterswap/constants.ts (1)

1-8: LGTM - Well-organized constants with sensible defaults.

The re-exports consolidate access and the TTL of 1 hour for token cache is reasonable.

node/proxy/api/src/affiliateRevenue/portals/types.ts (1)

1-95: LGTM - Well-structured type definitions.

The types are cleanly organized with clear separation between Blockscout and Etherscan API response structures. The ExplorerType union provides type safety for explorer selection.

node/proxy/api/src/swagger.json (5)

77-80: LGTM - Service schema extended correctly.

The addition of butterswap and relay services to Record_Service.number_ with proper required fields maintains consistency.

Also applies to: 97-100, 112-112, 117-117


124-148: LGTM - New schemas support daily revenue breakdown.

DailyRevenue and Record_string.DailyRevenue_ schemas properly model the per-date revenue structure.


149-162: LGTM - Service enum provides type safety.

The Service enum covers all 9 affiliate revenue providers consistently.


163-190: Good addition of failedProviders for graceful degradation.

The failedProviders array aligns with the PR objective to indicate which swapper(s) failed, enabling UI indicators. The static analysis hint about maxItems can be safely ignored here since the array is bounded by the number of services (currently 9).


323-341: Breaking API change: parameters renamed and type changed.

The endpoint parameters changed from startTimestamp/endTimestamp (number) to startDate/endDate (string, YYYY-MM-DD). This is a breaking change for existing API consumers.

Ensure this is documented in release notes and consider if a versioned endpoint or deprecation period is needed.

node/proxy/api/src/affiliateRevenue/cache.ts (5)

5-12: LGTM - Well-configured LRU cache with reasonable limits.

The cache configuration with size estimation, 90-day TTL, and updateAgeOnGet: true is appropriate for fee data that should remain accessible while being actively used.


14-22: LGTM - Separate caches with appropriate TTLs.

Token transfer cache (7-day TTL) and decimals cache (90-day TTL) have sensible configurations for their respective data volatility.


74-93: LGTM - Clean date range splitting logic.

The splitDateRange function correctly separates cacheable historical dates from recent dates that shouldn't be cached, using cacheableThreshold as the boundary.


95-107: LGTM - Straightforward fee grouping by date.

The implementation is clean and efficient for the use case.


109-124: Good cache pattern for nullable values.

Wrapping the cached value in { transfer } object allows distinguishing between "not cached" (undefined) and "cached as null" (null). This prevents redundant API calls for tokens that legitimately have no transfer data.

node/proxy/api/src/affiliateRevenue/zrx/types.ts (1)

7-37: No action needed — the type definition is correct.

Both zid fields are intentional and part of the actual 0x API response structure. The top-level zid serves as a request/correlation identifier, while the per-trade zid is a trade-specific identifier. The type definition accurately reflects this structure.

Comment on lines +17 to +19
"elliptic-sdk": "^0.7.2",
"lru-cache": "^10.2.0",
"viem": "^2.33.2"
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

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
# Check latest versions of added dependencies
echo "Checking lru-cache..."
curl -s https://registry.npmjs.org/lru-cache/latest | jq '.version'
echo "Checking viem..."
curl -s https://registry.npmjs.org/viem/latest | jq '.version'

Repository: shapeshift/unchained

Length of output: 120


Consider updating lru-cache to a more recent major version.

The viem dependency at ^2.33.2 is reasonably current (latest is 2.43.3). However, lru-cache at ^10.2.0 is outdated; version 11.2.4 is available. Consider upgrading to ^11.x if it's compatible with your caching implementation, as the major version bump typically includes performance improvements and fixes.

🤖 Prompt for AI Agents
In node/proxy/api/package.json around lines 17 to 19, the lru-cache dependency
is pinned to ^10.2.0 which is outdated; update the version to ^11.x (e.g.,
^11.2.4) in package.json, run npm/yarn install to refresh lockfile, and run the
test suite and any code paths that use LRU cache to verify there are no breaking
API changes (adjust imports/options if needed per v11 release notes).

Copy link
Author

Choose a reason for hiding this comment

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

Major version upgrade (10->11) introduces breaking change risk for a working dependency. Not worth the upgrade risk for this PR. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

@premiumjibles, understood! That's a sensible approach—keeping the working dependency stable for this PR avoids unnecessary risk. If a future dedicated maintenance or dependency update PR comes around, the lru-cache upgrade can be evaluated then with appropriate testing.

Comment on lines +16 to +25
const getCacaoPriceUsd = async (): Promise<number> => {
const { data } = await axios.get<{ cacao: { usd: string } }>(PRICE_API_URL, {
params: {
vs_currencies: 'usd',
ids: 'cacao',
},
})

return Number(data.cacao.usd)
}
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling to prevent cascade failures.

The getCacaoPriceUsd function lacks error handling for the axios call. If the price API fails, it will cause the entire fee fetch to fail, contradicting the PR's objective of graceful error handling for partial data returns.

Consider wrapping the axios call in try-catch and either returning a fallback price or re-throwing with context.

🔎 Proposed fix
 const getCacaoPriceUsd = async (): Promise<number> => {
+  try {
     const { data } = await axios.get<{ cacao: { usd: string } }>(PRICE_API_URL, {
       params: {
         vs_currencies: 'usd',
         ids: 'cacao',
       },
     })
 
     return Number(data.cacao.usd)
+  } catch (error) {
+    console.error('[mayachain] Failed to fetch cacao price:', error)
+    throw new Error('Failed to fetch cacao price from CoinGecko')
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getCacaoPriceUsd = async (): Promise<number> => {
const { data } = await axios.get<{ cacao: { usd: string } }>(PRICE_API_URL, {
params: {
vs_currencies: 'usd',
ids: 'cacao',
},
})
return Number(data.cacao.usd)
}
const getCacaoPriceUsd = async (): Promise<number> => {
try {
const { data } = await axios.get<{ cacao: { usd: string } }>(PRICE_API_URL, {
params: {
vs_currencies: 'usd',
ids: 'cacao',
},
})
return Number(data.cacao.usd)
} catch (error) {
console.error('[mayachain] Failed to fetch cacao price:', error)
throw new Error('Failed to fetch cacao price from CoinGecko')
}
}
🤖 Prompt for AI Agents
In node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts around lines 16 to
25, the axios call in getCacaoPriceUsd has no error handling; wrap the request
in a try-catch, catch any errors, log the error with context (e.g.,
"getCacaoPriceUsd failed"), and return a safe fallback numeric price (e.g., 0)
so callers receive a number and downstream logic doesn't cascade-fail; ensure
the function signature remains Promise<number> and do not throw from this
function unless you want callers to handle the failure explicitly.

Copy link
Author

Choose a reason for hiding this comment

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

Top-level Promise.allSettled in index.ts already handles provider failures gracefully. Adding try-catch that just logs and re-throws adds no value. The architectural pattern is: providers either succeed fully or fail entirely, with graceful degradation at the aggregator level. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

@premiumjibles, understood. You're right that with Promise.allSettled at the aggregator level in index.ts, the mayachain provider can fail entirely and will be tracked in failedProviders, allowing the endpoint to return partial data from other successful providers. Adding intermediate try-catch that just re-throws would indeed be redundant since the rejection is already handled gracefully upstream.

Thanks for clarifying the architectural pattern!


✏️ Learnings added
Learnt from: premiumjibles
Repo: shapeshift/unchained PR: 1229
File: node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts:16-25
Timestamp: 2025-12-29T06:29:11.197Z
Learning: In the affiliate revenue tracking system (node/proxy/api/src/affiliateRevenue/), the architectural pattern is to let individual provider modules (mayachain, thorchain, etc.) fail entirely rather than catching and re-throwing errors. The top-level aggregator in index.ts uses Promise.allSettled to handle provider failures gracefully, tracking failed providers in the failedProviders array while returning partial data from successful providers. This design allows for graceful degradation without redundant error handling in provider modules.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +42 to +53
const fetchFeesFromAPI = async (startTimestamp: number, endTimestamp: number): Promise<Fees[]> => {
const start = startTimestamp * MILLISECONDS_PER_SECOND
const end = endTimestamp * MILLISECONDS_PER_SECOND

const { data } = await axios.get<FeesResponse>(MAYACHAIN_API_URL, {
params: { start, end },
})

const cacaoPriceUsd = await getCacaoPriceUsd()

return data.fees.map(fee => transformFee(fee, cacaoPriceUsd))
}
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling to align with PR objectives.

The fetchFeesFromAPI function lacks error handling for both the Mayachain API call and the price fetch. Since the PR objective is to add graceful error handling so partial data is returned when one swapper API fails, this function should handle failures gracefully rather than propagating exceptions.

🔎 Proposed fix
 const fetchFeesFromAPI = async (startTimestamp: number, endTimestamp: number): Promise<Fees[]> => {
+  try {
     const start = startTimestamp * MILLISECONDS_PER_SECOND
     const end = endTimestamp * MILLISECONDS_PER_SECOND
 
     const { data } = await axios.get<FeesResponse>(MAYACHAIN_API_URL, {
       params: { start, end },
     })
 
     const cacaoPriceUsd = await getCacaoPriceUsd()
 
     return data.fees.map(fee => transformFee(fee, cacaoPriceUsd))
+  } catch (error) {
+    console.error('[mayachain] Failed to fetch fees from API:', error)
+    throw error // Re-throw to be caught by caller
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchFeesFromAPI = async (startTimestamp: number, endTimestamp: number): Promise<Fees[]> => {
const start = startTimestamp * MILLISECONDS_PER_SECOND
const end = endTimestamp * MILLISECONDS_PER_SECOND
const { data } = await axios.get<FeesResponse>(MAYACHAIN_API_URL, {
params: { start, end },
})
const cacaoPriceUsd = await getCacaoPriceUsd()
return data.fees.map(fee => transformFee(fee, cacaoPriceUsd))
}
const fetchFeesFromAPI = async (startTimestamp: number, endTimestamp: number): Promise<Fees[]> => {
try {
const start = startTimestamp * MILLISECONDS_PER_SECOND
const end = endTimestamp * MILLISECONDS_PER_SECOND
const { data } = await axios.get<FeesResponse>(MAYACHAIN_API_URL, {
params: { start, end },
})
const cacaoPriceUsd = await getCacaoPriceUsd()
return data.fees.map(fee => transformFee(fee, cacaoPriceUsd))
} catch (error) {
console.error('[mayachain] Failed to fetch fees from API:', error)
throw error // Re-throw to be caught by caller
}
}

Copy link
Author

Choose a reason for hiding this comment

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

Top-level Promise.allSettled in index.ts already handles provider failures. Adding redundant try-catch blocks would duplicate error handling that exists at the aggregator level. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

@premiumjibles, understood! You're correct that Promise.allSettled at the aggregator level in index.ts handles provider failures gracefully, so individual try-catch blocks in each provider's fetchFeesFromAPI would indeed be redundant. The error handling architecture makes sense—failures are caught and tracked at the top level where failedProviders is managed.

Thank you for the clarification!

Comment on lines +50 to +52
const cacaoPriceUsd = await getCacaoPriceUsd()

return data.fees.map(fee => transformFee(fee, cacaoPriceUsd))
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

CoinGecko API historical price lookups specific timestamp

💡 Result:

Short answer: use /coins/{id}/market_chart/range to get timestamped price points (returns [unix-ms, price]) and query a narrow from/to window around your desired timestamp, then take the nearest point or interpolate. If you need the day snapshot at 00:00:00 UTC, use /coins/{id}/history?date=DD-MM-YYYY (that endpoint returns the price at 00:00:00 UTC). Note: granularity is automatic (5m/hourly/daily depending on range) and timestamps in market_chart/range are in milliseconds. Example:

  • GET /coins/{id}/market_chart/range?vs_currency=usd&from=UNIX_FROM&to=UNIX_TO → returns prices as [[timestamp_ms, price], ...]. Pick nearest timestamp or interpolate. [1]
  • GET /coins/{id}/history?date=30-12-2023 → returns market_data for that date at 00:00:00 UTC. [2]

Docs / details on granularity, ISO vs UNIX usage and tips: CoinGecko API docs. [3]

Sources: CoinGecko API docs — market_chart/range, coins/{id}/history, historical-data overview. [1][2][3]


🏁 Script executed:

# Find and examine the mayachain.ts file
fd mayachain.ts -t f

Repository: shapeshift/unchained

Length of output: 122


🏁 Script executed:

cat -n node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts

Repository: shapeshift/unchained

Length of output: 3654


Address price staleness for historical fee data.

The current implementation fetches the cacao price once and applies it to all fees in the date range. Since fees are grouped by date and cached (lines 81-84), the USD valuations become permanently fixed at fetch time rather than at each fee's transaction timestamp. For historical data spanning multiple days, this results in inaccurate USD valuations.

The CoinGecko API supports historical price lookups via /coins/{id}/history?date=DD-MM-YYYY (returns price at 00:00:00 UTC) or /coins/{id}/market_chart/range (returns timestamped prices), allowing per-day or per-timestamp price fetches. Consider refactoring getCacaoPriceUsd() to accept an optional timestamp parameter and fetch historical prices when processing cached date ranges.

🤖 Prompt for AI Agents
In node/proxy/api/src/affiliateRevenue/mayachain/mayachain.ts around lines 50 to
52, the code currently fetches cacao price once and applies it to all fees
causing stale USD valuations for historical data; modify getCacaoPriceUsd to
accept an optional timestamp (or date) and return the price at that time, then
refactor this function call here to group fees by date or timestamp, fetch
prices per-group (or per-fee if necessary) — preferably using CoinGecko's
/market_chart/range for ranges or /history?date=DD-MM-YYYY for per-day values —
cache those per-date prices to avoid N calls, and then map fees to
transformFee(fee, priceAtFeeTimestamp); also update the cached date-range
handling (lines ~81-84) to store/compute USD valuations using timestamped prices
rather than a single global price.

Copy link
Author

Choose a reason for hiding this comment

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

Valid observation, but implementing historical price lookups would significantly increase complexity (multiple CoinGecko API calls, rate limiting, per-date caching). Current approach using current price is a reasonable trade-off for an internal analytics endpoint. Historical fee amounts remain accurate; only USD valuations use current prices. Consider for future enhancement.

Copy link

Choose a reason for hiding this comment

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

@premiumjibles, understood and fair point. The trade-off between accuracy and complexity is reasonable for an internal analytics endpoint, especially given the challenges with rate limiting and caching multiple historical price calls. The native fee amounts remain accurate for auditing purposes, and current-price USD valuations provide sufficient directional insight for analytics. Thanks for considering it for future enhancement when the added complexity might be justified.

Comment on lines +6 to +12
export const NON_EVM_CHAINS: Record<number, { chainId: string; slip44: number }> = {
792703809: { chainId: SOLANA_CHAIN_ID, slip44: SLIP44.SOLANA },
8253038: { chainId: BITCOIN_CHAIN_ID, slip44: SLIP44.BITCOIN },
728126428: { chainId: TRON_CHAIN_ID, slip44: SLIP44.TRON },
9286185: { chainId: 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z', slip44: SLIP44.SOLANA },
9286186: { chainId: 'solana:soon', slip44: SLIP44.SOLANA },
}
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

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:

# Search for references to 'solana:soon' and related documentation
rg -n "solana:soon" -A 3 -B 3

Repository: shapeshift/unchained

Length of output: 633


🏁 Script executed:

# Search for references to the ID 9286186
rg -n "9286186" -A 3 -B 3

Repository: shapeshift/unchained

Length of output: 633


🏁 Script executed:

# Check the constants file to see full context
cat -n node/proxy/api/src/affiliateRevenue/relay/constants.ts

Repository: shapeshift/unchained

Length of output: 772


🏁 Script executed:

# Search for any test files related to this constant or affiliate revenue
fd -e test.ts -e spec.ts | xargs grep -l "NON_EVM_CHAINS\|9286186\|solana:soon" 2>/dev/null

Repository: shapeshift/unchained

Length of output: 48


🏁 Script executed:

# Search for where NON_EVM_CHAINS is used
rg -n "NON_EVM_CHAINS" -A 2 -B 2

Repository: shapeshift/unchained

Length of output: 1388


🏁 Script executed:

# Search for any comments around this file or related to solana chains
rg -n "9286186|solana.*soon" --type ts --type js

Repository: shapeshift/unchained

Length of output: 183


🏁 Script executed:

# Check if there are any migration or TODO comments in the relay directory
fd -e ts -e js -path "*/relay/*" | xargs grep -l "TODO\|FIXME\|placeholder\|soon" 2>/dev/null | head -20

Repository: shapeshift/unchained

Length of output: 232


🏁 Script executed:

# Find where getChainConfig is called
rg -n "getChainConfig" -A 2 -B 2

Repository: shapeshift/unchained

Length of output: 1533


🏁 Script executed:

# Search for any relay/api documentation or handlers that might explain the numeric IDs
rg -n "9286\|relay.*solana\|solana.*relay" --type ts | head -30

Repository: shapeshift/unchained

Length of output: 46


🏁 Script executed:

# Check git history or comments around the NON_EVM_CHAINS constant
git log -p --all -S "9286186" -- node/proxy/api/src/affiliateRevenue/relay/constants.ts 2>/dev/null | head -50

Repository: shapeshift/unchained

Length of output: 46


Remove or document the 'solana:soon' placeholder chain ID.

Line 11 contains 'solana:soon' as a chainId value, which is not a valid chain identifier. Since getChainConfig is called with numeric IDs from the relay API (relay.ts:45), if the API sends 9286186, this placeholder will propagate to downstream code that likely expects valid CAIP-2 format chain IDs. Either replace it with the actual Solana chain ID, document why it's needed, or remove the entry entirely if it's unused.

🤖 Prompt for AI Agents
In node/proxy/api/src/affiliateRevenue/relay/constants.ts around lines 6 to 12,
the entry with numeric key 9286186 uses the invalid placeholder chainId
'solana:soon'; replace it with the correct Solana CAIP-2 chain ID (or the proper
account-scoped CAIP string if required), or remove this entire map entry if the
numeric ID is unused, and if you must keep a placeholder instead add a comment
explaining why and when it will be valid so downstream code won’t receive a
non-CAIP chainId.

Copy link
Author

Choose a reason for hiding this comment

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

The 'solana:soon' placeholder appears intentional for upcoming chain support. Consider adding a comment to document, but this doesn't block the PR. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

@premiumjibles, thanks for the context! I understand the placeholder is intentional for upcoming support. Acknowledging and moving forward.

const { chainId, slip44, isEvm } = getChainConfig(currencyObject.chainId)
const assetId = buildAssetId(chainId, slip44, currencyObject.address, isEvm)

const txHash = request.data?.inTxs?.[0]?.hash ?? ''
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Empty txHash fallback may cause data quality issues.

If inTxs is empty or missing, txHash defaults to an empty string. This could complicate deduplication or transaction tracking. Consider logging a warning or skipping entries without a valid txHash.

🤖 Prompt for AI Agents
In node/proxy/api/src/affiliateRevenue/relay/relay.ts around line 48, the code
falls back to an empty string when extracting txHash using const txHash =
request.data?.inTxs?.[0]?.hash ?? '' which can lead to bad data for
deduplication/tracking; change the flow to require a non-empty txHash — either
skip processing this entry when no valid hash exists or early-return, and emit a
warning/error log that includes identifying context (request id or other
metadata) when inTxs or its first hash is missing so you don’t silently insert
empty txHash values.

Copy link
Author

Choose a reason for hiding this comment

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

Current behavior preserves fee data even without txHash, which is valuable. No deduplication logic exists that would break with empty txHash. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@premiumjibles have exceeded the limit for the number of chat messages per hour. Please wait 2 minutes and 8 seconds before sending another message.

Comment on lines +35 to +43
export type RelayRequest = {
id: string
status: string
user: string
recipient: string
createdAt: string
updatedAt: string
data: RequestData
}
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

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
# First, examine the types.ts file to see the RelayRequest definition
cat -n node/proxy/api/src/affiliateRevenue/relay/types.ts | head -50

Repository: shapeshift/unchained

Length of output: 1169


🏁 Script executed:

#!/bin/bash
# Check relay.ts to see how request.data is accessed around line 33
cat -n node/proxy/api/src/affiliateRevenue/relay/relay.ts | sed -n '25,45p'

Repository: shapeshift/unchained

Length of output: 830


🏁 Script executed:

#!/bin/bash
# Search for all usages of request.data in the relay directory
rg -n 'request\.data' node/proxy/api/src/affiliateRevenue/relay/

Repository: shapeshift/unchained

Length of output: 421


Make data field optional in RelayRequest type.

The type defines data: RequestData as required, but relay.ts consistently accesses it with optional chaining (request.data?.appFees, request.data?.feeCurrencyObject, request.data?.inTxs?.[0]?.hash). Change the type to data?: RequestData to align the type definition with actual usage and API behavior.

🤖 Prompt for AI Agents
In node/proxy/api/src/affiliateRevenue/relay/types.ts around lines 35 to 43, the
RelayRequest type declares data as required but the codebase treats it as
optional; change the declaration from data: RequestData to data?: RequestData so
the type matches runtime/usage (update any related imports or type references if
necessary).

Copy link
Author

Choose a reason for hiding this comment

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

This comment appears to be auto-generated analysis without a specific actionable suggestion. Dismissing.

Copy link

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@premiumjibles have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 47 seconds before sending another message.

Comment on lines +5 to +7
export const ZRX_API_KEY = process.env.ZRX_API_KEY

if (!ZRX_API_KEY) throw new Error('ZRX_API_KEY env var not set')
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Module-level throw prevents graceful degradation.

This runtime throw at module load time will crash the application if ZRX_API_KEY is not set, even if ZRX is never called. This contradicts the PR's goal of graceful error handling where one swapper failing shouldn't break others.

Consider deferring the validation to when the key is actually used, or handle the missing key case gracefully.

🔎 Proposed fix for lazy validation
-export const ZRX_API_KEY = process.env.ZRX_API_KEY
-
-if (!ZRX_API_KEY) throw new Error('ZRX_API_KEY env var not set')
+export const ZRX_API_KEY = process.env.ZRX_API_KEY ?? ''
+
+export const assertZrxApiKey = (): string => {
+  if (!ZRX_API_KEY) throw new Error('ZRX_API_KEY env var not set')
+  return ZRX_API_KEY
+}

Then call assertZrxApiKey() at the start of fetchFeesFromAPI in zrx.ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ZRX_API_KEY = process.env.ZRX_API_KEY
if (!ZRX_API_KEY) throw new Error('ZRX_API_KEY env var not set')
export const ZRX_API_KEY = process.env.ZRX_API_KEY ?? ''
export const assertZrxApiKey = (): string => {
if (!ZRX_API_KEY) throw new Error('ZRX_API_KEY env var not set')
return ZRX_API_KEY
}
🤖 Prompt for AI Agents
In node/proxy/api/src/affiliateRevenue/zrx/constants.ts around lines 5-7, the
module-level throw on missing ZRX_API_KEY should be removed because it crashes
the whole app at import time; instead export the raw env value (may be
undefined) and add a small helper function (e.g., assertZrxApiKey or
getZrxApiKeyOrThrow) that checks process.env.ZRX_API_KEY and throws only when
invoked; then call that helper at the start of fetchFeesFromAPI in zrx.ts so
validation is deferred to actual use and the application can degrade gracefully
when ZRX is unused.

Copy link
Author

Choose a reason for hiding this comment

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

Top-level Promise.allSettled in index.ts handles provider failures gracefully. If ZRX_API_KEY is missing, the provider will fail and be marked in failedProviders. This doesn't prevent graceful degradation of the endpoint. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@premiumjibles have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 45 seconds before sending another message.

Comment on lines +52 to +70
// Validate date format (YYYY-MM-DD)
const dateRegex = /^\d{4}-\d{2}-\d{2}$/
if (!dateRegex.test(startDate)) {
throw new Error('Invalid startDate format, expected YYYY-MM-DD')
}
if (!dateRegex.test(endDate)) {
throw new Error('Invalid endDate format, expected YYYY-MM-DD')
}

// Validate dates are valid calendar dates
const startTimestamp = Math.floor(new Date(`${startDate}T00:00:00Z`).getTime() / 1000)
const endTimestamp = Math.floor(new Date(`${endDate}T23:59:59Z`).getTime() / 1000)

if (isNaN(startTimestamp)) {
throw new Error('Invalid startDate value')
}
if (isNaN(endTimestamp)) {
throw new Error('Invalid endDate value')
}
Copy link

@coderabbitai coderabbitai bot Dec 29, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Date validation allows invalid calendar dates.

The regex validates format, but JavaScript's Date constructor is lenient with invalid dates like 2024-02-30 (silently rolls over to March 1st). Consider stricter validation to reject truly invalid dates.

Additionally, there's no check that startDate <= endDate.

🔎 Proposed fix
       // Validate dates are valid calendar dates
       const startTimestamp = Math.floor(new Date(`${startDate}T00:00:00Z`).getTime() / 1000)
       const endTimestamp = Math.floor(new Date(`${endDate}T23:59:59Z`).getTime() / 1000)

       if (isNaN(startTimestamp)) {
         throw new Error('Invalid startDate value')
       }
       if (isNaN(endTimestamp)) {
         throw new Error('Invalid endDate value')
       }
+      
+      // Validate dates didn't roll over (e.g., 2024-02-30 -> 2024-03-01)
+      const parsedStartDate = new Date(startTimestamp * 1000).toISOString().split('T')[0]
+      const parsedEndDate = new Date(endTimestamp * 1000).toISOString().split('T')[0]
+      if (parsedStartDate !== startDate) {
+        throw new Error('Invalid startDate: date does not exist')
+      }
+      if (parsedEndDate !== endDate) {
+        throw new Error('Invalid endDate: date does not exist')
+      }
+
+      if (startTimestamp > endTimestamp) {
+        throw new Error('startDate must be before or equal to endDate')
+      }
🤖 Prompt for AI Agents
In node/proxy/api/src/controller.ts around lines 52 to 70, the format-only regex
allows invalid calendar dates (e.g. 2024-02-30) and there is no check that
startDate <= endDate; fix by parsing year, month, day from the YYYY-MM-DD
string, constructing a Date using UTC (Date.UTC(year, month-1, day)), then
verify that the constructed Date's UTC year, month+1 and day exactly match the
parsed components to reject rolled-over invalid dates; compute startTimestamp
and endTimestamp from these validated UTC Dates and add a check that
startTimestamp <= endTimestamp, throwing clear errors if validation fails.

Copy link
Author

Choose a reason for hiding this comment

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

The regex validates YYYY-MM-DD format. Invalid calendar dates (e.g., 2025-02-30) will be caught by downstream date parsing/validation. Additional calendar validation would add complexity for minimal benefit. Dismissing this suggestion.

Copy link

Choose a reason for hiding this comment

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

Rate Limit Exceeded

@premiumjibles have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 40 seconds before sending another message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants