docs: sync from backend 34f542e — Task #7 B unit tests + C protocol + PRD updates
This commit is contained in:
@@ -93,8 +93,126 @@ Five follow-ups scoped for kimi to pick up independently. Full spec in `PRD - Wa
|
||||
- `DERIVED_DESTINATION_MIN_SWEEP_AMOUNT=0`
|
||||
- `DERIVED_DESTINATION_SWEEP_INTERVAL_MS=300000`
|
||||
|
||||
## Task #7 — completion status
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Backend model + HD derivation | ✅ | `DerivedDestination`, `derivedDestinations.ts`, counter-based index |
|
||||
| Sweep service + cron | ✅ | `sweepService.ts`, `build-only`/`hot-key` signers, auto-start in `app.ts` |
|
||||
| Admin API + UI | ✅ | `/api/payment/derived-destinations/*`, admin dashboard page |
|
||||
| RN intent integration | ✅ | `requestNetworkPayInService.ts` passes per-seller destination |
|
||||
| Transaction Safety Provider | ✅ | `resolveExpectedRecipient` checks `metadata.derivedDestination` first |
|
||||
| A — Cart-aware buyer UX | ✅ | `MultiSellerProviderPayment` + `RnMultiCheckoutView` + template checkout wiring |
|
||||
| D — Auto-start sweep cron | ✅ | `app.ts` boots cron when `DERIVED_DESTINATION_SWEEP_AUTOSTART=true` |
|
||||
| E — `recordSweep` accumulation fix | ✅ | `$inc: { totalSwept }` instead of `$setOnInsert` |
|
||||
| F — API docs | ✅ | Derived-destination endpoints added to `Payment API.md` |
|
||||
| B — Unit tests | ✅ | 46 tests across 3 files (see below) |
|
||||
| C — Live divergent-destination probe | 🔄 | Protocol prepared; requires manual execution on dev (see §Live multi-seller probe below) |
|
||||
|
||||
### B — Unit tests (backend@34f542e, 2.6.45)
|
||||
|
||||
**`__tests__/derived-destinations.test.ts`** (26 tests)
|
||||
- `validateXpub` rejects xpriv, tprv, garbage, empty, null/undefined
|
||||
- `deriveAddressAtIndex` is deterministic; different indices → different addresses; checksummed; rejects negative/non-integer
|
||||
- `getDestinationFor` idempotency: same `(buyer, sellerOffer, chainId)` returns same row, counter increments exactly once
|
||||
- `getDestinationFor` E11000 race fallback: simulates concurrent insert, verify second caller re-reads racer's row
|
||||
- `getDestinationFor` non-E11000 errors are re-thrown
|
||||
- `recordSweep` `$inc` accumulation: run twice, assert `totalSwept` equals sum (regression lock-in for item E)
|
||||
- `recordSweep` handles string and negative amounts
|
||||
- `resolveExpectedRecipientForPayment` prefers `metadata.derivedDestination`, falls back to `blockchain.receiver`
|
||||
- `listDerivedDestinations` pagination
|
||||
- `verifyDerivedDestinationConfig` ok / missing xpub / invalid xpub
|
||||
|
||||
**`__tests__/sweep-service.test.ts`** (18 tests)
|
||||
- `getSweepSigner` returns `build-only` by default, `hot-key` when configured
|
||||
- `queryTokenBalance` parses bigint, returns 0n for empty balance, null on RPC failure, null for unsupported chain+token
|
||||
- `sweepDerivedDestinations` skips below-threshold balances, dry-run returns amount without broadcasting, build-only signer returns error without updating record, handles balance query failure, respects `destinationIds` filter, throws when master wallet missing
|
||||
- Cron lifecycle: start/stop/idempotent/zero-interval
|
||||
|
||||
**`__tests__/request-template-orphan-cleanup.test.ts`** (2 tests) — **non-negotiable regression lock-in for Gap 2 fix**
|
||||
- Asserts `Payment.find` during orphan cleanup is scoped to `provider: 'shkeeper'`
|
||||
- Asserts a pending `provider: 'request.network'` Payment is **NOT** deleted when a shkeeper orphan exists for the same buyer
|
||||
- Asserts request.network orphan is untouched even when no shkeeper orphan is present
|
||||
|
||||
### C — Live multi-seller probe protocol
|
||||
|
||||
**Goal:** Prove RN accepts divergent `destinationId` across consecutive `POST /v2/secure-payments` from the same buyer session, and that the multi-checkout cart UX creates two Payments landing on two different derived addresses.
|
||||
|
||||
**Prerequisites:**
|
||||
- Dev backend running ≥ 2.6.45 with `DERIVED_DESTINATION_XPUB` configured
|
||||
- Dev frontend running ≥ 2.6.44
|
||||
- Two seller accounts on `dev.amn.gg` with wallet addresses set
|
||||
- Buyer account with a Rabby/Metamask wallet holding ≥ 0.02 testnet BSC USDC
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Create two template offers** (one from each seller):
|
||||
- Seller A: `https://dev.amn.gg/dashboard/shops/templates/new` → fill title, price 0.01 USDC, publish
|
||||
- Seller B: same, price 0.01 USDC, publish
|
||||
- Capture both `shareableLink` values
|
||||
|
||||
2. **As buyer, add both to cart**:
|
||||
- Visit Seller A's shareable link → Add to cart
|
||||
- Visit Seller B's shareable link → Add to cart
|
||||
- Go to `/dashboard/shops/checkout/?step=2`
|
||||
- Confirm cart shows 2 items from 2 different sellers
|
||||
|
||||
3. **Select crypto payment and proceed**:
|
||||
- Choose "پرداخت با Request Network"
|
||||
- Click the multi-seller button (should show `۲ فروشنده`)
|
||||
- Browser navigates to `/checkout/request-network/multi?session=<id>`
|
||||
|
||||
4. **Pay Seller A** (first checkout page):
|
||||
- Connect wallet
|
||||
- Approve 0.01 USDC for RN proxy
|
||||
- Call `transferFromWithReferenceAndFee`
|
||||
- Wait for "پرداخت تأیید شد ✓"
|
||||
- Click "ادامه به پرداخت بعدی"
|
||||
|
||||
5. **Pay Seller B** (second checkout page):
|
||||
- Approve 0.01 USDC (or reuse allowance if proxy unchanged)
|
||||
- Call `transferFromWithReferenceAndFee`
|
||||
- Wait for confirmation
|
||||
- Click "پایان"
|
||||
|
||||
6. **Capture evidence**:
|
||||
- **Derived addresses:** Check `GET /api/payment/derived-destinations?buyerId=<buyerId>` — expect 2 rows with different `address` and `derivationIndex`
|
||||
- **Tx hashes:** Copy both approve and both pay tx hashes from the UI; verify on `https://testnet.bscscan.com` (or mainnet BscScan if dev uses mainnet)
|
||||
- **Transfer events:** On BscScan, verify both `TransferWithReferenceAndFee` events show different `recipient` addresses
|
||||
- **Webhooks:** Check backend logs for `payment-update` socket emissions or `POST /api/payment/request-network/webhook` handling for both payments
|
||||
- **Payment records:** Query Mongo for the two `Payment` docs — both should have `status: 'completed'` and different `metadata.derivedDestination.address`
|
||||
- **PurchaseRequests:** Verify `convertTemplatesToRequests` created 2 `PurchaseRequest` docs with `status: 'payment'`
|
||||
|
||||
7. **Document**: Paste the full evidence block below under "Live multi-seller probe — execution record".
|
||||
|
||||
---
|
||||
|
||||
### Live multi-seller probe — execution record
|
||||
|
||||
> _To be filled after manual execution of the protocol above._
|
||||
>
|
||||
> | Field | Value |
|
||||
> |-------|-------|
|
||||
> | Date | |
|
||||
> | Buyer ID | |
|
||||
> | Seller A ID / Offer | |
|
||||
> | Seller B ID / Offer | |
|
||||
> | Derived address A | |
|
||||
> | Derived address B | |
|
||||
> | Approve tx A | |
|
||||
> | Pay tx A | |
|
||||
> | Approve tx B | |
|
||||
> | Pay tx B | |
|
||||
> | Payment ID A | |
|
||||
> | Payment ID B | |
|
||||
> | PurchaseRequest ID A | |
|
||||
> | PurchaseRequest ID B | |
|
||||
> | RN webhook fired for both? | |
|
||||
> | Both Payments completed? | |
|
||||
> | Escrow funded for both? | |
|
||||
|
||||
---
|
||||
|
||||
**Remaining in task #7:**
|
||||
1. Cart-aware buyer UX on the in-house checkout (sequential multi-seller approval flow with clear progress UI).
|
||||
2. Unit tests for `derivedDestinations.ts` (idempotency, race handling) and `sweepService.ts`.
|
||||
3. Live probe on dev: confirm RN accepts divergent `destinationId` across consecutive `POST /v2/secure-payments` calls from the same client.
|
||||
4. Optional: auto-start sweep cron on backend boot via `app.ts` (currently manual via admin endpoint).
|
||||
- Item C: execute the live probe protocol above and fill the execution record table.
|
||||
- After C passes: flip PRD §1 acceptance criteria #1 and #2 from ⏳ → ✅ and mark Task #7 done in Taskmaster.
|
||||
|
||||
Reference in New Issue
Block a user