Skip to content

feat: affiliate system alignment - public-api, widget, dashboard#12150

Merged
NeOMakinG merged 29 commits intodevelopfrom
feat/affiliate-public-api
Mar 13, 2026
Merged

feat: affiliate system alignment - public-api, widget, dashboard#12150
NeOMakinG merged 29 commits intodevelopfrom
feat/affiliate-public-api

Conversation

@NeOMakinG
Copy link
Collaborator

@NeOMakinG NeOMakinG commented Mar 12, 2026

Summary

Align affiliate BPS handling across web, widget, public-api, and affiliate-dashboard.

Changes

packages/public-api

  • Add affiliate BPS lookup from microservices when X-Affiliate-Address header provided
  • Add X-Partner-Code header support for partner code resolution
  • Fallback to default 60 BPS if affiliate not registered
  • Add partnerCode to AffiliateInfo type

packages/swap-widget

  • Add partnerCode prop to SwapWidgetProps
  • Pass X-Partner-Code header in API requests
  • Update both external and internal wallet variants

packages/affiliate-dashboard

  • 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

docs/

  • Add docs/affiliates.md - affiliate program guide
  • Add docs/architecture/affiliate-system.md - system architecture
  • Add docs/architecture/affiliate-data-model.md - data model design

Related

Testing

  • Public API: curl with X-Affiliate-Address header
  • Widget: test with affiliateAddress and partnerCode props
  • Dashboard: wallet connection and stats display

Summary by CodeRabbit

  • New Features

    • Affiliate dashboard launched with wallet sign-in, Overview, Swap History, and Settings (manage codes, BPS, receive address).
    • Swap widget now supports partner codes and optional app URL for attribution and redirects.
    • Added swap status endpoint and one-time registration sync for improved transaction tracking.
  • Documentation

    • Added comprehensive affiliate program, architecture, and data model docs.

Minimoi added 7 commits March 12, 2026 09:28
- 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
@NeOMakinG NeOMakinG requested a review from a team as a code owner March 12, 2026 08:39
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 99a463b4-abb2-461b-b1c3-326ba18a2f07

📥 Commits

Reviewing files that changed from the base of the PR and between aa30bc4 and 7fb8a64.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • packages/swap-widget/package.json

📝 Walkthrough

Walkthrough

Replaces 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 partnerCode/appUrl, migrates local storage to partner-centric keys, and updates docs and OpenAPI.

Changes

