feat: affiliate system alignment - public-api, widget, dashboard#12150
feat: affiliate system alignment - public-api, widget, dashboard#12150
Conversation
- Document current affiliate flow across web, widget, public-api, swappers - Identify gaps: no BPS storage, no swap attribution, no partner codes - Include mermaid diagrams for current and proposed architecture - List all related files and proposed database schema Closes: web-rdn
- Prisma schema for affiliates table and swap extensions - DTOs for API contracts - All endpoint specifications with request/response formats - Migration SQL - Validation rules and security considerations Closes: web-drq
- Lookup affiliate BPS from microservices when X-Affiliate-Address header provided - Add X-Partner-Code header support to resolve partner codes - Fallback to default 60 BPS if affiliate not registered - Add partnerCode to AffiliateInfo type Closes: web-9d4
- Add partnerCode prop to SwapWidgetProps - Add partnerCode to ApiClientConfig and pass X-Partner-Code header - Update SwapWidgetWithExternalWallet and SwapWidgetWithInternalWallet Closes: web-66a
- Add wagmi/viem for wallet connection (Arbitrum) - Support MetaMask and WalletConnect connectors - Auto-fetch stats when wallet connects - Display affiliate config (BPS, partner code, status) - Keep manual address input for non-connected viewing - Add useAffiliateConfig hook Closes: web-rca
- Quick start for widget and API - Header documentation - Registration and partner codes - Fee structure - Revenue attribution - API endpoints Closes: web-hoe
|
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:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
📝 WalkthroughWalkthroughReplaces affiliate-address/BPS header flow with partner-code-based attribution backed by a partner-resolution microservice; adds affiliate dashboard (Wagmi, SIWE auth, React Query, hooks), updates swap widget/client/hooks to accept Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Dashboard as Affiliate Dashboard
participant Auth as SIWE Auth
participant API as Public API
participant Partner as Partner Service
participant Storage as LocalStorage
User->>Dashboard: Connect wallet
Dashboard->>Auth: signIn()
Auth->>API: GET /v1/auth/siwe/nonce
API-->>Auth: nonce
Auth->>Auth: signMessage(SIWE)
Auth->>API: POST /v1/auth/siwe/verify
API-->>Auth: JWT
Auth->>Storage: store token & address
Dashboard->>API: GET /v1/affiliate/:address (auth)
API->>Partner: resolve partnerCode -> address + bps
Partner-->>API: affiliateAddress & bps
API-->>Dashboard: affiliate config
sequenceDiagram
actor User
participant Widget as Swap Widget
participant Client as ApiClient
participant PublicAPI as Public API
participant Partner as Partner Service
participant DB as Swap Store
User->>Widget: Initialize(partnerCode)
Widget->>Client: createApiClient(partnerCode)
Client->>PublicAPI: POST /v1/swap/quote (X-Partner-Code)
PublicAPI->>Partner: resolvePartnerCode(code)
Partner-->>PublicAPI: address & bps
PublicAPI->>DB: persist quote with attribution
DB-->>PublicAPI: quote created
PublicAPI-->>Client: quote result
Client-->>Widget: quote displayed
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/swap-widget/src/api/client.ts (1)
26-34:⚠️ Potential issue | 🟠 MajorDon’t send both affiliate selectors in the same request.
The new
x-partner-codeheader is added independently ofx-affiliate-address, so callers can now emit both at once. The PR describes those as alternate resolution paths, and if they disagree the request becomes backend-precedence dependent. Please make this deterministic here by choosing one precedence or rejecting the invalid combination before sending the request.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/swap-widget/src/api/client.ts` around lines 26 - 34, The code currently adds both headers independently (headers, config.affiliateAddress, config.affiliateBps, config.partnerCode), which allows sending x-affiliate-address and x-partner-code together; update the header-building logic in the client (where headers are populated) to enforce a deterministic rule: if both config.affiliateAddress and config.partnerCode are present, either reject the request (throw an error or return a validation failure) or enforce a single precedence (e.g., prefer config.partnerCode and omit x-affiliate-address and x-affiliate-bps); implement one of these behaviors and ensure the error/decision is surfaced before the request is sent.packages/public-api/src/middleware/auth.ts (1)
93-117:⚠️ Potential issue | 🟠 MajorDon't drop
X-Partner-CodewhenX-Affiliate-Addressis also present.The new partner-code branch only runs when
!address, and the laterreq.affiliateInfoassignment never includespartnerCode. So any request that sends both headers falls back to the legacy address path and loses the partner code entirely, which means the new header support won't reach downstream handlers on that path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/public-api/src/middleware/auth.ts` around lines 93 - 117, The partner code handling only runs when partnerCode && !address so requests that include both X-Partner-Code and X-Affiliate-Address drop partnerCode from req.affiliateInfo; update the logic in the middleware around partnerCode/address handling (the branch that calls resolvePartnerCode and the later assignment to req.affiliateInfo) so that partnerCode is preserved whenever present — e.g., always include partnerCode in the constructed req.affiliateInfo object (alongside affiliateAddress and affiliateBps), and still perform bps lookup via lookupAffiliateBps(address) when address is provided but bps is missing; ensure resolvePartnerCode is used when partnerCode is provided to populate affiliateAddress/affiliateBps if address/bps are missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.beads/backup/backup_state.json:
- Around line 1-13: This PR includes an ephemeral backup snapshot (the JSON
containing keys like "last_dolt_commit" and "counts") that should be dropped;
remove that file from the commit(s) in this PR, ensure .beads/*.json is ignored
going forward, and re-create a clean commit without the snapshot so it no longer
appears in the PR diff. Locate the file by the presence of the
"last_dolt_commit" key, remove it from the index/commit history for this branch,
confirm .beads/*.json is in .gitignore (or add it), and push an amended commit
so the snapshot is not part of the PR.
In @.beads/backup/config.jsonl:
- Around line 1-11: This file contains ephemeral local Beads backup state (keys
like "auto_compact_enabled", "issue_prefix", "schema_version") and should not be
merged; remove .beads/backup/config.jsonl from the PR by unstaging or deleting
it from the commit, ensure .beads/*.jsonl is excluded (or listed in .gitignore)
so it isn't re-added, and amend the commit (or create a new one) so only product
source changes remain in the PR.
In `@docs/architecture/affiliate-data-model.md`:
- Around line 15-16: The schema documents partnerCode as case-insensitive but
the Prisma/SQL column partner_code (partnerCode) is plain VARCHAR with UNIQUE,
so "Vultisig" and "vultisig" could coexist; update both the docs and
implementation to enforce lowercase writes and a case-insensitive uniqueness
constraint: ensure all write paths normalize partnerCode to lowercase (e.g., in
the creation/update logic that sets partnerCode) and add a database-level unique
index that is case-insensitive (either use the DB citext type or create a unique
index on lower(partner_code)) so the database rejects differing-case duplicates;
apply the same fixes for the other occurrences of partnerCode-like fields
referenced in the file.
- Around line 33-39: The current model ties Swap.affiliateAddress to the
Affiliate relation (affiliate Affiliate? `@relation`(..., fields:
[affiliateAddress], ...)), which forces every non-null affiliateAddress to be a
registered foreign key; instead keep affiliateAddress (affiliateAddress String?
`@map`("affiliate_address")) as a raw address field and introduce a separate
nullable foreign-key field (e.g., affiliateId String? or Int? depending on
Affiliate PK) to represent the relation; update the relation declaration to use
that new field (e.g., affiliate Affiliate? `@relation`("AffiliateSwaps", fields:
[affiliateId], references: [id|walletAddress]) and map it appropriately, leaving
affiliateAddress independent so unknown affiliates can still be stored.
In `@packages/affiliate-dashboard/src/App.tsx`:
- Around line 318-323: The empty-state branch currently shows "Loading your
affiliate stats..." even though it only renders when !isLoading; update the text
logic in the App component (referencing stats, statsError, isLoading,
isConnected, and styles.emptyText) so that when isConnected and not isLoading it
shows a true empty message (e.g., "No affiliate stats found." or "No stats
available for this affiliate.") instead of "Loading your affiliate stats...",
while leaving the non-connected prompt unchanged.
In `@packages/affiliate-dashboard/src/config/wagmi.ts`:
- Around line 5-6: The code currently uses a hardcoded fallback 'demo' for the
WalletConnect projectId (const projectId =
import.meta.env.VITE_WALLETCONNECT_PROJECT_ID || 'demo'), which is unsafe for
production; change this to derive an effectiveProjectId, e.g. read
import.meta.env.VITE_WALLETCONNECT_PROJECT_ID into projectId, if missing either
throw an error (fail fast) or log a clear warning via your logger and continue,
and then replace usages with effectiveProjectId so the connector uses the
validated value; reference the symbol projectId (and new effectiveProjectId) in
your wagmi connector setup and ensure the behavior in production is to fail or
emit a warning rather than silently using 'demo'.
In `@packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts`:
- Around line 30-58: In useAffiliateConfig, prevent stale responses by adding a
per-call request id (e.g. useRef<number> requestIdRef) and incrementing it at
the start of fetchConfig(address); clear the previous config immediately when a
new fetch starts (call setConfig(null) before the fetch) and store the current
id locally in the async closure; after each await (response checks, parsing)
only call setConfig or setError if the stored local id matches
requestIdRef.current (including when handling 404), so slower responses cannot
overwrite newer selections; keep setIsLoading(false) in finally but only if the
ids still match.
In `@packages/public-api/src/middleware/auth.ts`:
- Around line 16-33: In lookupAffiliateBps (and the similar helper functions
around lines 38-60) don’t collapse all failures into DEFAULT_AFFILIATE_BPS:
treat a 404/not-found as the only case that returns DEFAULT_AFFILIATE_BPS, but
for 5xx responses, non-JSON/bad payload, or network/timeouts log a structured
error including the address and response metadata and surface the error
(rethrow) so the caller can handle it instead of silently falling back; also add
a fetch timeout using AbortController (and clear it) to avoid hanging requests,
and validate the parsed payload (ensure data.bps is a number) — log and throw on
invalid payloads.
---
Outside diff comments:
In `@packages/public-api/src/middleware/auth.ts`:
- Around line 93-117: The partner code handling only runs when partnerCode &&
!address so requests that include both X-Partner-Code and X-Affiliate-Address
drop partnerCode from req.affiliateInfo; update the logic in the middleware
around partnerCode/address handling (the branch that calls resolvePartnerCode
and the later assignment to req.affiliateInfo) so that partnerCode is preserved
whenever present — e.g., always include partnerCode in the constructed
req.affiliateInfo object (alongside affiliateAddress and affiliateBps), and
still perform bps lookup via lookupAffiliateBps(address) when address is
provided but bps is missing; ensure resolvePartnerCode is used when partnerCode
is provided to populate affiliateAddress/affiliateBps if address/bps are
missing.
In `@packages/swap-widget/src/api/client.ts`:
- Around line 26-34: The code currently adds both headers independently
(headers, config.affiliateAddress, config.affiliateBps, config.partnerCode),
which allows sending x-affiliate-address and x-partner-code together; update the
header-building logic in the client (where headers are populated) to enforce a
deterministic rule: if both config.affiliateAddress and config.partnerCode are
present, either reject the request (throw an error or return a validation
failure) or enforce a single precedence (e.g., prefer config.partnerCode and
omit x-affiliate-address and x-affiliate-bps); implement one of these behaviors
and ensure the error/decision is surfaced before the request is sent.
🪄 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: 7b23a33c-3a99-41d6-8871-1e007e724df1
📒 Files selected for processing (20)
.beads/backup/backup_state.json.beads/backup/comments.jsonl.beads/backup/config.jsonl.beads/backup/dependencies.jsonl.beads/backup/events.jsonl.beads/backup/issues.jsonl.beads/backup/labels.jsonldocs/affiliates.mddocs/architecture/affiliate-data-model.mddocs/architecture/affiliate-system.mdpackages/affiliate-dashboard/package.jsonpackages/affiliate-dashboard/src/App.tsxpackages/affiliate-dashboard/src/config/wagmi.tspackages/affiliate-dashboard/src/hooks/useAffiliateConfig.tspackages/affiliate-dashboard/src/main.tsxpackages/public-api/src/middleware/auth.tspackages/public-api/src/types.tspackages/swap-widget/src/api/client.tspackages/swap-widget/src/components/SwapWidget.tsxpackages/swap-widget/src/types/index.ts
…lic-api # Conflicts: # pnpm-lock.yaml
gomesalexandre
left a comment
There was a problem hiding this comment.
Deep Review — Affiliate System Alignment
I went through every file in the diff (excluding lockfile and .beads/ backup artifacts). Here are my findings organized by severity.
🔴 Issues
1. .beads/backup/ files should not be in this PR
These are project management tool backup files (issues, events, config JSONLs) — not source code. They add ~23KB of unrelated data and will pollute the repo history. Should be .gitignored or removed from the PR.
2. server-standalone.ts middleware is out of sync with auth.ts
The standalone server (line 24-46) has its own simplified affiliateAddress middleware that does NOT include the new partner code support (X-Partner-Code header), the async BPS lookup from microservices, or the partnerCode field on affiliateInfo. If anyone runs the standalone server, partner codes will silently fail. Either:
- Refactor to share the same middleware, or
- Add a comment that standalone is intentionally simplified, or
- Sync the two implementations
3. Middleware changed from sync to async without error boundary
auth.ts:affiliateAddress is now async — if lookupAffiliateBps or resolvePartnerCode throw an unhandled error (e.g., JSON parse failure on a non-JSON response), the Express request will hang. The try/catch blocks exist but response.json() could throw if the response body is malformed. Consider wrapping the entire middleware body in a try/catch that calls next() on any unexpected error.
🟡 Suggestions
4. useAffiliateConfig.ts API base URL is relative (/v1/affiliate)
This assumes the dashboard is served from the same origin as the public API. If the dashboard is deployed separately (likely), this will 404. Should use an env var like VITE_API_BASE_URL similar to how the swap widget handles it.
5. wagmi config: projectId fallback is "demo"
packages/affiliate-dashboard/src/config/wagmi.ts:7 — WalletConnect with projectId: "demo" will fail in production. The comment says "should be env var in production" but there's no validation or warning when the env var is missing.
6. No input validation removed in dashboard
The old isValidEvmAddress check was removed from the dashboard App.tsx when switching to wallet-connect-first flow. Manual address input no longer validates format before fetching. Could send garbage addresses to the API (which does validate server-side, so not critical, but worse UX — user sees generic "Request failed (400)" instead of a helpful client-side message).
7. @tanstack/react-query version pinning
package.json pins "@tanstack/react-query": "^5.0.0" but wagmi 2.x requires specific react-query 5 versions. Consider matching the version used elsewhere in the monorepo to avoid resolution conflicts.
✅ What Looks Good
- Code is clean and readable — hooks are well-structured, types are explicit
- Docs are comprehensive — the 3 docs files cover the affiliate program, system architecture, and data model thoroughly. The Prisma schema, API contracts, and migration SQL are well thought out
- Graceful degradation — BPS lookup falls back to default 60 on any error, partner code resolution fails silently. No hard failures in the middleware
- SwapWidget changes are minimal and correct —
partnerCodeprop threaded through to the API client correctly in both external and internal wallet variants - Wallet connection UX — auto-fetch on connect, manual fallback for non-connected viewing, clean disconnect flow
Summary
The core affiliate alignment (public-api middleware + widget + dashboard) is solid. Main concerns are the .beads/ files that shouldn't be in the PR, the standalone server middleware being out of sync, and the async middleware needing a top-level error boundary. The docs are excellent.
- Remove .beads/ backup files from PR - Fix empty state text for connected users (was showing 'Loading' after load) - Add console.warn when WalletConnect projectId is missing - Guard fetchConfig against stale responses with request ID ref
gomesalexandre
left a comment
There was a problem hiding this comment.
✅ Re-review — Post CodeRabbit Fixes
All CodeRabbit comments addressed. Fresh review of the current state:
What Changed Since First Review
.beads/backup files removed from PR ✅- Empty state text fixed (no longer says "Loading" after load completes) ✅
console.warnadded for missing WalletConnect projectId ✅useAffiliateConfignow guards against stale responses viarequestIdRef✅
Remaining Architectural Notes (non-blocking)
server-standalone.tsstill has its own simplified affiliate middleware — intentionally different fromauth.ts. Worth aligning eventually but not blocking.- Microservice error handling intentionally degrades to defaults — reasonable for MVP.
- Partner code case-insensitivity and Swap FK strictness are docs/schema proposals for the microservices PR, not enforced in this code.
Code Quality
- All hooks well-structured with proper cleanup
- Types are explicit, no
anyusage - SwapWidget threading is minimal and correct
- Docs are thorough (724 lines across 3 files)
- Dashboard wallet connection UX is clean
Ship it. 🚀
- Always fetch fresh BPS from server on page load (no stale cache) - Send partnerCode + sellAmountUsd in POST /swaps (drop affiliateAddress) - Use bnOrZero for precise USD calculations - Store partner code to localStorage only after successful server validation Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- useSiweAuth: SIWE sign-in flow with Reown AppKit (Arbitrum only) - useAffiliateSwaps: paginated swap history from backend - useAffiliateConfig: receiveAddress support - wagmi config aligned with existing Reown version Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Single-page dashboard with Overview, Swap History, and Settings tabs - Inline styles only, no semicolons, dark theme - BPS display subtracts ShapeShift's 10 BPS cut via parseInt Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Remove affiliateAddress/affiliateBps header handling - Resolve partner code via swap-service, compute totalBps server-side - Update startup banner and standalone server Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Nest relay metadata under relayTransactionMetadata in quote response - Remove affiliate address mismatch check from status endpoint - Update OpenAPI schema with X-Partner-Code header Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Thread partnerCode and appUrl through SwapWidgetCore and SwapWidgetContent - Remove affiliateAddress prop, add appUrl for configurable redirect target - Demo uses placeholder partner code Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Send X-Partner-Code header in API requests - Add getSwapStatus method for swap registration - Pass partnerCode through redirect URLs (?partner=code) - Configurable appUrl for redirect target Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Register swap with backend on first status poll - Fix EVM_CHAINS_BY_ID -> VIEM_CHAINS_BY_ID reference (runtime crash) - Add worldchain (480) and all 13 EVM chains via VIEM_CHAINS_BY_ID import Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Replace affiliateAddress/affiliateBps with partnerCode prop - Document X-Partner-Code header for REST API usage - Remove caip/utils from optimizeDeps exclude Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…lic-api # Conflicts: # pnpm-lock.yaml
…and tradeExecution
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/public-api/src/routes/quote.ts (1)
494-501:⚠️ Potential issue | 🟠 MajorUpdate
StoredQuote.metadatatype definition to match the new relay metadata structure.The code at lines 498-500 stores relay data as
relayTransactionMetadata: { relayId }, but theStoredQuote.metadatatype definition inpackages/public-api/src/lib/quoteStore.ts(line 22) still expects a flatrelayId?: stringfield. Update the type to reflect the new nested structure:metadata: { chainflipSwapId?: number nearIntentsDepositAddress?: string nearIntentsDepositMemo?: string relayTransactionMetadata?: { relayId: string } // Update this cowswapOrderUid?: string acrossDepositId?: string }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/public-api/src/routes/quote.ts` around lines 494 - 501, Update the StoredQuote metadata type in packages/public-api/src/lib/quoteStore.ts so it matches the new nested relay object shape used when writing quotes: change the metadata definition on the StoredQuote interface/type (the metadata property) to include relayTransactionMetadata?: { relayId: string } instead of a flat relayId?: string, and keep the other optional fields (chainflipSwapId, nearIntentsDepositAddress, nearIntentsDepositMemo, cowswapOrderUid, acrossDepositId) as optional; update any related type references of StoredQuote.metadata to use the new nested relayTransactionMetadata shape.packages/public-api/src/server-standalone.ts (1)
207-229:⚠️ Potential issue | 🟡 MinorKeep the standalone server aligned with the new status-registration flow.
The widget now makes a first
/v1/swap/statuscall to bindquoteIdtotxHash, but this mock server still only documents/exposes/ratesand/quote. Pointing the updated widget atserver-standalone.tswill silently 404 that call and never exercise partner-tracked status registration locally.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/public-api/src/server-standalone.ts` around lines 207 - 229, The standalone server currently documents only /v1/swap/rates and /v1/swap/quote but must also implement the new /v1/swap/status endpoint so the widget's initial status-registration call succeeds; add a POST /v1/swap/status route (e.g., handler name registerSwapStatus or postSwapStatus) that accepts JSON with quoteId and txHash (and respects X-Partner-Code header), stores the binding in the server's in-memory map used by the mock quote flow, and update the endpoints listing/comment block to include the /v1/swap/status entry so local partner-tracked status registration is exercised.
🧹 Nitpick comments (3)
packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts (1)
5-17: Consider narrowing the asset field types.The union type
string | { symbol?: string; name?: string }forsellAssetandbuyAssetis flexible but may indicate API response inconsistency. If the API can return either shape, this is fine; otherwise, consider aligning with the actual API contract.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts` around lines 5 - 17, The sellAsset/buyAsset fields use a loose inline union (string | { symbol?: string; name?: string }); create a named Asset interface (e.g. interface Asset { symbol?: string; name?: string }) and then update the AffiliateSwap type to use either Asset (if API always returns object) or a clear alias AssetOrString = string | Asset (if the API can return both) so the shape is explicit and reusable; update any references to AffiliateSwap accordingly (sellAsset, buyAsset).packages/swap-widget/README.md (1)
488-491: Add language identifier to fenced code block.The static analysis tool flagged this code block as missing a language specification. Since this shows HTTP headers, use
httportextas the language identifier.📝 Suggested fix
-``` +```http GET /v1/swap/rates?... X-Partner-Code: your-partner-code</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@packages/swap-widget/README.mdaround lines 488 - 491, The fenced code block
showing the HTTP request (starting with "GET /v1/swap/rates?...") is missing a
language identifier; update that triple-backtick block to include a language tag
such as "http" (or "text") so the block becomes ```http and ensure the contents
remain unchanged to preserve the header "X-Partner-Code: your-partner-code".</details> </blockquote></details> <details> <summary>packages/swap-widget/src/api/client.ts (1)</summary><blockquote> `68-72`: **Consider adding a specific response type for swap status.** The `getSwapStatus` method returns `Record<string, unknown>`, which loses type safety. If the status response shape is known, a specific type would improve developer experience. <details> <summary>💡 Optional: Define a SwapStatusResponse type</summary> ```diff +export type SwapStatusResponse = { + status: string + message?: string + buyTxHash?: string + // add other known fields +} + getSwapStatus: (params: { quoteId: string; txHash?: string }) => - fetchWithConfig<Record<string, unknown>>('/v1/swap/status', { + fetchWithConfig<SwapStatusResponse>('/v1/swap/status', { quoteId: params.quoteId, ...(params.txHash && { txHash: params.txHash }), }), ``` </details> <details> <summary>🤖 Prompt for AI Agents</summary> ``` Verify each finding against the current code and only fix it if needed. In `@packages/swap-widget/src/api/client.ts` around lines 68 - 72, The getSwapStatus helper currently types its response as Record<string, unknown>, losing type safety; define a concrete response interface (e.g., SwapStatusResponse) that models the known fields of the swap status payload and replace Record<string, unknown> with SwapStatusResponse in the getSwapStatus signature, updating any call sites as needed; keep using fetchWithConfig but change its generic parameter to the new SwapStatusResponse type so callers get proper typings for the swap status shape. ``` </details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@packages/affiliate-dashboard/src/App.tsx:
- Around line 222-233: The registration handler currently treats "0" BPS as
falsy because it uses parseInt(registerBps, 10) || 30 in handleRegister (and the
same pattern around registerBps at the second occurrence), causing a legitimate
0 to be replaced with 30; change the logic to detect empty/invalid input rather
than falsy numeric 0 (e.g., use a conditional that checks registerBps is empty
or parseInt returns NaN, or use nullish coalescing with an explicit NaN check)
so that parseInt(registerBps, 10) yields 0 when the user entered "0" and only
falls back to 30 when the input is missing/invalid; update both occurrences (the
one in handleRegister and the other block referencing registerBps at lines
~673-680).In
@packages/affiliate-dashboard/src/hooks/useSiweAuth.ts:
- Around line 23-25: The hook currently persists the SIWE bearer token to
localStorage via getStoredToken (and related setters at the other locations),
which enables credential theft via XSS; remove localStorage usage and stop
rehydrating the JWT from localStorage. Replace getStoredToken / setStoredToken /
clearStoredToken logic to use ephemeral in-memory storage (module-scoped
variable) or rely on a server-set HttpOnly session cookie and only read session
state from the server on mount (update the useSiweAuth effect to fetch session
state and populate React state without writing a persistent token). Ensure all
references to TOKEN_KEY/localStorage in getStoredToken, the setter and any
rehydration code are removed/rewired to the in-memory or cookie-based approach
and that token values are never written to or read from localStorage.- Around line 74-79: The auth logic compares and persists addresses
inconsistently; normalize the verified address (and stored address) before
storing or comparing by lowercasing them so checksum-cased values don't break
equality. Update places using verifiedAddress (e.g. where you call
setAuthenticatedAddress, persist it, and where you compare stored === address)
to use verifiedAddress.toLowerCase() (and ensure address/from getStoredAddress()
are compared via .toLowerCase()). Keep all persisted/compared addresses in
lowercase (or consistently normalized) across getStoredAddress(),
setAuthenticatedAddress, and clearAuth usage to prevent immediate session
clears.In
@packages/swap-widget/src/hooks/useStatusPolling.ts:
- Around line 45-55: The current logic sets registeredWithApi = true before
apiClient.getSwapStatus() completes, so a transient failure prevents future
registration; change poll() so it only sets registeredWithApi to true after a
successful getSwapStatus() resolution (e.g., await apiClient.getSwapStatus(...)
inside a try block and set registeredWithApi = true on success), and keep the
catch handler without flipping the guard (so failures leave registeredWithApi
false and allow subsequent retries using the existing poll loop with
context.quote?.quoteId and context.txHash checks).In
@src/hooks/useAffiliateTracking/useAffiliateTracking.ts:
- Around line 88-103: The resolvePartnerCode function performs a fetch without a
timeout; update it to call the partner lookup via timeoutMonadic (wrapping the
fetch to${PARTNER_LOOKUP_URL}/v1/partner/${encodeURIComponent(code)}) with an
appropriate timeout value (e.g., 2–5s) and await the result, then keep the
existing response.ok check and JSON parsing path, and ensure the catch block
distinguishes/handles timeout errors gracefully (returning null as before) so
timeouts don't hang the call; reference resolvePartnerCode, PARTNER_LOOKUP_URL,
and the existing fetch/encodeURIComponent usage when making the change.
Outside diff comments:
In@packages/public-api/src/routes/quote.ts:
- Around line 494-501: Update the StoredQuote metadata type in
packages/public-api/src/lib/quoteStore.ts so it matches the new nested relay
object shape used when writing quotes: change the metadata definition on the
StoredQuote interface/type (the metadata property) to include
relayTransactionMetadata?: { relayId: string } instead of a flat relayId?:
string, and keep the other optional fields (chainflipSwapId,
nearIntentsDepositAddress, nearIntentsDepositMemo, cowswapOrderUid,
acrossDepositId) as optional; update any related type references of
StoredQuote.metadata to use the new nested relayTransactionMetadata shape.In
@packages/public-api/src/server-standalone.ts:
- Around line 207-229: The standalone server currently documents only
/v1/swap/rates and /v1/swap/quote but must also implement the new
/v1/swap/status endpoint so the widget's initial status-registration call
succeeds; add a POST /v1/swap/status route (e.g., handler name
registerSwapStatus or postSwapStatus) that accepts JSON with quoteId and txHash
(and respects X-Partner-Code header), stores the binding in the server's
in-memory map used by the mock quote flow, and update the endpoints
listing/comment block to include the /v1/swap/status entry so local
partner-tracked status registration is exercised.
Nitpick comments:
In@packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts:
- Around line 5-17: The sellAsset/buyAsset fields use a loose inline union
(string | { symbol?: string; name?: string }); create a named Asset interface
(e.g. interface Asset { symbol?: string; name?: string }) and then update the
AffiliateSwap type to use either Asset (if API always returns object) or a clear
alias AssetOrString = string | Asset (if the API can return both) so the shape
is explicit and reusable; update any references to AffiliateSwap accordingly
(sellAsset, buyAsset).In
@packages/swap-widget/README.md:
- Around line 488-491: The fenced code block showing the HTTP request (starting
with "GET /v1/swap/rates?...") is missing a language identifier; update that
triple-backtick block to include a language tag such as "http" (or "text") so
the block becomes ```http and ensure the contents remain unchanged to preserve
the header "X-Partner-Code: your-partner-code".In
@packages/swap-widget/src/api/client.ts:
- Around line 68-72: The getSwapStatus helper currently types its response as
Record<string, unknown>, losing type safety; define a concrete response
interface (e.g., SwapStatusResponse) that models the known fields of the swap
status payload and replace Record<string, unknown> with SwapStatusResponse in
the getSwapStatus signature, updating any call sites as needed; keep using
fetchWithConfig but change its generic parameter to the new SwapStatusResponse
type so callers get proper typings for the swap status shape.</details> <details> <summary>🪄 Autofix (Beta)</summary> Fix all unresolved CodeRabbit comments on this PR: - [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended) - [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes </details> --- <details> <summary>ℹ️ Review info</summary> <details> <summary>⚙️ Run configuration</summary> **Configuration used**: Organization UI **Review profile**: CHILL **Plan**: Pro **Run ID**: `84320fcd-0223-4beb-b44d-b46e26365ff6` </details> <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 5d1cd6ff59ac60ad8c58b8ddf5953ec94fe7d5e7 and 15e1311bb827ec4d811631b1a4b0330d797bbf0f. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml` </details> <details> <summary>📒 Files selected for processing (51)</summary> * `.beads/pr-context.jsonl` * `.beads/ss-dx5.10.json` * `.beads/ss-dx5.12.json` * `.beads/ss-dx5.13.json` * `.beads/ss-dx5.14.json` * `.beads/ss-dx5.15.json` * `.beads/ss-dx5.16.json` * `.beads/ss-dx5.17.json` * `.beads/ss-dx5.18.json` * `.beads/ss-dx5.19.json` * `.beads/ss-dx5.2.json` * `.beads/ss-dx5.20.json` * `.beads/ss-dx5.21.json` * `.beads/ss-dx5.3.json` * `.beads/ss-dx5.4.json` * `.beads/ss-dx5.5.json` * `.beads/ss-dx5.6.json` * `.beads/ss-dx5.7.json` * `.beads/ss-dx5.8.json` * `.beads/ss-dx5.9.json` * `.beads/ss-dx5.json` * `package.json` * `packages/affiliate-dashboard/package.json` * `packages/affiliate-dashboard/src/App.tsx` * `packages/affiliate-dashboard/src/config/wagmi.ts` * `packages/affiliate-dashboard/src/hooks/useAffiliateConfig.ts` * `packages/affiliate-dashboard/src/hooks/useAffiliateSwaps.ts` * `packages/affiliate-dashboard/src/hooks/useSiweAuth.ts` * `packages/affiliate-dashboard/src/main.tsx` * `packages/public-api/src/docs/openapi.ts` * `packages/public-api/src/index.ts` * `packages/public-api/src/middleware/auth.ts` * `packages/public-api/src/routes/quote.ts` * `packages/public-api/src/routes/status.ts` * `packages/public-api/src/server-standalone.ts` * `packages/swap-widget/README.md` * `packages/swap-widget/src/api/client.ts` * `packages/swap-widget/src/components/SwapWidget.tsx` * `packages/swap-widget/src/constants/viemChains.ts` * `packages/swap-widget/src/demo/App.tsx` * `packages/swap-widget/src/hooks/useStatusPolling.ts` * `packages/swap-widget/src/hooks/useSwapHandlers.ts` * `packages/swap-widget/src/hooks/useSwapRates.ts` * `packages/swap-widget/src/services/transactionStatus.ts` * `packages/swap-widget/src/types/index.ts` * `packages/swap-widget/src/utils/redirect.ts` * `packages/swap-widget/vite.config.ts` * `src/hooks/useAffiliateTracking/index.ts` * `src/hooks/useAffiliateTracking/useAffiliateTracking.ts` * `src/lib/fees/utils.ts` * `src/lib/tradeExecution.ts` </details> <details> <summary>💤 Files with no reviewable changes (21)</summary> * .beads/ss-dx5.18.json * .beads/ss-dx5.8.json * .beads/ss-dx5.9.json * .beads/ss-dx5.5.json * .beads/ss-dx5.2.json * .beads/ss-dx5.6.json * .beads/ss-dx5.15.json * .beads/ss-dx5.12.json * .beads/ss-dx5.13.json * .beads/ss-dx5.7.json * .beads/ss-dx5.17.json * .beads/ss-dx5.21.json * .beads/ss-dx5.3.json * .beads/ss-dx5.14.json * .beads/ss-dx5.16.json * .beads/ss-dx5.4.json * packages/public-api/src/routes/status.ts * .beads/ss-dx5.20.json * .beads/ss-dx5.10.json * .beads/ss-dx5.19.json * packages/swap-widget/vite.config.ts </details> <details> <summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary> * packages/swap-widget/src/types/index.ts * packages/public-api/src/middleware/auth.ts </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
… status retry, fetch timeout - Move SIWE token from localStorage to in-memory state to mitigate XSS risk - Normalize verified address to lowercase before comparison - Fix parseInt || 30 treating 0 BPS as falsy via Number.isNaN check - Retry swap status API registration on next poll cycle instead of fire-and-forget - Add 5s AbortController timeout to partner code resolution fetch
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
packages/affiliate-dashboard/src/App.tsx (1)
232-233:⚠️ Potential issue | 🟠 Major
0BPS is still being coerced to30during registration.
parseInt(registerBps, 10) || 30treats a valid0as falsy, so Line 232 submits the wrong value.💡 Minimal fix
try { + const parsedRegisterBps = Number.parseInt(registerBps, 10) const res = await fetch(API_BASE, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders }, body: JSON.stringify({ walletAddress: affiliateAddress, - bps: parseInt(registerBps, 10) || 30, + bps: Number.isNaN(parsedRegisterBps) ? 30 : parsedRegisterBps, }), })#!/bin/bash node - <<'NODE' const raw = '0' const parsed = Number.parseInt(raw, 10) console.log('Current:', parsed || 30) // 30 (incorrect) console.log('Fixed :', Number.isNaN(parsed) ? 30 : parsed) // 0 (correct) NODE🤖 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 232 - 233, The registration code currently uses parseInt(registerBps, 10) || 30 which treats a legitimate 0 as falsy and coerces it to 30; change the logic in the object where bps is set (the bps: parseInt(registerBps, 10) || 30 expression) to check for NaN instead, e.g. compute parsed = Number.parseInt(registerBps, 10) and set bps to Number.isNaN(parsed) ? 30 : parsed so that 0 remains 0 but non-numeric inputs fall back to 30.
🤖 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`:
- Around line 275-284: In handleUpdateBps validate updateBps before sending the
PATCH: parse it to an integer (e.g., const bps = Number.parseInt(updateBps,
10)), verify Number.isInteger(bps) and 0 <= bps && bps <= 1000, and if
validation fails call clearActionMessage()/setActionMessage (or similar) and
return early; only construct the fetch body with the validated bps to avoid
sending NaN/null. Reference: handleUpdateBps, updateBps, affiliateAddress,
setActionLoading, clearActionMessage.
- Around line 196-217: The three useEffect hooks suppress exhaustive-deps and
therefore risk stale closures; update each effect to include the functions and
values they reference instead of disabling the rule: 1) the effect that calls
setSwapPage(0) and doFetch should list doFetch and setSwapPage (and any of
doFetch's own dependencies such as affiliateAddress/currentPeriod/swapPage if
doFetch is defined inline) in its dependency array; 2) the effect that checks
affiliateAddress.trim() and calls doFetch should include affiliateAddress and
doFetch (or move the trim check into a stable callback) along with
selectedPeriod; and 3) the effect that calls fetchSwaps should include
affiliateAddress, currentPeriod, and fetchSwaps (not just swapPage), or refactor
fetchSwaps into a memoized callback (useCallback) that declares its dependencies
so the useEffect can safely depend on it.
In `@packages/public-api/src/server-standalone.ts`:
- Around line 22-36: The middleware partnerTracking uses (req as any) and has no
return type; replace that with a proper typed request shape by declaring an
interface (e.g., PartnerRequest extends express.Request { affiliateInfo?: {
partnerCode: string } }) and type the first parameter as PartnerRequest, remove
the leading semicolon and the any cast, assign req.affiliateInfo = { partnerCode
}, and add an explicit return type (e.g., : express.RequestHandler or the
function signature returning void) so the function is type-safe and conforms to
no-semicolon style.
---
Duplicate comments:
In `@packages/affiliate-dashboard/src/App.tsx`:
- Around line 232-233: The registration code currently uses
parseInt(registerBps, 10) || 30 which treats a legitimate 0 as falsy and coerces
it to 30; change the logic in the object where bps is set (the bps:
parseInt(registerBps, 10) || 30 expression) to check for NaN instead, e.g.
compute parsed = Number.parseInt(registerBps, 10) and set bps to
Number.isNaN(parsed) ? 30 : parsed so that 0 remains 0 but non-numeric inputs
fall back to 30.
🪄 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: cf558533-3180-4652-96c5-51a9e5ae30f5
📒 Files selected for processing (6)
packages/affiliate-dashboard/src/App.tsxpackages/affiliate-dashboard/src/main.tsxpackages/public-api/src/server-standalone.tspackages/swap-widget/src/hooks/useSwapHandlers.tssrc/lib/tradeExecution.tstsconfig.json
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/swap-widget/src/hooks/useSwapHandlers.ts
- packages/affiliate-dashboard/src/main.tsx
…tion, typed request - Add missing dependencies to three useEffect hooks, remove eslint-disable-line suppressions - Validate updateBps (0-1000 range, NaN check) before PATCH request - Replace `as any` cast in server-standalone with global Express.Request augmentation - Add explicit void return type to partnerTracking middleware
…olvePartnerCode, update tests to X-Partner-Code - Exclude packages/swap-widget/** from root tsconfig to fix wagmi type conflict in CI - Rename affiliateAddress middleware export to resolvePartnerCode for clarity - Update smoke tests from X-Affiliate-Address to X-Partner-Code header - Replace TEST_AFFILIATE_ADDRESS with TEST_PARTNER_CODE in test config
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/public-api/src/middleware/auth.ts (1)
14-22: Response payload lacks runtime validation.The response is type-asserted without validating that
data.affiliateAddressis a string anddata.bpsis a number. If the microservice returns an unexpected shape, this could propagate invalid values downstream.🛡️ Proposed validation
if (response.ok) { - const data = (await response.json()) as { - affiliateAddress: string - bps: number - } + const data = (await response.json()) as Record<string, unknown> + + if ( + typeof data.affiliateAddress !== 'string' || + typeof data.bps !== 'number' + ) { + console.error('[resolvePartnerCodeFromService] Invalid response shape:', { code, data }) + return null + } + return { affiliateAddress: data.affiliateAddress, bps: String(data.bps), } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/public-api/src/middleware/auth.ts` around lines 14 - 22, The code is asserting the response shape without runtime checks (variable data in middleware/auth.ts) so validate that data.affiliateAddress is a string and data.bps is a finite number before returning; if valid, return { affiliateAddress: data.affiliateAddress, bps: String(data.bps) }, otherwise throw or return a clear error/invalid result. Locate the response.ok branch and the local variable data, add typeof checks (and Number.isFinite for bps) and handle the failure path consistently (throw an Error or return null) so malformed responses cannot propagate.packages/affiliate-dashboard/src/App.tsx (1)
140-877: Consider extracting sub-components or hooks to improve maintainability.The
Appcomponent spans ~740 lines (excluding styles), which exceeds the 200-line guideline. Potential candidates for extraction:
OverviewTab,SwapsTab,SettingsTabcomponentsuseAffiliateActionshook encapsulatinghandleRegister,handleClaimCode,handleUpdateBps,handleUpdateReceiveAddressConfigBarcomponent for the affiliate config displayThis would improve readability and make individual pieces easier to test. Deferring to a follow-up is fine given the PR scope.
🤖 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 140 - 877, The App component is very large—extract logical pieces to improve readability: create three subcomponents OverviewTab, SwapsTab, and SettingsTab and move the JSX blocks currently gated by activeTab into them (reference activeTab, statCards, swaps, swapsLoading, swapsError, totalSwapPages, swapPage, setSwapPage, periods, selectedPeriod, setSelectedPeriod). Extract ConfigBar as a small component that reads affiliateConfig and renders the configBar block (reference affiliateConfig and styles.configBar). Move the four action handlers and related state (registerBps, claimCode, updateBps, updateReceiveAddress, actionLoading, actionMessage, clearActionMessage, handleRegister, handleClaimCode, handleUpdateBps, handleUpdateReceiveAddress, fetchConfig, authHeaders, affiliateAddress) into a new useAffiliateActions hook that returns the state and handlers so SettingsTab can consume them. Keep App responsible for data fetching hooks (useAffiliateStats/useAffiliateConfig/useAffiliateSwaps), selectedPeriod/swapPage state and the top-level layout; pass required props into the new components/hooks.
🤖 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`:
- Around line 674-675: The registration form is shown when affiliateConfig is
null even while the config is still loading; update the render condition in
App.tsx (the fragment using affiliateConfig and isAuthenticated) to also check
the loading flag (configLoading) so it only renders the registration UI when
affiliateConfig is null AND isAuthenticated AND configLoading is false (i.e.,
!configLoading), ensuring the form doesn't flash while load is in progress.
In `@packages/public-api/src/middleware/auth.ts`:
- Around line 8-29: resolvePartnerCodeFromService lacks the 5s AbortController
timeout and swallows errors; wrap the fetch in an AbortController with a 5000ms
setTimeout that calls controller.abort(), pass controller.signal into
fetch(`${MICROSERVICES_URL}/v1/partner/${encodeURIComponent(code)}`), clear the
timeout after response, and in the catch block log the error with structured
context (include the partner code and target URL) before returning null; keep
references to resolvePartnerCodeFromService and MICROSERVICES_URL so the changes
are applied to the correct function.
---
Nitpick comments:
In `@packages/affiliate-dashboard/src/App.tsx`:
- Around line 140-877: The App component is very large—extract logical pieces to
improve readability: create three subcomponents OverviewTab, SwapsTab, and
SettingsTab and move the JSX blocks currently gated by activeTab into them
(reference activeTab, statCards, swaps, swapsLoading, swapsError,
totalSwapPages, swapPage, setSwapPage, periods, selectedPeriod,
setSelectedPeriod). Extract ConfigBar as a small component that reads
affiliateConfig and renders the configBar block (reference affiliateConfig and
styles.configBar). Move the four action handlers and related state (registerBps,
claimCode, updateBps, updateReceiveAddress, actionLoading, actionMessage,
clearActionMessage, handleRegister, handleClaimCode, handleUpdateBps,
handleUpdateReceiveAddress, fetchConfig, authHeaders, affiliateAddress) into a
new useAffiliateActions hook that returns the state and handlers so SettingsTab
can consume them. Keep App responsible for data fetching hooks
(useAffiliateStats/useAffiliateConfig/useAffiliateSwaps),
selectedPeriod/swapPage state and the top-level layout; pass required props into
the new components/hooks.
In `@packages/public-api/src/middleware/auth.ts`:
- Around line 14-22: The code is asserting the response shape without runtime
checks (variable data in middleware/auth.ts) so validate that
data.affiliateAddress is a string and data.bps is a finite number before
returning; if valid, return { affiliateAddress: data.affiliateAddress, bps:
String(data.bps) }, otherwise throw or return a clear error/invalid result.
Locate the response.ok branch and the local variable data, add typeof checks
(and Number.isFinite for bps) and handle the failure path consistently (throw an
Error or return null) so malformed responses cannot propagate.
🪄 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: 2c3d0463-7942-4239-9e1d-7448488e9326
📒 Files selected for processing (10)
packages/affiliate-dashboard/src/App.tsxpackages/affiliate-dashboard/src/hooks/useSiweAuth.tspackages/public-api/src/index.tspackages/public-api/src/middleware/auth.tspackages/public-api/src/server-standalone.tspackages/public-api/tests/smoke-tests.tspackages/public-api/tests/test-config.tspackages/swap-widget/src/hooks/useStatusPolling.tssrc/hooks/useAffiliateTracking/useAffiliateTracking.tstsconfig.json
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/public-api/src/server-standalone.ts
- tsconfig.json
- packages/affiliate-dashboard/src/hooks/useSiweAuth.ts
- packages/swap-widget/src/hooks/useStatusPolling.ts
- src/hooks/useAffiliateTracking/useAffiliateTracking.ts
- packages/public-api/src/index.ts
…st failure The swap-widget had @shapeshiftoss/caip, types, utils listed as both npm semver ranges in dependencies and workspace:^ in devDependencies. pnpm resolved the npm version (8.16.8) instead of the workspace (8.16.7), creating a nested node_modules copy whose ESM build has broken lodash imports (missing .js extension). Using workspace:^ in dependencies and removing the duplicate devDependencies entries fixes the resolution.
Summary
Align affiliate BPS handling across web, widget, public-api, and affiliate-dashboard.
Changes
packages/public-api
X-Affiliate-Addressheader providedX-Partner-Codeheader support for partner code resolutionpartnerCodeto AffiliateInfo typepackages/swap-widget
partnerCodeprop to SwapWidgetPropsX-Partner-Codeheader in API requestspackages/affiliate-dashboard
docs/
docs/affiliates.md- affiliate program guidedocs/architecture/affiliate-system.md- system architecturedocs/architecture/affiliate-data-model.md- data model designRelated
Testing
X-Affiliate-AddressheaderaffiliateAddressandpartnerCodepropsSummary by CodeRabbit
New Features
Documentation