Skip to content

feat(swap-service): affiliate admin wallets - #72

Merged
kaladinlight merged 5 commits into
developfrom
feat/affiliate-members
Oct 15, 2026
Merged

kaladinlight merged 5 commits into
developfrom
feat/affiliate-members

Conversation

@kaladinlight

@kaladinlight kaladinlight commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Description

Lets a partner grant additional wallets admin access to its affiliate config.

  • New affiliate_members table (migration 20261014000000_affiliate_members): each row is one admin wallet of exactly one affiliate. The registering wallet stays on affiliates.wallet_address as the owner and is never a member row, so nothing can remove it through the API.
  • resolveAffiliateByWalletAddress maps a wallet to its affiliate as owner or admin in one query. GET /v1/affiliate/:address and PATCH /v1/affiliate/:address use it, so an admin wallet reads and updates its partner through the existing routes. The config response carries role.
  • New SIWE-gated routes under /v1/affiliate/:address/members (list, add, remove). :address is always the caller's own wallet and must equal the token address; owner and admins both resolve to the one affiliate.
  • A wallet belongs to at most one affiliate across both tables. Registration and add-member run under a per-wallet advisory lock (registration also locks the partner code) so the exclusivity checks cannot race.
  • Every address is normalised to EIP-55 checksum form at the write boundary. A null receiveAddress on PATCH clears the override so payouts fall back to the wallet address, as before. isActive is no longer accepted on PATCH; only the backend deactivates a partner.
  • Registration and add-member reuse the same resolver inside the locked transaction, so the owner-or-member rule is checked in one place and one query. Removal is a single scoped delete.

Reads stay public, as today. Admin parity with the owner (including the receive address) is deliberate.

Pairs with shapeshift/web#12764. Deploy this first; the web side tolerates either order because role is optional there.

Risk

Low. Additive migration, no data backfill. Authorization on every gated route is unchanged in shape (SIWE token must match :address) and now resolves through the members table. Covered by service tests for the resolver, the exclusivity rules, locking, and checksum normalisation.

Testing

  1. yarn db:migrate, then yarn workspace @shapeshift/swap-service test affiliate.
  2. With the dashboard: register as owner, add an admin wallet under Settings → Authorized Wallets, sign in as that wallet and confirm it sees and can update the same partner, then remove it and confirm the row is gone and the wallet gets 404 on config.
  3. Conflicts: adding the owner address, an address that owns another affiliate, or an existing admin returns 409.
  4. Lock: in psql, BEGIN; SELECT pg_advisory_xact_lock(1, hashtext('<admin wallet, lowercase>')); then add that wallet from the dashboard. The request blocks until COMMIT, then completes. Verified locally.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Affiliate owners can add, view, and remove member wallets that can manage the affiliate.
    • Affiliate responses now indicate whether the caller is the owner or an admin.
    • Affiliate payout addresses can be cleared when updating an affiliate.
  • Bug Fixes
    • Affiliate wallet and payout addresses are handled consistently regardless of letter casing.
    • Affiliate creation and member additions reject reserved codes and wallet or code conflicts.

kaladinlight and others added 3 commits October 14, 2026 14:45
…ffiliate config

The registering wallet stays on affiliates.wallet_address as the owner. A new
affiliate_members table holds additional wallets, each an admin of exactly one
affiliate, unique across both tables so address lookups stay unambiguous.

Config reads and writes keyed by :address now resolve through members too, so an
admin wallet reads and updates the partner it belongs to with the existing
routes. Owner-or-admin gated member endpoints list, add and remove admins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ates, resolve wallets in one query

- registration and add-member run under a per-wallet advisory lock so the
  owner/member exclusivity check cannot race; a partner code unique
  violation on insert maps to 409
- isActive is no longer accepted on PATCH; only the backend deactivates a partner
- the service throws Nest exceptions directly instead of the controller
  sniffing error messages
- updateAffiliate takes the resolved affiliate and writes only provided fields
- resolveAffiliateByWalletAddress is a single findFirst over owner and members

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… lookup under the lock

- registration locks both the wallet and the partner code, so the code
  uniqueness check is race-free and the Prisma unique-violation catch is gone
- createAffiliate and addMember reuse resolveAffiliateByWalletAddress inside
  the transaction instead of two point lookups each
- removeMember deletes by wallet and affiliate id in one query
- addedBy comes from the SIWE address; addMember takes an object and returns void
- a null receiveAddress clears the override again, as on develop
- toChecksumAddress dropped for a direct viem getAddress; inputs are DTO-validated
- controller shares the not-found mapping between the public GET and resolveCaller
- tests use an options-object mock builder and assert exception class and message together

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 51bb3bd3-bb13-41b4-890e-7059bb341c56
📥 Commits