Cohort / File(s) Summary
Documentation
docs/affiliates.md, docs/architecture/affiliate-system.md, docs/architecture/affiliate-data-model.md
New affiliate program, architecture, and data-model docs including Prisma schemas, DTOs, API contracts, migrations, validation rules, and examples.
Affiliate Dashboard (new package)
packages/affiliate-dashboard/package.json, packages/affiliate-dashboard/src/...
New dashboard with Wagmi/AppKit setup, React Query, SIWE auth, hooks (config/stats/swaps), main App refactor to wallet-first flow and settings UI.
Dashboard hooks
packages/affiliate-dashboard/src/hooks/useSiweAuth.ts, .../useAffiliateConfig.ts, .../useAffiliateSwaps.ts
New hooks: SIWE auth (signIn/signOut, auth headers), affiliate config fetch, paginated swaps fetch with stale-response guards and detailed error handling.
Wagmi / App wiring
packages/affiliate-dashboard/src/config/wagmi.ts, .../main.tsx, .../App.tsx
Wagmi/AppKit adapter exported, QueryClientProvider + WagmiProvider added, App updated to use wallet auth and hook-driven data.
Public API: partner resolution & docs
packages/public-api/src/middleware/auth.ts, .../types.ts, .../index.ts, .../docs/openapi.ts, .../server-standalone.ts, .../routes/status.ts
Replace X-Affiliate-Address/X-Affiliate-Bps with X-Partner-Code; add resolvePartnerCode middleware that queries partner microservice; update types, OpenAPI/docs, startup messages; remove prior access guard in status route.
Swap Widget: props, client, hooks, docs
packages/swap-widget/src/types/index.ts, .../api/client.ts, .../components/SwapWidget.tsx, .../README.md, .../demo/App.tsx
Widget props change: remove affiliateAddress/affiliateBps, add partnerCode and appUrl; ApiClient uses X-Partner-Code; docs and demo updated; new getSwapStatus client method.
Swap Widget internals
packages/swap-widget/src/hooks/useSwapHandlers.ts, useSwapRates.ts, useStatusPolling.ts, src/utils/redirect.ts, src/services/transactionStatus.ts, src/constants/viemChains.ts
Hook signatures and deps updated to partnerCode/appUrl; status polling accepts apiClient and does one-shot getSwapStatus; centralized VIEM chain lookup and worldchain mapping; redirect builder uses partnerCode/appUrl.
Affiliate tracking & storage
src/hooks/useAffiliateTracking/useAffiliateTracking.ts, src/hooks/useAffiliateTracking/index.ts
Local storage switched to partner-centric keys (address, bps, code, timestamp, TTL); added readStoredPartnerBps/readStoredPartnerCode, resolvePartnerCode network lookup, expiry handling, and export alias for AFFILIATE_STORAGE_KEY.
Fees & trade execution
src/lib/fees/utils.ts, src/lib/tradeExecution.ts
getAffiliateBps reads stored partner BPS (fallback to default); trade execution adds sellAmountUsd calculation and includes partnerCode in swap POST payload.
Public API tests & configs
packages/public-api/tests/*, tsconfig.json, package.json
Tests updated to use TEST_PARTNER_CODE; tsconfig excludes updated; package.json additions for dashboard deps and portless.
Build & package adjustments
packages/swap-widget/package.json, packages/affiliate-dashboard/..., packages/affiliate-dashboard/src/config/wagmi.ts, packages/affiliate-dashboard/src/main.tsx
Dependency and Vite config adjustments: workspace dependency changes, new dashboard dependencies, wagmi adapter export.
Cleanup: beads
.beads/ss-dx5.*.json (multiple deletions)
Removed many backlog bead JSON files (ss-dx5.2 through ss-dx5.21).

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped a code across the net,
Partner codes in place, no fret,
SIWE signed and dashboards bright,
Widgets whisper fees just right,
A rabbit cheers — connections set!

🚥 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 PR title 'feat: affiliate system alignment - public-api, widget, dashboard' accurately summarizes the main changes in the changeset. It clearly indicates the feature focus (affiliate system alignment) and specifies the affected packages/areas, making it clear to reviewers scanning history.
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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/affiliate-public-api
📝 Coding Plan
  • Generate coding plan for human review comments

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
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: 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 | 🟠 Major

Don’t send both affiliate selectors in the same request.

The new x-partner-code header is added independently of x-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 | 🟠 Major

Don't drop X-Partner-Code when X-Affiliate-Address is also present.

The new partner-code branch only runs when !address, and the later req.affiliateInfo assignment never includes partnerCode. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 189e2be and 5d1cd6f.

📒 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.jsonl
  • docs/affiliates.md
  • docs/architecture/affiliate-data-model.md
  • docs/architecture/affiliate-system.md
  • 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/main.tsx
  • packages/public-api/src/middleware/auth.ts
  • packages/public-api/src/types.ts
  • packages/swap-widget/src/api/client.ts
  • packages/swap-widget/src/components/SwapWidget.tsx
  • packages/swap-widget/src/types/index.ts

@NeOMakinG NeOMakinG marked this pull request as draft March 12, 2026 14:19
@NeOMakinG NeOMakinG marked this pull request as ready for review March 12, 2026 14:25
Copy link
Contributor

@gomesalexandre gomesalexandre left a comment

Choose a reason for hiding this comment

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

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 correctpartnerCode prop 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
Copy link
Contributor

@gomesalexandre gomesalexandre left a comment

Choose a reason for hiding this comment

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

✅ 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.warn added for missing WalletConnect projectId ✅
  • useAffiliateConfig now guards against stale responses via requestIdRef

Remaining Architectural Notes (non-blocking)

  • server-standalone.ts still has its own simplified affiliate middleware — intentionally different from auth.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 any usage
  • SwapWidget threading is minimal and correct
  • Docs are thorough (724 lines across 3 files)
  • Dashboard wallet connection UX is clean

Ship it. 🚀

@NeOMakinG NeOMakinG marked this pull request as draft March 12, 2026 20:08
NeOMakinG and others added 10 commits March 13, 2026 18:38
- 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>
@NeOMakinG NeOMakinG marked this pull request as ready for review March 13, 2026 17:44
Copy link
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

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 | 🟠 Major

Update StoredQuote.metadata type definition to match the new relay metadata structure.

The code at lines 498-500 stores relay data as relayTransactionMetadata: { relayId }, but the StoredQuote.metadata type definition in packages/public-api/src/lib/quoteStore.ts (line 22) still expects a flat relayId?: string field. 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 | 🟡 Minor

Keep the standalone server aligned with the new status-registration flow.

The widget now makes a first /v1/swap/status call to bind quoteId to txHash, but this mock server still only documents/exposes /rates and /quote. Pointing the updated widget at server-standalone.ts will 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 } for sellAsset and buyAsset is 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 http or text as 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.md around 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
Copy link
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: 3

♻️ Duplicate comments (1)
packages/affiliate-dashboard/src/App.tsx (1)

232-233: ⚠️ Potential issue | 🟠 Major

0 BPS is still being coerced to 30 during registration.

parseInt(registerBps, 10) || 30 treats a valid 0 as 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

📥 Commits

Reviewing files that changed from the base of the PR and between 15e1311 and b6f3e42.

📒 Files selected for processing (6)
  • packages/affiliate-dashboard/src/App.tsx
  • packages/affiliate-dashboard/src/main.tsx
  • packages/public-api/src/server-standalone.ts
  • packages/swap-widget/src/hooks/useSwapHandlers.ts
  • src/lib/tradeExecution.ts
  • tsconfig.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
Copy link
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: 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.affiliateAddress is a string and data.bps is 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 App component spans ~740 lines (excluding styles), which exceeds the 200-line guideline. Potential candidates for extraction:

  • OverviewTab, SwapsTab, SettingsTab components
  • useAffiliateActions hook encapsulating handleRegister, handleClaimCode, handleUpdateBps, handleUpdateReceiveAddress
  • ConfigBar component for the affiliate config display

This 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

📥 Commits

Reviewing files that changed from the base of the PR and between b6f3e42 and aa30bc4.

📒 Files selected for processing (10)
  • packages/affiliate-dashboard/src/App.tsx
  • packages/affiliate-dashboard/src/hooks/useSiweAuth.ts
  • packages/public-api/src/index.ts
  • packages/public-api/src/middleware/auth.ts
  • packages/public-api/src/server-standalone.ts
  • packages/public-api/tests/smoke-tests.ts
  • packages/public-api/tests/test-config.ts
  • packages/swap-widget/src/hooks/useStatusPolling.ts
  • src/hooks/useAffiliateTracking/useAffiliateTracking.ts
  • tsconfig.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.
@NeOMakinG NeOMakinG merged commit a0f423b into develop Mar 13, 2026
4 checks passed
@NeOMakinG NeOMakinG deleted the feat/affiliate-public-api branch March 13, 2026 21: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