Repository navigation
feat(swap-service): affiliate admin wallets - #72
Conversation
…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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
…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>
Description
Lets a partner grant additional wallets admin access to its affiliate config.
affiliate_memberstable (migration20261014000000_affiliate_members): each row is one admin wallet of exactly one affiliate. The registering wallet stays onaffiliates.wallet_addressas the owner and is never a member row, so nothing can remove it through the API.resolveAffiliateByWalletAddressmaps a wallet to its affiliate asowneroradminin one query.GET /v1/affiliate/:addressandPATCH /v1/affiliate/:addressuse it, so an admin wallet reads and updates its partner through the existing routes. The config response carriesrole./v1/affiliate/:address/members(list, add, remove).:addressis always the caller's own wallet and must equal the token address; owner and admins both resolve to the one affiliate.receiveAddressonPATCHclears the override so payouts fall back to the wallet address, as before.isActiveis no longer accepted onPATCH; only the backend deactivates a partner.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
roleis 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
yarn db:migrate, thenyarn workspace @shapeshift/swap-service test affiliate.BEGIN; SELECT pg_advisory_xact_lock(1, hashtext('<admin wallet, lowercase>'));then add that wallet from the dashboard. The request blocks untilCOMMIT, then completes. Verified locally.🤖 Generated with Claude Code
Summary by CodeRabbit