Skip to content

feat: add affiliate & auth routes to public-api, refactor affiliate-dashboard for prod#12200

Merged
kaladinlight merged 9 commits intodevelopfrom
feat/affiliate-public-api-routes
Mar 26, 2026
Merged

feat: add affiliate & auth routes to public-api, refactor affiliate-dashboard for prod#12200
kaladinlight merged 9 commits intodevelopfrom
feat/affiliate-public-api-routes

Conversation

@kaladinlight
Copy link
Copy Markdown
Contributor

@kaladinlight kaladinlight commented Mar 23, 2026

Summary

public-api

  • Add missing affiliate proxy routes: GET /affiliate/:address, POST /affiliate, PATCH /affiliate/:address, POST /affiliate/claim-code, GET /affiliate/swaps
  • Add SIWE auth proxy routes: POST /auth/siwe/nonce, POST /auth/siwe/verify
  • Extract fetchSwapService helper to deduplicate AbortController + 503/504 error handling across all upstream fetch calls
  • Add affiliateMutationLimiter (20 req/min) applied to all mutation and auth routes
  • Add chainId filtering to GET /assets/count
  • Reorganize affiliate types.ts by feature group with consistent *RequestSchema/*ResponseSchema naming
  • Complete OpenAPI response codes (400/404/429/500/503/504) across all route handlers

affiliate-dashboard

  • Replace Vite dev proxy with explicit VITE_API_URL env var for production compatibility
  • Switch from Dockerfile to Railpack for Railway deployment; remove over-engineered proxy config
  • Remove tsconfig.node.json reference (folded into main tsconfig)
  • Add .env.example with required env vars
  • Add early throw in main.tsx if VITE_API_URL is not set in production

Test plan

  • GET /v1/affiliate/:address returns affiliate config from swap-service
  • POST /v1/affiliate creates affiliate (requires Authorization header)
  • PATCH /v1/affiliate/:address updates affiliate (requires Authorization header)
  • POST /v1/affiliate/claim-code claims partner code (requires Authorization header)
  • GET /v1/affiliate/swaps returns paginated swap history
  • POST /v1/auth/siwe/nonce returns a nonce
  • POST /v1/auth/siwe/verify returns JWT on valid signature
  • GET /v1/assets/count?chainId=eip155:1 returns filtered count
  • Rate limiting returns 429 with correct error shape on all routes
  • Affiliate dashboard builds and runs pointing at VITE_API_URL

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • SIWE wallet sign-in endpoints added
    • Affiliate API: create/update accounts, claim partner codes, list swaps, and view stats
    • Dashboard now reads API base URL and WalletConnect project ID from environment and validates them at startup
  • Chores

    • Added example environment file with required API settings
    • Removed Docker-based deployment; switched to Railway RAILPACK builder
    • Added a package cleanup script (removes build output and node modules)

…ashboard for prod

public-api:
- Add missing affiliate routes: GET /affiliate/:address, POST /affiliate,
  PATCH /affiliate/:address, POST /affiliate/claim-code, GET /affiliate/swaps
- Add SIWE auth routes: POST /auth/siwe/nonce, POST /auth/siwe/verify
- Extract shared fetchSwapService helper (AbortController + 503/504 handling)
- Add affiliateMutationLimiter (20 req/min) on mutation and auth routes
- Add chainId filtering to GET /assets/count
- Reorganize affiliate types by feature group with consistent naming (*RequestSchema/*ResponseSchema)
- Complete OpenAPI response codes (400/404/429/500/503/504) across all route handlers

affiliate-dashboard:
- Replace Vite proxy with explicit VITE_API_URL for production compatibility
- Switch from Dockerfile to Railpack for Railway deployment
- Remove tsconfig.node.json (folded into main tsconfig)
- Add .env.example with required env vars
- Add early throw in main.tsx if VITE_API_URL not set in prod

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kaladinlight kaladinlight requested a review from a team as a code owner March 23, 2026 22:47
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Externalized affiliate-dashboard runtime config (VITE_API_URL, WalletConnect), removed the package Dockerfile and Vite proxy, updated TypeScript/Vite configs, added affiliate and SIWE endpoints and schemas in the public API, and introduced fetchSwapService to proxy upstream swap-service calls with timeout/error handling.

Changes

Cohort / File(s) Summary
Affiliate dashboard env & TS
packages/affiliate-dashboard/.env.example, packages/affiliate-dashboard/tsconfig.json, packages/affiliate-dashboard/tsconfig.node.json
Added .env.example with VITE_* placeholders; tsconfig now includes vite/client and vite.config.ts; removed tsconfig.node.json.
Affiliate dashboard build/runtime
packages/affiliate-dashboard/Dockerfile, packages/affiliate-dashboard/railway.json, packages/affiliate-dashboard/vite.config.ts, packages/affiliate-dashboard/package.json
Removed multi-stage Dockerfile; Railway build switched to RAILPACK; Vite dev proxy for /v1 removed and dev port uses process.env.PORT fallback; added clean npm script.
Affiliate dashboard runtime code & hooks
packages/affiliate-dashboard/src/main.tsx, packages/affiliate-dashboard/src/App.tsx, packages/affiliate-dashboard/src/config/wagmi.ts, packages/affiliate-dashboard/src/hooks/...
Replaced hardcoded relative API paths with import.meta.env.VITE_API_URL across app and hooks; added startup guards for required envs (API_URL and WalletConnect projectId); adjusted error parsing and one UI style rule.
Public API core helper & wiring
packages/public-api/src/index.ts, packages/public-api/src/lib/fetchSwapService.ts
Wired affiliate and SIWE routes into /v1; added fetchSwapService to perform upstream fetches with AbortController timeout and standardized 503/504 handling.
Affiliate routes & types
packages/public-api/src/routes/affiliate/*, packages/public-api/src/routes/affiliate/types.ts, packages/public-api/src/types.ts
Added handlers: createAffiliate, claimPartnerCode, getAffiliate, getAffiliateSwaps, updateAffiliate; introduced EVM_ADDRESS schema, comprehensive Zod request/response schemas and inferred types; updated getAffiliateStats to use fetchSwapService.
SIWE auth routes & types
packages/public-api/src/routes/auth/*, packages/public-api/src/routes/auth/types.ts
Added SIWE endpoints: siweNonce, siweVerify, their Zod schemas and OpenAPI registration; re-exported via auth index.
Status, quote, rates, chains, assets — docs & typing
packages/public-api/src/routes/status/*, packages/public-api/src/routes/quote/*, packages/public-api/src/routes/rates/*, packages/public-api/src/routes/chains/*, packages/public-api/src/routes/assets/*
Expanded OpenAPI response metadata (400/500/other), switched several handlers to inline satisfies typing, renamed local parse vars, made AssetSchema.relatedAssetKey nullable, and introduced Zod-backed SwapServiceStatus schema.

Sequence Diagram

sequenceDiagram
    participant Client as Affiliate Dashboard
    participant PublicAPI as Public API
    participant SwapService as Swap Service

    rect rgba(200,150,255,0.5)
    Note over Client: Startup validation
    Client->>Client: Read VITE_API_URL & VITE_WALLETCONNECT_PROJECT_ID
    Client->>Client: Throw if missing
    end

    rect rgba(100,200,255,0.5)
    Note over Client,PublicAPI: Affiliate create/claim/update
    Client->>PublicAPI: POST/PATCH /v1/affiliate... (Authorization)
    PublicAPI->>PublicAPI: Zod validate request
    PublicAPI->>SwapService: Forward via fetchSwapService (timeout)
    SwapService-->>PublicAPI: 2xx or error JSON/status
    PublicAPI-->>Client: Forward status/body or mapped error
    end

    rect rgba(100,255,200,0.5)
    Note over Client,PublicAPI: SIWE auth flow
    Client->>PublicAPI: POST /v1/auth/siwe/nonce
    PublicAPI->>SwapService: POST /v1/auth/siwe/nonce
    SwapService-->>PublicAPI: { nonce }
    PublicAPI-->>Client: { nonce }
    Client->>PublicAPI: POST /v1/auth/siwe/verify {message,signature}
    PublicAPI->>SwapService: POST /v1/auth/siwe/verify
    SwapService-->>PublicAPI: { token,address } or error
    PublicAPI-->>Client: Forward response
    end

    rect rgba(255,200,100,0.5)
    Note over PublicAPI,SwapService: Error handling
    SwapService-->>PublicAPI: Timeout/unavailable
    PublicAPI->>PublicAPI: fetchSwapService returns null → respond 503/504
    PublicAPI-->>Client: Error response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through envs and found my trail,

VITE_API_URL now guides each tale.
Docker waved goodbye, configs rearranged,
SIWE and affiliates neatly exchanged.
I watch timeouts closely — tidy and hale. 🌿

🚥 Pre-merge checks | ✅ 3
✅ 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 'feat: add affiliate & auth routes to public-api, refactor affiliate-dashboard for prod' accurately captures the main changes: adding affiliate and auth routes to public-api and refactoring the affiliate-dashboard for production deployment.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

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

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
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/affiliate-dashboard/src/App.tsx (1)

227-237: ⚠️ Potential issue | 🟡 Minor

Read error from the new public-api failure payloads.

These calls now hit packages/public-api, and the handlers added in this PR serialize local failures as { error, code }. Looking only at body.message collapses useful 400/401/409 responses into a generic Failed (status).

💡 Minimal fix
-        const body = (await res.json()) as { message?: string }
-        throw new Error(body.message ?? `Failed (${String(res.status)})`)
+        const body = (await res.json()) as { error?: string; message?: string }
+        throw new Error(body.error ?? body.message ?? `Failed (${String(res.status)})`)

Also applies to: 256-264, 285-293, 316-324

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

In `@packages/affiliate-dashboard/src/App.tsx` around lines 227 - 237, Update the
fetch error handling in App.tsx to read the new failure payload shape used by
public-api: when a response is not ok, parse the JSON and prefer body.error
(falling back to body.message or a status-based message) when constructing the
thrown Error; apply the same change for the other similar blocks where fetch
responses are checked (the blocks around the other fetch calls shown in the
diff, e.g., the sections currently using body.message at lines corresponding to
the other POST/PUT handlers) so errors surface the serialized { error, code }
message from the API.
🧹 Nitpick comments (4)
packages/public-api/src/routes/auth/types.ts (1)

17-23: Response schemas are defined but not validated at runtime.

The SiweVerifyResponseSchema and SiweNonceResponseSchema are registered for OpenAPI documentation, but per siweVerify.ts (line 63) and siweNonce.ts (line 43), the upstream responses are passed through directly without validation. If the upstream service returns a different shape (e.g., jwt instead of token), the API contract is violated silently.

Consider validating upstream responses before returning them:

const data = SiweVerifyResponseSchema.safeParse(await response.json())
if (!data.success) {
  // handle schema mismatch
}
res.status(200).json(data.data)

This ensures the public API contract matches what clients actually receive.

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

In `@packages/public-api/src/routes/auth/types.ts` around lines 17 - 23, The
registered response schemas SiweVerifyResponseSchema and SiweNonceResponseSchema
are only for docs and not used at runtime; update the handlers in siweVerify.ts
(handler around line 63) and siweNonce.ts (handler around line 43) to validate
upstream responses using the corresponding schema.safeParse(await
response.json()), and if validation fails, log the error and return an
appropriate error response (e.g., 502 Bad Gateway or 500) rather than passing
through the raw upstream body; on success return the parsed .data to guarantee
the public API matches the documented shape.
packages/public-api/src/routes/affiliate/updateAffiliate.ts (1)

71-74: Authorization header type may need handling for array case.

req.headers.authorization can be string | string[] | undefined in Express. The spread { Authorization: req.headers.authorization } works when it's a string, but if it's an array (unusual for Authorization but possible), it would be passed as-is to fetch headers.

🛡️ Optional fix for type safety
         headers: {
           'Content-Type': 'application/json',
-          ...(req.headers.authorization ? { Authorization: req.headers.authorization } : {}),
+          ...(req.headers.authorization && typeof req.headers.authorization === 'string'
+            ? { Authorization: req.headers.authorization }
+            : {}),
         },

This is a minor edge case since Authorization headers are virtually never arrays in practice.

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

In `@packages/public-api/src/routes/affiliate/updateAffiliate.ts` around lines 71
- 74, Normalize the Authorization header before spreading into the fetch
headers: read req.headers.authorization in updateAffiliate handler, check
Array.isArray(req.headers.authorization) and if so use the first element (or
join with ',') to produce a string authHeader, then include Authorization:
authHeader only when authHeader is truthy; update the headers construction that
currently spreads { Authorization: req.headers.authorization } to use this
normalized authHeader variable to avoid passing string[] to fetch.
packages/public-api/src/routes/affiliate/types.ts (1)

56-57: Consider defining explicit schemas for sellAsset and buyAsset.

Using z.unknown() bypasses validation entirely for these fields. If the asset structure is known and consistent, defining an explicit schema would:

  • Catch upstream changes that break the expected contract
  • Provide better TypeScript types for consumers
  • Improve OpenAPI documentation

If the structure varies significantly, this is acceptable as-is.

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

In `@packages/public-api/src/routes/affiliate/types.ts` around lines 56 - 57, The
current affiliate schema uses z.unknown() for sellAsset and buyAsset which skips
validation; replace these with explicit Zod schemas (for example create and
reference SellAssetSchema and BuyAssetSchema or a shared AssetSchema) and use
them where sellAsset and buyAsset are defined so types and OpenAPI output are
accurate; if multiple asset shapes exist, use z.union([...]) or
z.discriminatedUnion(...) for the variants and update any exported TypeScript
types derived from the schema (e.g., via z.infer) so consumers and docs reflect
the real structure.
packages/public-api/src/routes/affiliate/claimPartnerCode.ts (1)

51-59: Reject missing Authorization locally.

This route is documented as authenticated, but a missing header still burns an upstream request. A local 401 keeps the contract in public-api and avoids avoidable load on swap-service.

💡 Minimal fix
     if (!parseResult.success) {
       res.status(400).json({
         error: 'Invalid request body',
         code: 'INVALID_REQUEST',
         details: parseResult.error.errors,
       } as ErrorResponse)
       return
     }
+
+    if (!req.headers.authorization) {
+      res.status(401).json({
+        error: 'Missing Authorization header',
+        code: 'UNAUTHORIZED',
+      } as ErrorResponse)
+      return
+    }
 
     const response = await fetchSwapService(
       res,
       `${SWAP_SERVICE_BASE_URL}/v1/affiliate/claim-code`,
       {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
-          ...(req.headers.authorization ? { Authorization: req.headers.authorization } : {}),
+          Authorization: req.headers.authorization,
         },
         body: JSON.stringify(parseResult.data),
       },
     )
As per coding guidelines, "ALWAYS validate inputs before processing with clear validation error messages and use early returns for validation failures".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/public-api/src/routes/affiliate/claimPartnerCode.ts` around lines 51
- 59, The route currently forwards requests to fetchSwapService even when
Authorization is missing; update the handler in claimPartnerCode (in
packages/public-api/src/routes/affiliate/claimPartnerCode.ts) to validate
req.headers.authorization early and return a local 401 response with a clear
validation message (e.g., "Authorization header required") before calling
fetchSwapService or constructing the request to
`${SWAP_SERVICE_BASE_URL}/v1/affiliate/claim-code`; keep the existing logic to
forward the Authorization header when present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/affiliate-dashboard/src/App.tsx`:
- Line 136: The AFFILIATE_URL construction uses import.meta.env.VITE_API_URL
directly and can produce malformed URLs when VITE_API_URL is missing or has a
trailing slash; update the code that defines AFFILIATE_URL to first assert that
import.meta.env.VITE_API_URL is present (throw or fail-fast), normalize it by
removing any trailing slash (or use the URL constructor with new
URL('/v1/affiliate', base) to join paths safely), and then compose AFFILIATE_URL
from the normalized base so that AFFILIATE_URL is always a valid absolute URL;
reference the AFFILIATE_URL constant and import.meta.env.VITE_API_URL when
making the change.

---

Outside diff comments:
In `@packages/affiliate-dashboard/src/App.tsx`:
- Around line 227-237: Update the fetch error handling in App.tsx to read the
new failure payload shape used by public-api: when a response is not ok, parse
the JSON and prefer body.error (falling back to body.message or a status-based
message) when constructing the thrown Error; apply the same change for the other
similar blocks where fetch responses are checked (the blocks around the other
fetch calls shown in the diff, e.g., the sections currently using body.message
at lines corresponding to the other POST/PUT handlers) so errors surface the
serialized { error, code } message from the API.

---

Nitpick comments:
In `@packages/public-api/src/routes/affiliate/claimPartnerCode.ts`:
- Around line 51-59: The route currently forwards requests to fetchSwapService
even when Authorization is missing; update the handler in claimPartnerCode (in
packages/public-api/src/routes/affiliate/claimPartnerCode.ts) to validate
req.headers.authorization early and return a local 401 response with a clear
validation message (e.g., "Authorization header required") before calling
fetchSwapService or constructing the request to
`${SWAP_SERVICE_BASE_URL}/v1/affiliate/claim-code`; keep the existing logic to
forward the Authorization header when present.

In `@packages/public-api/src/routes/affiliate/types.ts`:
- Around line 56-57: The current affiliate schema uses z.unknown() for sellAsset
and buyAsset which skips validation; replace these with explicit Zod schemas
(for example create and reference SellAssetSchema and BuyAssetSchema or a shared
AssetSchema) and use them where sellAsset and buyAsset are defined so types and
OpenAPI output are accurate; if multiple asset shapes exist, use z.union([...])
or z.discriminatedUnion(...) for the variants and update any exported TypeScript
types derived from the schema (e.g., via z.infer) so consumers and docs reflect
the real structure.

In `@packages/public-api/src/routes/affiliate/updateAffiliate.ts`:
- Around line 71-74: Normalize the Authorization header before spreading into
the fetch headers: read req.headers.authorization in updateAffiliate handler,
check Array.isArray(req.headers.authorization) and if so use the first element
(or join with ',') to produce a string authHeader, then include Authorization:
authHeader only when authHeader is truthy; update the headers construction that
currently spreads { Authorization: req.headers.authorization } to use this
normalized authHeader variable to avoid passing string[] to fetch.

In `@packages/public-api/src/routes/auth/types.ts`:
- Around line 17-23: The registered response schemas SiweVerifyResponseSchema
and SiweNonceResponseSchema are only for docs and not used at runtime; update
the handlers in siweVerify.ts (handler around line 63) and siweNonce.ts (handler
around line 43) to validate upstream responses using the corresponding
schema.safeParse(await response.json()), and if validation fails, log the error
and return an appropriate error response (e.g., 502 Bad Gateway or 500) rather
than passing through the raw upstream body; on success return the parsed .data
to guarantee the public API matches the documented shape.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a57c1682-6d1d-4507-869e-793bc92867bd

📥 Commits

Reviewing files that changed from the base of the PR and between 6606e9e and b40569c.

📒 Files selected for processing (36)
  • packages/affiliate-dashboard/.env.example
  • packages/affiliate-dashboard/Dockerfile
  • packages/affiliate-dashboard/package.json
  • packages/affiliate-dashboard/railway.json
  • packages/affiliate-dashboard/src/App.tsx
  • packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts
  • packages/affiliate-dashboard/src/hooks/useAffiliateStats.ts
  • packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts
  • packages/affiliate-dashboard/src/hooks/useSiweAuth.ts
  • packages/affiliate-dashboard/src/main.tsx
  • packages/affiliate-dashboard/tsconfig.json
  • packages/affiliate-dashboard/tsconfig.node.json
  • packages/affiliate-dashboard/vite.config.ts
  • packages/public-api/src/index.ts
  • packages/public-api/src/lib/fetchSwapService.ts
  • packages/public-api/src/middleware/rateLimit.ts
  • packages/public-api/src/routes/affiliate/claimPartnerCode.ts
  • packages/public-api/src/routes/affiliate/createAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliateStats.ts
  • packages/public-api/src/routes/affiliate/getAffiliateSwaps.ts
  • packages/public-api/src/routes/affiliate/index.ts
  • packages/public-api/src/routes/affiliate/types.ts
  • packages/public-api/src/routes/affiliate/updateAffiliate.ts
  • packages/public-api/src/routes/assets/getAssetById.ts
  • packages/public-api/src/routes/assets/getAssetCount.ts
  • packages/public-api/src/routes/assets/getAssets.ts
  • packages/public-api/src/routes/auth/index.ts
  • packages/public-api/src/routes/auth/siweNonce.ts
  • packages/public-api/src/routes/auth/siweVerify.ts
  • packages/public-api/src/routes/auth/types.ts
  • packages/public-api/src/routes/chains/getChainCount.ts
  • packages/public-api/src/routes/chains/getChains.ts
  • packages/public-api/src/routes/quote/getQuote.ts
  • packages/public-api/src/routes/rates/getRates.ts
  • packages/public-api/src/routes/status/getSwapStatus.ts
💤 Files with no reviewable changes (2)
  • packages/affiliate-dashboard/tsconfig.node.json
  • packages/affiliate-dashboard/Dockerfile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/public-api/src/routes/affiliate/getAffiliateStats.ts`:
- Line 36: The OpenAPI 400 response description in getAffiliateStats.ts is too
specific to address format while the handler uses safeParse(req.query) which can
fail for date filters and other params; update the 400 response description (the
entry keyed by 400 in the route definition) to a generic message such as
"Invalid request parameters" so it matches the runtime validation behavior
performed by safeParse(req.query).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 28a6c306-c923-4b46-9d7b-fd95e446322f

📥 Commits

Reviewing files that changed from the base of the PR and between b40569c and b613597.

📒 Files selected for processing (3)
  • packages/public-api/src/index.ts
  • packages/public-api/src/routes/affiliate/getAffiliateStats.ts
  • packages/public-api/src/routes/status/getSwapStatus.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/public-api/src/routes/status/getSwapStatus.ts

kaladinlight and others added 2 commits March 24, 2026 14:36
- Validate upstream responses with Zod safeParse before returning to client
- Replace `as Type` response casts with `satisfies` for compile-time enforcement
- Reject missing Authorization header locally on authenticated routes
- Validate env vars strictly: enum for feature flags, min(1) for rate limits,
  regex for PORT and DEFAULT_AFFILIATE_BPS
- Use env directly instead of SWAP_SERVICE_BASE_URL re-export from config
- Fix new URL() leading-slash bug in getAffiliateSwaps
- Use AssetSchema from assets/types for affiliate swap item validation
- Move AffiliateFeeResponseSchema to types.ts for consistency
- Remove || 'demo' fallback for WalletConnect project ID

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…shboard

public-api returns { error, code } not { message }, so body.message was
always undefined and errors fell back to the generic "Failed (status)" string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/public-api/src/routes/affiliate/getAffiliateStats.ts (1)

80-83: Add structured context to these error logs.

Both branches log free-form strings, so production triage loses the affiliate address, upstream status, and route context. Please switch these to the service logger with structured fields.

As per coding guidelines, ALWAYS log errors for debugging using structured logging with relevant context and error metadata.

Also applies to: 99-103

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

In `@packages/public-api/src/routes/affiliate/getAffiliateStats.ts` around lines
80 - 83, Replace the free-form console.error calls that log
responseResult.error.errors with structured service logging: in the
getAffiliateStats handler, call the service logger (e.g., serviceLogger.error)
and pass a structured payload including affiliateAddress (the affiliateAddress
variable), route or handler identifier (e.g., "getAffiliateStats"),
upstreamStatus (from responseResult.status or response.status), and the full
response/result object (responseResult) plus the error details, and use a short
message like "Unexpected response shape from swap-service
/swaps/affiliate-fees"; apply the same structured-logging change to the other
branch that currently logs at lines 99-103 so both places use
serviceLogger.error with the same fields for easier triage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/affiliate-dashboard/src/config/wagmi.ts`:
- Line 5: The code reads VITE_WALLETCONNECT_PROJECT_ID into the projectId
constant without validation; add a fail-fast check after the declaration of
projectId that throws a clear Error (or throws new Error("Missing
VITE_WALLETCONNECT_PROJECT_ID: please set env var
VITE_WALLETCONNECT_PROJECT_ID")) when projectId is falsy so downstream uses of
projectId (used to initialize WalletConnect in this module) never run with an
undefined value; reference the projectId constant to locate where to insert this
guard.

In `@packages/public-api/src/routes/affiliate/getAffiliateStats.ts`:
- Around line 78-88: The code calls await response.json() before running
AffiliateFeeResponseSchema.safeParse, so if the swap-service returns non-JSON
this throws out to the outer catch and becomes a 500; wrap the JSON parsing in a
small try/catch and treat JSON parse errors the same as schema failures: log the
parse error and respond with res.status(503).json({ error: 'Invalid response
from swap service', code: 'INVALID_RESPONSE' }) (the same flow used when
AffiliateFeeResponseSchema.safeParse fails). Apply the same change to the
duplicate parsing block around the code handling the other response (the block
referenced at lines ~98-103) so all malformed upstream bodies produce
503/INVALID_RESPONSE instead of 500.

In `@packages/public-api/src/routes/affiliate/types.ts`:
- Around line 100-106: The affiliate address fields are using plain strings
which weakens validation; update AffiliateFeeResponseSchema.affiliateAddress and
AffiliateStatsResponseSchema.address (and any other stat/address fields in these
schemas) to use the shared EVM_ADDRESS schema instead of z.string(); locate the
EVM_ADDRESS export and replace the z.string() usages in the
AffiliateFeeResponseSchema and AffiliateStatsResponseSchema definitions so the
fields validate as viem EVM addresses.
- Around line 33-38: The partnerCode field in CreateAffiliateRequestSchema
accepts empty or whitespace-only strings; update partnerCode to reject blank
values by applying trimming and a minimum-length constraint (e.g., use
z.string().trim().min(1).optional()) so whitespace/empty inputs fail validation
and return 400; apply the same change to the other affiliate-related request
schema in this file that also declares partnerCode so both schemas consistently
reject blank partner codes.

In `@packages/public-api/src/routes/status/getSwapStatus.ts`:
- Around line 30-35: The handler getSwapStatus currently swallows upstream
swap-service failures in its catch block (around the code handling lines 97-116)
and proceeds to return a 200 with stale local status; change that behavior so
when the upstream call to swap-service fails or times out the handler returns an
appropriate 5xx (use 504 for timeouts and 503 for other upstream errors) with a
clear error body, log the error details, and short-circuit the request instead
of returning the stale status; also update the OpenAPI responses object (the
response map near the top where 400/404/409/429/500 are defined) to include 503
and 504 descriptions so clients can see these new responses.

---

Nitpick comments:
In `@packages/public-api/src/routes/affiliate/getAffiliateStats.ts`:
- Around line 80-83: Replace the free-form console.error calls that log
responseResult.error.errors with structured service logging: in the
getAffiliateStats handler, call the service logger (e.g., serviceLogger.error)
and pass a structured payload including affiliateAddress (the affiliateAddress
variable), route or handler identifier (e.g., "getAffiliateStats"),
upstreamStatus (from responseResult.status or response.status), and the full
response/result object (responseResult) plus the error details, and use a short
message like "Unexpected response shape from swap-service
/swaps/affiliate-fees"; apply the same structured-logging change to the other
branch that currently logs at lines 99-103 so both places use
serviceLogger.error with the same fields for easier triage.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21a8a324-99a2-4b62-95d2-370a96897278

📥 Commits

Reviewing files that changed from the base of the PR and between b613597 and f965ba9.

📒 Files selected for processing (21)
  • packages/affiliate-dashboard/src/App.tsx
  • packages/affiliate-dashboard/src/config/wagmi.ts
  • packages/public-api/src/lib/fetchSwapService.ts
  • packages/public-api/src/routes/affiliate/claimPartnerCode.ts
  • packages/public-api/src/routes/affiliate/createAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliateStats.ts
  • packages/public-api/src/routes/affiliate/getAffiliateSwaps.ts
  • packages/public-api/src/routes/affiliate/types.ts
  • packages/public-api/src/routes/affiliate/updateAffiliate.ts
  • packages/public-api/src/routes/assets/getAssetById.ts
  • packages/public-api/src/routes/assets/getAssetCount.ts
  • packages/public-api/src/routes/assets/getAssets.ts
  • packages/public-api/src/routes/assets/types.ts
  • packages/public-api/src/routes/auth/siweNonce.ts
  • packages/public-api/src/routes/auth/siweVerify.ts
  • packages/public-api/src/routes/chains/getChainCount.ts
  • packages/public-api/src/routes/chains/getChains.ts
  • packages/public-api/src/routes/quote/getQuote.ts
  • packages/public-api/src/routes/rates/getRates.ts
  • packages/public-api/src/routes/status/getSwapStatus.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/public-api/src/routes/chains/getChainCount.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • packages/public-api/src/routes/chains/getChains.ts
  • packages/public-api/src/routes/rates/getRates.ts
  • packages/public-api/src/routes/assets/getAssetById.ts
  • packages/affiliate-dashboard/src/App.tsx
  • packages/public-api/src/routes/quote/getQuote.ts
  • packages/public-api/src/routes/affiliate/getAffiliate.ts
  • packages/public-api/src/lib/fetchSwapService.ts
  • packages/public-api/src/routes/affiliate/claimPartnerCode.ts
  • packages/public-api/src/routes/assets/getAssetCount.ts
  • packages/public-api/src/routes/assets/getAssets.ts
  • packages/public-api/src/routes/affiliate/createAffiliate.ts
  • packages/public-api/src/routes/auth/siweVerify.ts
  • packages/public-api/src/routes/affiliate/getAffiliateSwaps.ts
  • packages/public-api/src/routes/affiliate/updateAffiliate.ts

…ashboard

- Add .catch(() => null) to all upstream safeParse calls for non-JSON safety
- Convert SwapServiceStatus type to SwapServiceStatusSchema for runtime validation
- Refactor getSwapStatus: immutable store updates, mutable response object,
  fetchSwapService helper, 503/504 on upstream failures, early return after
  first registration, remove dead concurrent guard, require txHash to begin tracking
- Use EVM_ADDRESS for AffiliateFeeResponseSchema.affiliateAddress and
  AffiliateStatsResponseSchema.address
- Add .trim().min(1) to partnerCode fields in CreateAffiliate and ClaimPartnerCode
- Add fail-fast guard for VITE_WALLETCONNECT_PROJECT_ID in wagmi.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/public-api/src/routes/status/types.ts (1)

15-15: Use the shared EVM address validator instead of a plain string.

Line 15 accepts any string for affiliateAddress. Reusing the existing EVM address schema here would keep validation consistent and prevent malformed addresses from propagating.

♻️ Proposed refactor
 import { z } from 'zod'
 
 import { registry } from '../../registry'
+import { EVM_ADDRESS } from '../affiliate/types'
@@
       hasAffiliate: z.boolean(),
       affiliateBps: z.number().optional(),
-      affiliateAddress: z.string().optional(),
+      affiliateAddress: EVM_ADDRESS.optional(),

As per coding guidelines: "ALWAYS validate inputs before processing with clear validation error messages and use early returns for validation failures."

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

In `@packages/public-api/src/routes/status/types.ts` at line 15, Replace the loose
affiliateAddress: z.string().optional() schema with the shared EVM address
validator: import the existing evm address schema (e.g., evmAddressSchema or the
shared validator exported by your common schemas) and use
evmAddressSchema.optional() for the affiliateAddress field in the status types
definition so addresses are validated consistently; update the import at the top
of the file to pull the shared validator and remove the plain z.string() usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/public-api/src/routes/status/types.ts`:
- Line 15: Replace the loose affiliateAddress: z.string().optional() schema with
the shared EVM address validator: import the existing evm address schema (e.g.,
evmAddressSchema or the shared validator exported by your common schemas) and
use evmAddressSchema.optional() for the affiliateAddress field in the status
types definition so addresses are validated consistently; update the import at
the top of the file to pull the shared validator and remove the plain z.string()
usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb46c212-0788-41bc-b52b-68c12f05842d

📥 Commits

Reviewing files that changed from the base of the PR and between f965ba9 and 021366c.

📒 Files selected for processing (12)
  • packages/affiliate-dashboard/src/config/wagmi.ts
  • packages/public-api/src/routes/affiliate/claimPartnerCode.ts
  • packages/public-api/src/routes/affiliate/createAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliateStats.ts
  • packages/public-api/src/routes/affiliate/getAffiliateSwaps.ts
  • packages/public-api/src/routes/affiliate/types.ts
  • packages/public-api/src/routes/affiliate/updateAffiliate.ts
  • packages/public-api/src/routes/auth/siweNonce.ts
  • packages/public-api/src/routes/auth/siweVerify.ts
  • packages/public-api/src/routes/status/getSwapStatus.ts
  • packages/public-api/src/routes/status/types.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/public-api/src/routes/affiliate/claimPartnerCode.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/affiliate-dashboard/src/config/wagmi.ts
  • packages/public-api/src/routes/auth/siweNonce.ts
  • packages/public-api/src/routes/affiliate/getAffiliateSwaps.ts
  • packages/public-api/src/routes/status/getSwapStatus.ts
  • packages/public-api/src/routes/affiliate/types.ts
  • packages/public-api/src/routes/affiliate/updateAffiliate.ts
  • packages/public-api/src/routes/affiliate/getAffiliateStats.ts

…schemas

Move EVM_ADDRESS from affiliate/types to src/types so it can be shared
across routes. Apply to affiliateAddress fields in SwapServiceStatusSchema
and SwapStatusResponseSchema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/public-api/src/routes/affiliate/types.ts (1)

27-42: Consider adding max constraint for bps field.

The bps field validates min(0) but has no upper bound. Basis points typically max at 10000 (100%). If the upstream service enforces this, you might want to fail-fast here.

💡 Suggested validation
 export const CreateAffiliateRequestSchema = z.object({
   walletAddress: EVM_ADDRESS,
   receiveAddress: EVM_ADDRESS.optional(),
   partnerCode: z.string().trim().min(1, 'partnerCode must not be empty').optional(),
-  bps: z.number().int().min(0).optional(),
+  bps: z.number().int().min(0).max(10000, 'bps cannot exceed 10000 (100%)').optional(),
 })

 export const UpdateAffiliateRequestSchema = z.object({
   receiveAddress: EVM_ADDRESS.optional(),
-  bps: z.number().int().min(0).optional(),
+  bps: z.number().int().min(0).max(10000, 'bps cannot exceed 10000 (100%)').optional(),
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/public-api/src/routes/affiliate/types.ts` around lines 27 - 42, The
bps field currently only enforces .min(0) which allows values >10000; update the
zod validators for bps inside CreateAffiliateRequestSchema and
UpdateAffiliateRequestSchema to also enforce an upper bound (e.g., .max(10000,
'bps must be <= 10000')) so basis-points cannot exceed 100% and will fail-fast
before calling the upstream service.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/public-api/src/routes/affiliate/types.ts`:
- Around line 27-42: The bps field currently only enforces .min(0) which allows
values >10000; update the zod validators for bps inside
CreateAffiliateRequestSchema and UpdateAffiliateRequestSchema to also enforce an
upper bound (e.g., .max(10000, 'bps must be <= 10000')) so basis-points cannot
exceed 100% and will fail-fast before calling the upstream service.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ef8c19e-3a70-4623-b7be-cc2dbd14ea25

📥 Commits

Reviewing files that changed from the base of the PR and between 021366c and a7d2db7.

📒 Files selected for processing (3)
  • packages/public-api/src/routes/affiliate/types.ts
  • packages/public-api/src/routes/status/types.ts
  • packages/public-api/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/public-api/src/routes/status/types.ts

Copy link
Copy Markdown
Collaborator

@NeOMakinG NeOMakinG left a comment

Choose a reason for hiding this comment

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

Stamping for the sake of progression as this is quite urgent considering we have a customer accepting to integrate the widget with the affiliate system

And this should be tested carefully in the dev environment for DX purposes

@kaladinlight kaladinlight merged commit ea5c3f6 into develop Mar 26, 2026
4 checks passed
@kaladinlight kaladinlight deleted the feat/affiliate-public-api-routes branch March 26, 2026 16:11
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