feat: referral code#555
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughCaptures referral codes from URLs, provides a UI to create/share referral links, verifies signed wallet requests, attributes referrals and records platform-fee events on confirmed transactions, and forwards validated writes to the internal data-api using server-only origin and admin key. ChangesReferral and Platform Fee Tracking
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as ReferralRewardsBlock
participant CodeAPI as /api/referrals/code
participant DataAPI as data-api /internal
User->>UI: Click "Create referral link"
UI->>CodeAPI: POST { address, signature, message }
CodeAPI->>DataAPI: POST /internal/referrals/code
DataAPI-->>CodeAPI: { code }
CodeAPI-->>UI: { code, referrerWallet }
UI->>User: Show/share referral URL
sequenceDiagram
participant TxHook as useTransactionWithToast
participant FeeHook as usePlatformFeeTracking
participant AttrHook as useReferralAttributionTracking
participant FeeAPI as /api/platform-fees
participant AttrAPI as /api/referrals/attribute
participant DataAPI as data-api /internal
TxHook->>TxHook: Transaction confirmed (hash + chainId)
TxHook->>AttrHook: trackReferralAttribution(chainId, txHash)
AttrHook->>AttrAPI: POST {referredWallet, referralCode, chainId, txHash}
AttrAPI->>DataAPI: POST /internal/referrals/attribute
par Fee submission
TxHook->>FeeHook: trackPlatformFeeEvents(chainId, txHash, events[])
FeeHook->>FeeAPI: POST each event
FeeAPI->>DataAPI: POST /internal/platform-fees
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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.
Code Review
This pull request introduces platform fee tracking and referral attribution features, including new API routes, a client-side referral tracking provider, and UI components for managing referral links. The feedback focuses on ensuring data consistency by normalizing wallet addresses, token addresses, and transaction hashes to lowercase, as well as using shared utilities for referral code validation. Additionally, the reviewer recommends addressing potential information disclosure vulnerabilities by logging internal server errors on the server rather than exposing them directly to the client.
| const userWallet = readString(body.userWallet); | ||
| const chainId = typeof body.chainId === 'number' ? body.chainId : Number.NaN; | ||
| const txHash = readString(body.txHash); | ||
| const source = readString(body.source); | ||
| const tokenAddress = readString(body.tokenAddress); | ||
| const amountRaw = readString(body.amountRaw); | ||
|
|
||
| if ( | ||
| !userWallet || | ||
| !isAddress(userWallet) || | ||
| !Number.isInteger(chainId) || | ||
| !txHash || | ||
| !TX_HASH_PATTERN.test(txHash) || | ||
| !source || | ||
| !tokenAddress || | ||
| !isAddress(tokenAddress) || | ||
| !amountRaw || | ||
| !/^[0-9]+$/.test(amountRaw) || | ||
| BigInt(amountRaw) <= 0n | ||
| ) { | ||
| return NextResponse.json({ error: 'Invalid platform fee request.' }, { status: 400 }); | ||
| } |
There was a problem hiding this comment.
Normalize the wallet, token address, and transaction hash to lowercase to prevent case-sensitivity mismatches in the database. Additionally, validate the length of the source parameter to prevent excessively long strings from being processed.
const userWallet = readString(body.userWallet)?.toLowerCase();
const chainId = typeof body.chainId === 'number' ? body.chainId : Number.NaN;
const txHash = readString(body.txHash)?.toLowerCase();
const source = readString(body.source);
const tokenAddress = readString(body.tokenAddress)?.toLowerCase();
const amountRaw = readString(body.amountRaw);
if (
!userWallet ||
!isAddress(userWallet) ||
!Number.isInteger(chainId) ||
!txHash ||
!TX_HASH_PATTERN.test(txHash) ||
!source ||
source.length > 64 ||
!tokenAddress ||
!isAddress(tokenAddress) ||
!amountRaw ||
!/^[0-9]+$/.test(amountRaw) ||
BigInt(amountRaw) <= 0n
) {
return NextResponse.json({ error: 'Invalid platform fee request.' }, { status: 400 });
}| } catch (error) { | ||
| return NextResponse.json({ error: error instanceof Error ? error.message : 'Failed to record platform fee.' }, { status: 500 }); | ||
| } |
There was a problem hiding this comment.
Avoid exposing internal server error messages directly to the client to prevent potential information disclosure. Log the error on the server and return a generic error message instead.
} catch (error) {
console.error('Error recording platform fee:', error);
return NextResponse.json({ error: 'Failed to record platform fee.' }, { status: 500 });
}| import { type NextRequest, NextResponse } from 'next/server'; | ||
| import { isAddress } from 'viem'; | ||
| import { callDataApiInternal } from '@/utils/dataApiInternal'; |
There was a problem hiding this comment.
Import normalizeReferralCode to validate and normalize the referral code consistently on both client and server.
| import { type NextRequest, NextResponse } from 'next/server'; | |
| import { isAddress } from 'viem'; | |
| import { callDataApiInternal } from '@/utils/dataApiInternal'; | |
| import { type NextRequest, NextResponse } from 'next/server'; | |
| import { isAddress } from 'viem'; | |
| import { callDataApiInternal } from '@/utils/dataApiInternal'; | |
| import { normalizeReferralCode } from '@/utils/referrals'; |
| const referredWallet = readString(body.referredWallet); | ||
| const referralCode = readString(body.referralCode); | ||
| const chainId = typeof body.chainId === 'number' ? body.chainId : Number.NaN; | ||
| const txHash = readString(body.txHash); | ||
|
|
||
| if ( | ||
| !referredWallet || | ||
| !isAddress(referredWallet) || | ||
| !referralCode || | ||
| !Number.isInteger(chainId) || | ||
| !txHash || | ||
| !TX_HASH_PATTERN.test(txHash) | ||
| ) { | ||
| return NextResponse.json({ error: 'Invalid referral attribution request.' }, { status: 400 }); | ||
| } |
There was a problem hiding this comment.
Normalize the referred wallet and transaction hash to lowercase. Use the shared normalizeReferralCode utility to validate and normalize the referral code format and length.
| const referredWallet = readString(body.referredWallet); | |
| const referralCode = readString(body.referralCode); | |
| const chainId = typeof body.chainId === 'number' ? body.chainId : Number.NaN; | |
| const txHash = readString(body.txHash); | |
| if ( | |
| !referredWallet || | |
| !isAddress(referredWallet) || | |
| !referralCode || | |
| !Number.isInteger(chainId) || | |
| !txHash || | |
| !TX_HASH_PATTERN.test(txHash) | |
| ) { | |
| return NextResponse.json({ error: 'Invalid referral attribution request.' }, { status: 400 }); | |
| } | |
| const referredWallet = readString(body.referredWallet)?.toLowerCase(); | |
| const referralCode = normalizeReferralCode(readString(body.referralCode)); | |
| const chainId = typeof body.chainId === 'number' ? body.chainId : Number.NaN; | |
| const txHash = readString(body.txHash)?.toLowerCase(); | |
| if ( | |
| !referredWallet || | |
| !isAddress(referredWallet) || | |
| !referralCode || | |
| !Number.isInteger(chainId) || | |
| !txHash || | |
| !TX_HASH_PATTERN.test(txHash) | |
| ) { | |
| return NextResponse.json({ error: 'Invalid referral attribution request.' }, { status: 400 }); | |
| } |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/api/referrals/code/route.ts`:
- Around line 30-35: The error branch uses response.status || 502 which can
return a 2xx when upstream responded OK but data.code is missing; change the
status computation before calling NextResponse.json so that if response.ok is
true you force a 502 (or other error status) otherwise use response.status (or
502 fallback). Update the block around NextResponse.json to compute status =
response.ok ? 502 : (response.status ?? 502) and pass that status to
NextResponse.json; refer to response, data.code and NextResponse.json in your
changes.
In `@src/utils/dataApiInternal.ts`:
- Around line 16-24: The fetch call that returns fetch(`${origin}${path}`, { ...
}) has no timeout and can hang; wrap it with an AbortController, pass
controller.signal into the fetch options, and start a timer (e.g., 5–30s) that
calls controller.abort() on timeout; ensure you clear the timer after fetch
resolves or rejects so you don't leak timers; keep the existing headers
(including INTERNAL_ADMIN_HEADER and adminKey), body (JSON.stringify(body)), and
cache option while adding the signal and timer cleanup.
In `@src/utils/referrals.ts`:
- Around line 17-24: storeReferralCodeOnce currently calls
window.localStorage.setItem which can throw (private mode/quota) and bubble into
the caller (ReferralTrackingProvider); wrap the write in a try/catch inside
storeReferralCodeOnce so failures don't throw: after the existing guards
(normalizeReferralCode and getStoredReferralCode) perform the setItem call
inside a try block, catch any error, optionally log it, and return false on
error; reference REFERRAL_CODE_STORAGE_KEY, normalizeReferralCode,
getStoredReferralCode, and storeReferralCodeOnce when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 67a4f661-d675-45fd-b949-702f8ecd6bfe
📒 Files selected for processing (17)
.env.local.exampleapp/api/platform-fees/route.tsapp/api/referrals/attribute/route.tsapp/api/referrals/code/route.tsapp/layout.tsxdocs/TECHNICAL_OVERVIEW.mdsrc/components/providers/ReferralTrackingProvider.tsxsrc/features/rewards/referral-rewards-section.tsxsrc/features/rewards/rewards-landing-view.tsxsrc/hooks/useLeverageTransaction.tssrc/hooks/usePlatformFeeTracking.tssrc/hooks/useRebalanceExecution.tssrc/hooks/useReferralAttributionTracking.tssrc/hooks/useSmartRebalance.tssrc/hooks/useTransactionWithToast.tsxsrc/utils/dataApiInternal.tssrc/utils/referrals.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/utils/dataApiInternal.ts (1)
16-24:⚠️ Potential issue | 🟠 Major | ⚡ Quick winNo timeout on the internal fetch.
If the data API hangs, this call blocks the route handler indefinitely. Add an abort timeout.
Proposed fix
return fetch(`${origin}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', [INTERNAL_ADMIN_HEADER]: adminKey, }, body: JSON.stringify(body), cache: 'no-store', + signal: AbortSignal.timeout(10_000), });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/dataApiInternal.ts` around lines 16 - 24, The internal POST fetch in dataApiInternal.ts has no timeout; wrap the request in an AbortController, pass controller.signal into fetch, and set a timer (e.g., configurable default like 5–10s) that calls controller.abort() on timeout; ensure you clear the timer after fetch completes and handle the abort error path so the route doesn't hang — update the function that constructs the fetch (the call that uses INTERNAL_ADMIN_HEADER and adminKey) to use the AbortController and timer and surface a clear timeout/abort error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/utils/dataApiInternal.ts`:
- Around line 16-24: The internal POST fetch in dataApiInternal.ts has no
timeout; wrap the request in an AbortController, pass controller.signal into
fetch, and set a timer (e.g., configurable default like 5–10s) that calls
controller.abort() on timeout; ensure you clear the timer after fetch completes
and handle the abort error path so the route doesn't hang — update the function
that constructs the fetch (the call that uses INTERNAL_ADMIN_HEADER and
adminKey) to use the AbortController and timer and surface a clear timeout/abort
error.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b5b72730-8372-45e8-a26c-3dd318ba4eaf
📒 Files selected for processing (4)
.env.local.exampledocs/TECHNICAL_OVERVIEW.mddocs/VALIDATIONS.mdsrc/utils/dataApiInternal.ts
✅ Files skipped from review due to trivial changes (3)
- docs/VALIDATIONS.md
- .env.local.example
- docs/TECHNICAL_OVERVIEW.md
3dde302 to
839f93f
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/utils/requestNonce.ts`:
- Around line 2-3: The fallback branch in src/utils/requestNonce.ts (the block
where typeof crypto === 'undefined') can produce nonces shorter than the
16-character minimum enforced in src/utils/signedWalletRequest.ts; modify that
branch so the generated string is guaranteed to be at least 16 characters (for
example, build the nonce from Date.now().toString(36) plus one or more
Math.random().toString(36).slice(2) segments and, if needed, loop/apply padding
until the combined length >= 16), so any nonce produced when crypto is
unavailable meets the server's minimum length requirement.
In `@src/utils/signedWalletRequest.ts`:
- Around line 102-104: The nonce is only regex-validated
(NONCE_PATTERN.test(parsedMessage.nonce)) but never recorded so the same signed
payload can be replayed; update signedWalletRequest.ts to atomically
check-and-consume parsedMessage.nonce before returning success (e.g., consult a
nonce store such as Redis or an in-memory cache with the same TTL as the
signature window), reject if the nonce already exists, and insert the nonce when
it is first seen so subsequent calls (including those that reach
createGatewayApiKey in app/api/api-keys/route.ts) are refused; ensure the
check+insert is done atomically or via a SETNX-style operation and uses the same
TTL as the signature expiry.
- Around line 89-96: The origin check using parsedMessage.origin vs
getApplicationOrigin(request) is insufficient because a third party can have the
user sign any origin; instead require a server-issued, single-use challenge
bound to Monarch and verify it was included in the signed payload. Update the
signer flow and the verification in signedWalletRequest.ts so that parsedMessage
must include a server nonce/challenge (e.g., serverChallenge) and you confirm
that serverChallenge matches a stored/issued value tied to the current session
or request context before accepting the signature; keep the origin comparison
but make it secondary to validating the server-issued nonce. Ensure you
reference parsedMessage.origin and getApplicationOrigin(request) in the new
logic and reject if no matching serverChallenge or session is found.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 7577723b-b88d-4010-9903-ba6c7a13de8d
📒 Files selected for processing (9)
app/api/api-keys/route.tsapp/api/referrals/code/route.tsdocs/TECHNICAL_OVERVIEW.mddocs/VALIDATIONS.mdsrc/features/api-keys/api-key-console-view.tsxsrc/features/rewards/referral-rewards-block.tsxsrc/utils/referralRequest.tssrc/utils/requestNonce.tssrc/utils/signedWalletRequest.ts
✅ Files skipped from review due to trivial changes (1)
- docs/VALIDATIONS.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/TECHNICAL_OVERVIEW.md
Summary by CodeRabbit
New Features
Documentation
Chore