-
Notifications
You must be signed in to change notification settings - Fork 49
feat: finish implementing affiliate revenue endpoint #1229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughReworks affiliate revenue aggregation: uses Promise.allSettled to collect provider results, records per-provider failures in Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 addingmaxItemsconstraint to the array.The
failedProvidersfield is correctly defined and marked as required. The array is naturally bounded by the number of services (7), but adding an explicitmaxItemsconstraint 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.
📒 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
failedProvidersfield 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.allSettledcorrectly 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 updatedAffiliateRevenueResponseinterface.node/proxy/api/src/swagger.json (1)
114-125: LGTM!The
Serviceenum schema correctly reflects all defined services and aligns with the TypeScript type definition.
feat: add cacheing layer and make granularity daily
feat: finalise near intents and portals affiliate tracking
feat: add butterswap affiliate tracking
feat: add relay to affiliate revenue tracker
feat: finish implementation for near intents revenue tracking
There was a problem hiding this 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 betweenproviderNamesandPromise.allSettledis fragile.The
providerNamesarray must be kept in sync with the order of promises inPromise.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
FeesResponseinnode/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:
- Adding a request timeout
- 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
FeesResponsetype is identical to the one innode/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.tsornode/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_REFERRERis duplicated in multiple files (relay and bebop constants). Consider moving shared constants like this to the mainaffiliateRevenue/constants.tsfile to maintain a single source of truth.node/proxy/api/src/affiliateRevenue/mayachain/constants.ts (1)
1-4: Consider centralizing duplicate constants.
PRICE_API_URLandMILLISECONDS_PER_SECONDare duplicated in both mayachain and thorchain constants files with identical values. Consider moving these shared constants to the mainaffiliateRevenue/constants.tsfile to reduce duplication and ensure consistency.node/proxy/api/src/affiliateRevenue/bebop/constants.ts (1)
6-8: Consider centralizing duplicate constants.Both
SHAPESHIFT_REFERRERandFEE_BPS_DENOMINATORare duplicated across multiple modules:
SHAPESHIFT_REFERRERalso in relay/constants.tsFEE_BPS_DENOMINATORalso in nearIntents and portals constantsConsider moving these to the main
affiliateRevenue/constants.tsfile 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_KEYis 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 whengetFeesis 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
getFeesfunction:if (!NEAR_INTENTS_API_KEY) { throw new Error('NEAR_INTENTS_API_KEY env var not set') }
43-43: Hardcoded chain ID for monad.The
monadmapping uses a hardcoded value'eip155:143'while all other chains use imported constants. Consider adding aMONAD_CHAIN_IDconstant to../constantsfor 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 insidefetchFeesFromAPI, 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
getFeesand passing it tofetchFeesFromAPI.🔎 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 runePriceUsdnode/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 usingswapRequestNativeIdas the transaction identifier.The
txHashis set to an empty string, but the GraphQL response includesswapRequestNativeIdwhich 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
BigInttoNumberviaNumber(feesPerDay)can lose precision for values exceedingNumber.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_DENOMINATORis duplicated across multiple modules.This constant is defined in
bebop/constants.ts,nearIntents/constants.ts, and here. Consider extracting it to the shared../constants.tsto 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 theFeetype.The
Feetype is used inTradesResponsebut 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.getcall 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-levelpriceCacheobject can grow unbounded.Unlike
feeCache,tokenTransferCache, anddecimalsCachewhich useLRUCachewith size limits, thispriceCacheis a plain object that can grow indefinitely. Consider usingLRUCachefor 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 withtoISOString().split('T')[0].The return type annotation guarantees
string, butsplit('T')[0]could theoretically returnundefinedif 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 ingetDateEndTimestamp.
23:59:59is 1 second before midnight. This could cause edge cases where transactions at exactly23:59:59.5might be included or excluded unexpectedly. Consider usingT23:59:59.999Zor 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.
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (56)
.gitignorenode/proxy/api/package.jsonnode/proxy/api/src/affiliateRevenue/bebop.tsnode/proxy/api/src/affiliateRevenue/bebop/bebop.tsnode/proxy/api/src/affiliateRevenue/bebop/constants.tsnode/proxy/api/src/affiliateRevenue/bebop/index.tsnode/proxy/api/src/affiliateRevenue/bebop/types.tsnode/proxy/api/src/affiliateRevenue/butterswap/butterswap.tsnode/proxy/api/src/affiliateRevenue/butterswap/constants.tsnode/proxy/api/src/affiliateRevenue/butterswap/index.tsnode/proxy/api/src/affiliateRevenue/butterswap/types.tsnode/proxy/api/src/affiliateRevenue/butterswap/utils.tsnode/proxy/api/src/affiliateRevenue/cache.tsnode/proxy/api/src/affiliateRevenue/chainflip.tsnode/proxy/api/src/affiliateRevenue/chainflip/chainflip.tsnode/proxy/api/src/affiliateRevenue/chainflip/constants.tsnode/proxy/api/src/affiliateRevenue/chainflip/index.tsnode/proxy/api/src/affiliateRevenue/chainflip/types.tsnode/proxy/api/src/affiliateRevenue/constants.tsnode/proxy/api/src/affiliateRevenue/index.tsnode/proxy/api/src/affiliateRevenue/mayachain.tsnode/proxy/api/src/affiliateRevenue/mayachain/constants.tsnode/proxy/api/src/affiliateRevenue/mayachain/index.tsnode/proxy/api/src/affiliateRevenue/mayachain/mayachain.tsnode/proxy/api/src/affiliateRevenue/mayachain/types.tsnode/proxy/api/src/affiliateRevenue/nearIntents.tsnode/proxy/api/src/affiliateRevenue/nearIntents/constants.tsnode/proxy/api/src/affiliateRevenue/nearIntents/index.tsnode/proxy/api/src/affiliateRevenue/nearIntents/nearIntents.tsnode/proxy/api/src/affiliateRevenue/nearIntents/types.tsnode/proxy/api/src/affiliateRevenue/nearIntents/utils.tsnode/proxy/api/src/affiliateRevenue/portals.tsnode/proxy/api/src/affiliateRevenue/portals/constants.tsnode/proxy/api/src/affiliateRevenue/portals/index.tsnode/proxy/api/src/affiliateRevenue/portals/portals.tsnode/proxy/api/src/affiliateRevenue/portals/types.tsnode/proxy/api/src/affiliateRevenue/portals/utils.tsnode/proxy/api/src/affiliateRevenue/relay/constants.tsnode/proxy/api/src/affiliateRevenue/relay/index.tsnode/proxy/api/src/affiliateRevenue/relay/relay.tsnode/proxy/api/src/affiliateRevenue/relay/types.tsnode/proxy/api/src/affiliateRevenue/relay/utils.tsnode/proxy/api/src/affiliateRevenue/thorchain.tsnode/proxy/api/src/affiliateRevenue/thorchain/constants.tsnode/proxy/api/src/affiliateRevenue/thorchain/index.tsnode/proxy/api/src/affiliateRevenue/thorchain/thorchain.tsnode/proxy/api/src/affiliateRevenue/thorchain/types.tsnode/proxy/api/src/affiliateRevenue/zrx.tsnode/proxy/api/src/affiliateRevenue/zrx/constants.tsnode/proxy/api/src/affiliateRevenue/zrx/index.tsnode/proxy/api/src/affiliateRevenue/zrx/types.tsnode/proxy/api/src/affiliateRevenue/zrx/zrx.tsnode/proxy/api/src/controller.tsnode/proxy/api/src/models.tsnode/proxy/api/src/swagger.jsonnode/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
getFeesfunction 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
getFeesfunction 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
getFeesfunction 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
getFeesfunction 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
getFeesfunction from the butterswap module.node/proxy/api/src/affiliateRevenue/butterswap/types.ts (2)
8-15: LGTM!The
TokenListResponsetype 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
rpcCallfunction inutils.tscorrectly checks for the presence oferror(line 18) and throws before accessingresult(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
resultanderrorfields 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
Feesformat, 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 conston 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_URLandMILLISECONDS_PER_SECONDwith 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_KEYis 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
formatErrorhelper 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
byDateandfailedProvidersfields 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/prevPagefields.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:
servicesarray includes all providersDailyRevenueenables per-day breakdownsfailedProviders: Service[]allows the API to indicate which providers failed, enabling the UI to show appropriate indicatorsnode/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_BSCis 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.nearfor EVM tokensnep141:....nearfor native NEP-141 tokensnep245: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') ifresolveChainId('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
ExplorerTypeunion provides type safety for explorer selection.node/proxy/api/src/swagger.json (5)
77-80: LGTM - Service schema extended correctly.The addition of
butterswapandrelayservices toRecord_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.
DailyRevenueandRecord_string.DailyRevenue_schemas properly model the per-date revenue structure.
149-162: LGTM - Service enum provides type safety.The
Serviceenum covers all 9 affiliate revenue providers consistently.
163-190: Good addition offailedProvidersfor graceful degradation.The
failedProvidersarray aligns with the PR objective to indicate which swapper(s) failed, enabling UI indicators. The static analysis hint aboutmaxItemscan 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) tostartDate/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: trueis 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
splitDateRangefunction correctly separates cacheable historical dates from recent dates that shouldn't be cached, usingcacheableThresholdas 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
zidfields are intentional and part of the actual 0x API response structure. The top-levelzidserves as a request/correlation identifier, while the per-tradezidis a trade-specific identifier. The type definition accurately reflects this structure.
| "elliptic-sdk": "^0.7.2", | ||
| "lru-cache": "^10.2.0", | ||
| "viem": "^2.33.2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| 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) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| 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)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 | |
| } | |
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
| const cacaoPriceUsd = await getCacaoPriceUsd() | ||
|
|
||
| return data.fees.map(fee => transformFee(fee, cacaoPriceUsd)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 fRepository: shapeshift/unchained
Length of output: 122
🏁 Script executed:
cat -n node/proxy/api/src/affiliateRevenue/mayachain/mayachain.tsRepository: 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| 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 }, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Search for references to 'solana:soon' and related documentation
rg -n "solana:soon" -A 3 -B 3Repository: shapeshift/unchained
Length of output: 633
🏁 Script executed:
# Search for references to the ID 9286186
rg -n "9286186" -A 3 -B 3Repository: 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.tsRepository: 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/nullRepository: shapeshift/unchained
Length of output: 48
🏁 Script executed:
# Search for where NON_EVM_CHAINS is used
rg -n "NON_EVM_CHAINS" -A 2 -B 2Repository: 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 jsRepository: 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 -20Repository: shapeshift/unchained
Length of output: 232
🏁 Script executed:
# Find where getChainConfig is called
rg -n "getChainConfig" -A 2 -B 2Repository: 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 -30Repository: 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 -50Repository: 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ?? '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| export type RelayRequest = { | ||
| id: string | ||
| status: string | ||
| user: string | ||
| recipient: string | ||
| createdAt: string | ||
| updatedAt: string | ||
| data: RequestData | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -50Repository: 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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| export const ZRX_API_KEY = process.env.ZRX_API_KEY | ||
|
|
||
| if (!ZRX_API_KEY) throw new Error('ZRX_API_KEY env var not set') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| // 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') | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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:
If you want to properly test this locally:
Install dependencies (from repo root)
cd /home/sean/Repos/shapeshift-unchained
yarn install
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.)
Start reverse proxy (from repo root)
docker-compose up -d reverse-proxy
Start proxy API
cd node/proxy
docker-compose up
Test endpoint (in another terminal)
curl "http://api.proxy.localhost/api/v1/affiliate/revenue?startTimestamp=$(( $(date +%s) - 604800 ))&endTimestamp=$(date +%s)"Screenshots
Summary by CodeRabbit
New Features
Bug Fixes
API
✏️ Tip: You can customize this high-level summary in your review settings.