Reviewing files that changed from the base of the PR and between bc35176 and e95a3e0.

📒 Files selected for processing: 6
  • apps/swap-service/src/affiliate/__tests__/affiliate.service.test.ts
  • apps/swap-service/src/affiliate/affiliate.controller.ts
  • apps/swap-service/src/affiliate/affiliate.service.ts
  • apps/swap-service/src/affiliate/types.ts
  • prisma/migrations/20261014000000_affiliate_members/migration.sql
  • prisma/schema/swap-service.prisma

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The affiliate service now resolves owner and admin wallets, serializes wallet claims, and manages affiliate members. The schema adds member storage. Authenticated routes list, add, and remove members, and affiliate responses include the caller’s role.

Changes: Affiliate access and membership

Layer / File(s) Summary
Member contract and storage
apps/swap-service/src/affiliate/types.ts, prisma/schema/swap-service.prisma, prisma/migrations/20261014000000_affiliate_members/migration.sql
Affiliate types define owner and admin roles and member response data. The Prisma schema and migration add affiliate members with unique case-insensitive wallet addresses and affiliate relations.
Wallet resolution and affiliate creation
apps/swap-service/src/affiliate/affiliate.service.ts, apps/swap-service/src/affiliate/affiliate.controller.ts, apps/swap-service/src/affiliate/__tests__/affiliate.service.test.ts
The service resolves affiliate owners and members, normalizes addresses, and uses advisory locks for wallet and partner-code claims. Creation rejects reserved codes and conflicting claims. Tests cover creation, resolution, and the related Prisma mocks.
Role-aware affiliate updates
apps/swap-service/src/affiliate/affiliate.service.ts, apps/swap-service/src/affiliate/affiliate.controller.ts, apps/swap-service/src/affiliate/types.ts, apps/swap-service/src/affiliate/__tests__/affiliate.service.test.ts
Affiliate responses include the resolved caller role. Updates operate on the resolved affiliate and update only supplied fields; a null payout address clears the override. Tests check selective updates and address normalization.
Member management routes
apps/swap-service/src/affiliate/affiliate.controller.ts, apps/swap-service/src/affiliate/affiliate.service.ts, apps/swap-service/src/affiliate/__tests__/affiliate.service.test.ts
Authenticated routes list, add, and remove members. Member additions normalize addresses and serialize wallet claims. The service lists the owner before admins and constrains removal to the affiliate. Tests cover member listing, additions, conflicts, and removal.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  actor Caller
  participant AffiliateController
  participant AffiliateService
  participant Prisma
  Caller->>AffiliateController: Submit member wallet with SIWE request
  AffiliateController->>AffiliateService: Resolve caller affiliate
  AffiliateService->>Prisma: Look up owner or member wallet
  AffiliateController->>AffiliateService: Add member with caller as adder
  AffiliateService->>Prisma: Lock wallet claim and insert member
  AffiliateController->>AffiliateService: Get refreshed member list
  AffiliateService->>Prisma: Read affiliate members
  AffiliateController-->>Caller: Return member list
Loading
Merge Risk: ⚪ Minimal · up to e95a3

The affiliate admin wallet feature has no remaining merge-blocking concern in the reviewed changes.

Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely identifies the main change: adding affiliate admin wallet support.
✨ Finishing Touches: 💡 1
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

I’m a rabbit with a wallet to check,
An owner or admin, I know who’s who.
I lock the claim, then add a friend,
The member list comes back in view.
Hop through the routes, all tidy and new.

Comment @coderabbitai help to get the list of available commands.

kaladinlight and others added 2 commits October 14, 2026 16:20
…lasses in affiliate tests

- the members migration is already applied locally, so its header goes back
  to the original text to keep the checksum intact
- Prisma is only used as a type in the service now
- rejection tests assert the exception class as well as the message, and
  the mock reads jest fns from its own typed shape instead of the client

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pg_advisory_xact_lock returns void, which $queryRaw cannot deserialize, so
every locked write failed with P2010. Verified against a local Postgres that
$executeRaw acquires the lock and the dashboard add blocks behind a held one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@kaladinlight
kaladinlight merged commit ea2b85f into develop Oct 15, 2026
2 checks passed
@kaladinlight
kaladinlight deleted the feat/affiliate-members branch October 15, 2026 15:06
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.

1 participant