From ddc043481922c99b1a4fb038a872a6d0f763b411 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Thu, 28 May 2026 20:35:38 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20sync=20from=20backend=2019f7eb9,=20fron?= =?UTF-8?q?tend=2060ee6fb=20=E2=80=94=20Task=20#10=20AML=20screening?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 00 - Overview/Glossary.md | 18 +- 00 - Overview/Introduction.md | 8 +- 00 - Overview/Roles & Personas.md | 12 +- 00 - Overview/System Overview.md | 48 +-- 00 - Overview/Tech Stack.md | 13 +- 01 - Architecture/Backend Architecture.md | 39 ++- 01 - Architecture/Infrastructure.md | 2 +- 01 - Architecture/Real-time Layer.md | 2 +- ...Request Network Integration Constraints.md | 63 ++-- 01 - Architecture/Security Architecture.md | 40 +-- 01 - Architecture/System Architecture.md | 28 +- 02 - Data Models/Data Model Overview.md | 30 +- 02 - Data Models/Payment.md | 5 +- 03 - API Reference/API Overview.md | 12 +- 04 - Flows/Chat Flow.md | 2 +- 04 - Flows/Delivery Confirmation Flow.md | 4 +- 04 - Flows/Dispute Flow.md | 26 +- 04 - Flows/Escrow Flow.md | 301 ++++++++++-------- 04 - Flows/Negotiation Flow.md | 8 +- 04 - Flows/Payment Flow - DePay & Web3.md | 14 +- 04 - Flows/Payment Flow - SHKeeper.md | 3 + 04 - Flows/Payout Flow.md | 183 ++++++----- 04 - Flows/Purchase Request Flow.md | 6 +- 04 - Flows/Referral Flow.md | 2 +- 04 - Flows/Seller Offer Flow.md | 18 +- 04 - Flows/Trezor Safekeeping Flow.md | 8 +- 06 - Usage/Admin Guide.md | 12 +- 06 - Usage/Support Guide.md | 2 +- 08 - Operations/Incident Response.md | 27 +- 08 - Operations/Monitoring.md | 4 +- .../Payment and Trezor Verification Report.md | 4 +- 09 - Audits/Activity Log.md | 12 + ...stody and Smart-Contract Escrow Roadmap.md | 195 ++++++++++++ README.md | 11 +- 34 files changed, 709 insertions(+), 453 deletions(-) create mode 100644 PRD - Decentralized Custody and Smart-Contract Escrow Roadmap.md diff --git a/00 - Overview/Glossary.md b/00 - Overview/Glossary.md index fb89285..1d61477 100644 --- a/00 - Overview/Glossary.md +++ b/00 - Overview/Glossary.md @@ -44,17 +44,17 @@ created: 2026-05-23 ### Dispute > [!info] Definition -> A formal complaint opened by either party when a deal goes wrong. Would create a three-way chat (buyer, seller, admin) and a `Dispute` document with a structured `timeline[]`, `evidence[]`, and `resolution`. Categories: `product_quality | delivery_delay | wrong_item | payment_issue | seller_behavior | other`. Outcomes: `refund | replacement | compensation | warning_seller | ban_seller | no_action`. See `backend/src/models/Dispute.ts` *(planned, not yet implemented)*. +> A formal complaint opened by either party when a deal goes wrong. Creates a three-way chat (buyer, seller, admin) and a `Dispute` document with a structured `timeline[]`, `evidence[]`, and `resolution`. Categories: `product_quality | delivery_delay | wrong_item | payment_issue | seller_behavior | other`. Outcomes in the current model: `refund | replacement | compensation | warning_seller | ban_seller | no_action`. See `backend/src/models/Dispute.ts` and [[Dispute Flow]]. ### Escrow > [!info] Definition -> The custodial period during which buyer funds are held by the platform (SHKeeper or the smart contract layer) after payment but before release to the seller. Escrow guarantees the seller will be paid if they deliver, and guarantees the buyer can be refunded if they do not. The defining feature of Amn. +> The custodial period during which buyer funds are held by platform-controlled custody infrastructure after payment but before release to the seller. The current primary path uses Request Network pay-in, per-payment derived destinations, transaction-safety checks, and an internal funds ledger. Future custody decentralization is tracked in [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. ### Idempotency > [!info] Definition -> The property that the same request (identified by an idempotency key) can safely be retried without performing the underlying operation more than once. Critical for payment webhooks — SHKeeper may deliver the same webhook several times if it does not receive a 200 quickly. Amn enforces idempotency in `PaymentCoordinator` and at the model level via unique constraints on transaction hashes. +> The property that the same request (identified by an idempotency key) can safely be retried without performing the underlying operation more than once. Critical for payment webhooks and release/refund confirmations. Amn enforces idempotency in `PaymentCoordinator`, Request Network delivery handling, pending-intent indexes, and ledger idempotency keys. ### JWT (Access / Refresh) @@ -89,12 +89,12 @@ created: 2026-05-23 ### Pay-in > [!info] Definition -> Money flowing **into** escrow from the buyer. Recorded as `Payment.direction: "in"`. The buyer's choice of pay-in surface (SHKeeper invoice vs. Web3 wallet) is independent of how the payout will be sent. +> Money flowing **into** escrow from the buyer. Recorded as `Payment.direction: "in"`. The current primary path is Request Network in-house checkout, with payment safety verified by webhook/reconciliation plus on-chain transaction checks. ### Pay-in Intent > [!info] Definition -> The pre-authorisation record created when a buyer commits to paying but the funds have not yet arrived on-chain. Holds the chosen amount, currency, expected wallet address (SHKeeper) or counterparty (DePay), and an expiry. Becomes a confirmed `Payment` once the chain or webhook confirms settlement. +> The pre-authorisation record created when a buyer commits to paying but the funds have not yet arrived on-chain. Holds amount, currency, Request Network IDs/payment reference, in-house checkout metadata, and expected destination. Becomes a confirmed `Payment` only after webhook/reconciliation and transaction-safety checks approve settlement. ### Payment @@ -109,7 +109,7 @@ created: 2026-05-23 ### Payout > [!info] Definition -> Money flowing **out** of escrow to the seller's wallet. Recorded as `Payment.direction: "out"`. Triggered by admin action after delivery is confirmed; implemented via SHKeeper's payout API (`shkeeperPayoutService.ts`). +> Money flowing **out** of escrow to the seller's wallet. Triggered by release/refund orchestration after delivery confirmation or dispute resolution. The roadmap moves execution authority to Safe multisig/hardware signers before any custom smart-contract escrow pilot. ### Points @@ -174,7 +174,7 @@ created: 2026-05-23 ### SHKeeper > [!info] Definition -> A self-hosted crypto payment processor used as Amn's primary custodial pay-in / payout rail. Issues a fresh wallet address per invoice, watches the chain for incoming USDT, and emits a signed webhook on settlement. Lives at `https://pay.amn.gg` per `backend/TODO.md`. Integration code under `backend/src/services/payment/shkeeper/`. +> A self-hosted crypto payment processor used by older Amanat payment designs. Its docs remain for migration and historical context, but the current backend payment tree has moved to Request Network as the primary provider. ### Socket Room @@ -194,12 +194,12 @@ created: 2026-05-23 ### USDT / USDC > [!info] Definition -> The two stablecoins Amn supports out of the box for pay-in and payout. USDT is the default for SHKeeper invoices; both are supported in offer pricing (`SellerOffer.price.currency` enum: `USD | EUR | IRR | USDT | USDC`). +> The two stablecoins Amn supports out of the box for pay-in and payout. Request Network token registry work covers USDC/USDT across supported EVM chains; both are also supported in offer pricing (`SellerOffer.price.currency` enum: `USD | EUR | IRR | USDT | USDC`). ### Webhook > [!info] Definition -> An inbound HTTP POST from an external service notifying Amn of an event. SHKeeper webhooks (`/api/payment/shkeeper/webhook`) are the most important — they confirm pay-ins. All webhooks are HMAC-signed; verification uses `SHKEEPER_WEBHOOK_SECRET`. Failed verifications are dropped. +> An inbound HTTP POST from an external service notifying Amn of an event. The primary payment webhook is Request Network at `/api/payment/request-network/webhook`, signed with `x-request-network-signature`. Roadmap work puts durable ingress/replay in front of the backend while keeping backend signature verification and transaction-safety checks as the trust boundary. ### WalletConnect diff --git a/00 - Overview/Introduction.md b/00 - Overview/Introduction.md index d9c166b..a29cb6c 100644 --- a/00 - Overview/Introduction.md +++ b/00 - Overview/Introduction.md @@ -11,7 +11,7 @@ created: 2026-05-23 > - **Passkeys hardened** — challenge consumption is now single-use with immediate deletion, 5-minute expiry, and replay-attack protection. > - **Web3 verification real** — `BSCTransactionVerifier` performs on-chain `eth_getTransactionReceipt` validation with confirmation counting. > - **Socket.IO auth enforced** — all socket connections require a valid JWT; room joins enforce strict ownership/participation checks. -> - **Dispute holds** documented as planned but not yet implemented; the `Dispute` model, service layer, and API routes do not exist in the current backend. +> - **Dispute holds** now exist in the backend through the dispute/release-hold service; remaining work is canonical state-machine alignment and stronger release/refund policy enforcement. > - **Data model docs aligned** with actual Mongoose schemas (Payment provider/escrowState enums, User model omissions documented). # Introduction @@ -34,7 +34,7 @@ Traditional marketplaces tend to live at one of two extremes: 1. **Fully custodial platforms** (Amazon, eBay, Fiverr) take a large cut, dictate every term of the transaction, and freeze funds on a whim. They work, but they are expensive and opaque. 2. **Free-form P2P channels** (Telegram groups, Discord servers, direct DMs) charge nothing but offer no protection at all. The first scam empties the wallet and there is no recourse. -Amn sits between the two. It charges a thin escrow margin, holds funds for only as long as it takes to confirm delivery, and supports both fiat-style stablecoin escrow (via [[SHKeeper]]) and direct on-chain settlement (via [[DePay]] and the user's own wallet) — meaning the buyer can keep custody of their crypto until the literal moment of release. +Amn sits between the two. It charges a thin escrow margin, holds funds for only as long as it takes to confirm delivery, and now routes primary stablecoin pay-in through Request Network with an Amanat-rendered wallet checkout. The buyer keeps custody of their crypto until they sign the on-chain payment, while the platform keeps settlement, safety checks, and dispute resolution in one auditable flow. > [!tip] Why "crypto-native"? > The escrow rails are built around stablecoins (USDT/USDC) on EVM chains rather than card networks. That means no chargebacks, no 3-day settlement, no geographic restrictions — and a transparent, auditable transaction trail for every step of the deal. See [[Tech Stack]] for the full Web3 surface. @@ -56,7 +56,7 @@ Beyond the four roles, two ambient audiences read the platform: A handful of design choices set Amn apart from generic marketplace software: -1. **Dual payment rails.** Every order can be paid through SHKeeper (a self-hosted crypto payment processor that issues a fresh wallet per invoice) *or* through a Web3 wallet connect flow (DePay + Wagmi/Viem + MetaMask). The buyer picks; the escrow logic is identical downstream. See [[Payments Overview]]. +1. **Request Network in-house checkout.** Every order can be paid through an Amanat-rendered Web3 checkout that builds Request Network-compatible transactions directly in the buyer's wallet. The hosted Request Network page remains a fallback, while the app keeps Rabby/MetaMask UX, chain choice, transaction safety checks, and escrow state in-house. 2. **Request-first marketplace.** Most platforms list *products*. Amn lists *needs*. Buyers describe what they want and let the market come to them — closer to a reverse auction than a catalogue. The unidirectional flow eliminates the "thousand-listings-with-no-stock" problem. 3. **Request Templates.** Power buyers (and admins) can publish reusable purchase request templates that act like express checkouts — a buyer clicks "I want this" and the order is opened pre-filled. Templates are the bridge between Amn and conventional ecommerce. 4. **First-class i18n with RTL.** The frontend ships with six locales out of the box (English, French, Vietnamese, Chinese, Arabic, Persian) and full right-to-left support — Persian is the default fallback. See `frontend/src/locales/locales-config.ts:36`. @@ -78,4 +78,4 @@ A handful of design choices set Amn apart from generic marketplace software: ## Project status at a glance -Amn is at version **2.6.x** across both repositories, on the `development` branch, and tagged "production-ready with minor enhancements" by the project leads. The core escrow loop, real-time chat, multi-language UI, dispute system, points programme, and blog are all live. Active work focuses on UX polish, admin analytics, and a more granular permissions matrix — see `backend/TODO.md` and `frontend/VERSION_0_PREPARATION_TODO.md` for the rolling task list, and [[Roadmap]] (forthcoming) for the strategic view. +Amn is at version **2.6.x** across both repositories, on the `development` branch, and tagged "production-ready with minor enhancements" by the project leads. The core escrow loop, real-time chat, multi-language UI, dispute system, points programme, and blog are all live. Active work focuses on Request Network hardening, durable webhook ingress, derived-destination custody, admin signing, and a more granular permissions matrix. The custody/smart-contract strategy lives in [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. diff --git a/00 - Overview/Roles & Personas.md b/00 - Overview/Roles & Personas.md index ba9e8bc..54c60b5 100644 --- a/00 - Overview/Roles & Personas.md +++ b/00 - Overview/Roles & Personas.md @@ -37,7 +37,7 @@ flowchart LR - **Browse and search** the public marketplace and request templates. - **Create a [[Purchase Request]]** describing what they want — product type (physical / digital / service / consultation), budget, urgency, delivery info, attachments. See `backend/src/models/PurchaseRequest.ts`. - **Review incoming [[Seller Offer]]s**, negotiate over chat, accept the best one. -- **Pay** via [[SHKeeper]] (custodial crypto invoice) or Web3 wallet ([[DePay]] + MetaMask through Wagmi). +- **Pay** via the Request Network in-house checkout, using a supported EVM wallet through Wagmi/WalletConnect and the platform's payment request metadata. - **Track the order** through `processing → delivery → delivered → confirming → completed` states. - **Confirm receipt** (or let the SLA auto-confirm), leave a review, accrue points. - **Open a [[Dispute]]** if delivery never lands, item is wrong, or quality is poor. @@ -86,7 +86,7 @@ The buyer dashboard lives under `/dashboard` (`frontend/src/app/dashboard/`). No - **Negotiate** in the per-request chat — bilateral with the buyer until an offer is accepted. - **Fulfil** the order: ship physical goods (with optional tracking number), or upload/email digital deliverables. - **Use the [[delivery code]]** for physical handoffs: a six-digit one-time code the buyer reads to the courier to confirm receipt. -- **Receive payout** automatically via SHKeeper to the configured wallet once the order is finalised (admin-triggered batch or per-order based on shop policy). +- **Receive payout** to the configured wallet after ledger-gated release. Today this is an admin/custody-signer operation; the target path is Safe/hardware-backed approvals as described in [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. - **Manage [[Request Templates]]** scoped to their shop — publish "off-the-shelf" offerings buyers can purchase in one click. - **Engage with reviews and disputes**: respond to reviews, contest disputes, provide evidence. @@ -125,12 +125,12 @@ Seller dashboard reuses the same `/dashboard` shell with extra modules: - **Moderate users**: suspend / unsuspend accounts (`User.status: "active" | "suspended" | "deleted"`, see `backend/src/models/User.ts`), promote buyers to sellers, ban repeat offenders. - **Moderate marketplace content**: categories (`Category` model), request templates (the canonical platform-wide ones), blog posts. -- **Resolve disputes**: get assigned to disputes, drive them to resolution, choose an outcome (`refund | replacement | compensation | warning_seller | ban_seller | no_action`). See `backend/src/services/dispute/DisputeService.ts` *(planned, not yet implemented)*. -- **Operate payments**: trigger payouts, fetch on-chain transactions, manually confirm stuck payments (the manual transaction-hash flow described in `backend/TODO.md`), audit the SHKeeper webhook history (`services/payment/shkeeper/webhookStats.ts`). +- **Resolve disputes**: get assigned to disputes, drive them to resolution, choose an outcome (`refund | replacement | compensation | warning_seller | ban_seller | no_action`). See `backend/src/services/dispute/DisputeService.ts` and [[Dispute Flow]]. +- **Operate payments**: trigger ledger-gated releases/refunds, review Request Network webhooks, inspect derived destination wallets, fetch on-chain transactions, and manually confirm stuck payments only after Transaction Safety Provider checks. - **Configure the platform**: levels (`LevelConfig`), points multipliers, blog seed content, default templates. - **Run data cleanup**: `/api/admin/cleanup` exposes destructive maintenance utilities (`services/admin/`). - **Author blog posts** via the TipTap rich-text editor. -- **Monitor health**: SHKeeper status (background health monitor in `app.ts:433`), Redis, MongoDB. +- **Monitor health**: Request Network webhook/reconciliation status, ledger enforcement, custody signer/Safe readiness, Redis, and MongoDB. ### Key permissions @@ -149,7 +149,7 @@ Admins see the buyer/seller surfaces plus dedicated admin modules (typically und - User management (search, suspend, role change) - Dispute queue with assignment and resolution -- Payment console (manual confirmation, payout dispatch, webhook log) +- Payment console (manual confirmation, release/refund dispatch, Request Network webhook and ledger log) - Category and template management - Blog editor (publish / unpublish / featured) - Platform analytics (TODO — see `backend/TODO.md`) diff --git a/00 - Overview/System Overview.md b/00 - Overview/System Overview.md index 4a03347..d359439 100644 --- a/00 - Overview/System Overview.md +++ b/00 - Overview/System Overview.md @@ -16,7 +16,7 @@ Amn is a **two-repo system**: - **Frontend** (`/Users/mojtabaheidari/code/frontend`) — a Next.js 16 App Router application that serves the marketplace UI, the admin dashboard, the public blog, and the user-facing Web3 wallet flow. - **Backend** (`/Users/mojtabaheidari/code/backend`) — an Express 5 + TypeScript API server that owns all business logic, persists to MongoDB, caches in Redis, and brokers all external integrations. -The two repos are deployable independently. They communicate over **HTTPS (REST)** for stateful actions and over **WebSocket (Socket.IO)** for live updates. The frontend never talks directly to MongoDB, Redis, SHKeeper, or OpenAI — every external interaction is mediated by the backend so that secrets stay on the server. +The two repos are deployable independently. They communicate over **HTTPS (REST)** for stateful actions and over **WebSocket (Socket.IO)** for live updates. The frontend never talks directly to MongoDB, Redis, Request Network API keys, OpenAI, or admin custody secrets -- every sensitive external interaction is mediated by the backend so that secrets stay on the server. ## System map @@ -41,7 +41,7 @@ flowchart TB Auth["Auth service
JWT + Passkey + Google + Telegram"] Market["Marketplace service
Requests, Offers, Templates"] ChatSvc["Chat service"] - PaySvc["Payment service
SHKeeper + Request Network + ledger"] + PaySvc["Payment service
Request Network + ledger + custody controls"] TelegramSvc["Telegram service
bot + Mini App + notifications"] Disp["Dispute service"] Points["Points / Referrals"] @@ -58,8 +58,6 @@ flowchart TB end subgraph External["External services"] - SHK["SHKeeper
crypto invoicing"] - DePay["DePay widget"] Chain["EVM chains
BSC / ETH / Polygon"] SMTP["SMTP
(nodemailer)"] OpenAI["OpenAI API"] @@ -68,6 +66,7 @@ flowchart TB Alchemy["Alchemy RPC"] TelegramAPI["Telegram Bot API
+ Mini App"] ReqNet["Request Network
pay-in / webhooks"] + CFWorker["Durable webhook ingress
(roadmap)"] end Browser --> SSR @@ -88,13 +87,10 @@ flowchart TB Auth & PaySvc & Notif --> RedisDB Files --> Disk - PaySvc <--> SHK - SHK -.webhook.-> PaySvc PaySvc <--> ReqNet - ReqNet -.webhook.-> PaySvc + ReqNet -.webhook.-> CFWorker + CFWorker -.forward/replay.-> PaySvc PaySvc --> Chain - Wagmi --> DePay - DePay --> Chain PaySvc -.tx fetch.-> Alchemy TelegramSvc <--> TelegramAPI @@ -130,16 +126,17 @@ The heart of the platform. Three first-class models drive it: Services live in `backend/src/services/marketplace/` and are exposed through `/api/marketplace/*`. The frontend uses a mix of React Query (`@tanstack/react-query`) and SWR for data fetching, with mutations gated through the actions layer in `frontend/src/actions/`. -### Payments — [[Payments Overview]] / [[SHKeeper Integration]] +### Payments -- Request Network, Ledger, And Custody Controls -Payments are where Amn is most distinctive. The backend supports **four payment surfaces** routed through a common `Payment` model (`backend/src/models/Payment.ts`) via a provider-neutral adapter layer (`backend/src/services/payment/adapters/`): +Payments are where Amn is most distinctive. The live backend has converged on **Request Network** as the primary provider through a common `Payment` model (`backend/src/models/Payment.ts`) and provider-neutral adapter layer (`backend/src/services/payment/adapters/`): -- **SHKeeper** — `/api/payment/shkeeper`. Issues a fresh wallet address per invoice, polls / webhooks for payment confirmation, and runs through `PaymentCoordinator` to avoid race conditions. Health is monitored in the background (`shkeeperHealthCheck.ts`). -- **Request Network** — `/api/payment/request-network`. Creates on-chain payment requests via the Request Network protocol, generates Secure Payment Page URLs for the buyer, and receives real-time payment status via signed webhooks (`x-request-network-signature`). Pay-in service: `requestNetworkPayInService.ts`; reconciliation: `requestNetworkReconciliationService.ts`. -- **Decentralized (Wagmi + DePay)** — `/api/payment/decentralized`. The user signs and sends the transfer from their own wallet; the backend verifies on-chain via `blockchainTxFetcher.ts` and the Alchemy SDK. -- **Payout** — `/api/payment/shkeeper/payout`. Admin-triggered release of escrow funds to the seller's wallet once delivery is confirmed. +- **Request Network pay-in** -- `/api/payment/request-network`. Creates requests, exposes the Amanat in-house checkout block, and receives signed webhooks (`x-request-network-signature`). Pay-in service: `requestNetworkPayInService.ts`; reconciliation: `requestNetworkReconciliationService.ts`. +- **In-house wallet checkout** -- buyer signs the RN-compatible `approve` + `transferFromWithReferenceAndFee` flow from their own wallet, so Rabby/MetaMask wallet UX stays inside Amanat. +- **Derived destination wallets** -- `/api/payment/derived-destinations` admin endpoints manage per-`(buyer, sellerOffer, chainId)` receiving addresses, sweep status, and config health. +- **Funds ledger** -- `backend/src/services/payment/ledger/` tracks payment detection, holds, releases, refunds, fees, and adjustments independently of provider metadata. +- **Release/refund orchestration** -- `/api/payment/:id/(release|refund)` builds instructions; `/confirm` records confirmed transaction hashes. Optional Trezor enforcement gates confirmation when `TREZOR_SAFEKEEPING_REQUIRED=true`. -All surfaces converge on the same `Payment` record (with `direction: 'in' | 'out' | 'refund'`) and share the internal **funds ledger** (`backend/src/services/payment/ledger/`) which tracks available / held / releasable amounts independently of the provider. **Pending payments are auto-cleaned** by a background timer started in `app.ts`. +Historical SHKeeper and DePay docs remain in the vault for migration context, but the current backend tree no longer has `backend/src/services/payment/shkeeper/`. The current strategic path is in [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. ### Real-time chat — [[Chat System]] @@ -164,9 +161,10 @@ Push and SMS are tracked as **planned** in `backend/TODO.md`. ### Disputes — [[Dispute System]] -When a deal goes wrong (see [[Glossary#Dispute]]), either party can open a dispute. The backend would create a **three-way chat** between buyer, seller, and admin, open a `Dispute` document with a structured `timeline[]` and `evidence[]`, and assign the dispute to an admin via `assignAdmin()`. Resolution can be `refund | replacement | compensation | warning_seller | ban_seller | no_action` and is recorded on the dispute itself. -> [!warning] Not implemented -> `backend/src/services/dispute/DisputeService.ts` does not exist as of 2026-05-24. +When a deal goes wrong (see [[Glossary#Dispute]]), either party can open a dispute. The backend creates a **three-way chat** between buyer, seller, and admin, opens a `Dispute` document with a structured `timeline[]` and `evidence[]`, and can assign the dispute to an admin via `assignAdmin()`. Resolution can be `refund | replacement | compensation | warning_seller | ban_seller | no_action` in the current Mongoose model. + +> [!note] State alignment gap +> The dispute module exists now, but its model still uses the legacy `pending | in_progress | resolved | ...` enum. [[Funds Ledger and Escrow State Machine Specification]] defines the canonical future enum and financial side effects. ### Points & referrals — [[Points System]] @@ -191,9 +189,10 @@ OpenAI (model configurable per call) is exposed through `/api/ai/*`. The current - locks used by `PaymentCoordinator` to serialise status transitions - rate-limit counters (currently disabled in code but plumbed in) -**Background workers** run inside the Express process for now — no separate worker tier. Notable timers: +**Background workers** run inside the Express process for now -- no separate worker tier. Notable timers: - `startPendingPaymentsCleanup()` — sweeps stale unpaid invoices -- `startShkeeperHealthMonitor()` — pings the SHKeeper instance and surfaces alerts +- optional derived-destination sweep cron — sweeps eligible per-payment receiving addresses when configured +- Request Network reconciliation — enabled via provider config when the rollout requires fallback status repair - Auto-seed logic on startup (gated by `NODE_ENV` and `AUTO_SEED_ON_START`) ## Request lifecycle (the happy path) @@ -203,9 +202,10 @@ OpenAI (model configurable per call) is exposed through `/api/ai/*`. The current > 2. Buyer creates a [[Purchase Request]] → `POST /api/marketplace/requests`. The request lands in `pending`/`active`. Sellers in the matching category receive a Socket.IO notification. > 3. Seller views the request, opens [[Seller Offer]] modal, submits price + delivery time → `POST /api/marketplace/offers`. Buyer sees the offer arrive live. > 4. Buyer accepts an offer → request moves to `payment`. UI opens the payment selector. -> 5. Buyer picks **SHKeeper** → backend creates a SHKeeper invoice, returns a wallet address + QR code. Buyer pays. SHKeeper webhook hits `/api/payment/shkeeper/webhook`; `PaymentCoordinator` flips `Payment.status = paid` and `PurchaseRequest.status = processing`. -> 6. Seller ships. Buyer confirms delivery (or it auto-confirms after the SLA window). Admin triggers (or schedules) a **payout** → SHKeeper releases USDT to the seller's wallet. -> 7. Both parties leave reviews. Points are awarded. The deal is closed. +> 5. Buyer picks **Request Network** -> backend creates a Payment and RN intent, returns an in-house checkout block, and the buyer signs the on-chain payment from their wallet. +> 6. Request Network webhook/reconciliation plus the Transaction Safety Provider confirm tx hash, recipient, token, amount, and confirmations before the backend marks escrow funded. +> 7. Seller ships. Buyer confirms delivery (or an admin resolves the order/dispute). Admin/custody owners execute release/refund through the release/refund instruction flow. +> 8. Both parties leave reviews. Points are awarded. The deal is closed. > > If the buyer disputes the delivery, jump to step 7 of the [[Dispute Flow]] instead. diff --git a/00 - Overview/Tech Stack.md b/00 - Overview/Tech Stack.md index bfbb82d..4320216 100644 --- a/00 - Overview/Tech Stack.md +++ b/00 - Overview/Tech Stack.md @@ -117,7 +117,7 @@ The frontend is a Next.js 16 App Router application written in TypeScript. The b ## Backend stack -The backend is `amn-backend@2.6.3-beta`, an Express 5 server in TypeScript backed by MongoDB (Mongoose), Redis, and Socket.IO. It owns all integrations with SHKeeper, the EVM chains, OpenAI, Google OAuth, and SMTP. +The backend is `amn-backend@2.6.3-beta`, an Express 5 server in TypeScript backed by MongoDB (Mongoose), Redis, and Socket.IO. It owns all integrations with Request Network, EVM chains, OpenAI, Google OAuth, Telegram, SMTP, and custody/signing controls. ### Core runtime & framework @@ -135,7 +135,7 @@ The backend is `amn-backend@2.6.3-beta`, an Express 5 server in TypeScript backe | sharp | ^0.34.3 | Image resizing / format conversion | Upload pipeline | | dotenv | ^17.2.0 | Env var loader | Bootstrap | | uuid | ^11.1.0 | ID generation | Tokens, ephemeral IDs | -| axios | ^1.11.0 | Outbound HTTP (SHKeeper, blockchain) | Integration calls | +| axios | ^1.11.0 | Outbound HTTP (Request Network, blockchain/RPC helpers) | Integration calls | | @babel/runtime | ^7.27.6 | Babel runtime helpers | Compiled output | > [!warning] React in backend dependencies @@ -210,9 +210,12 @@ The backend is `amn-backend@2.6.3-beta`, an Express 5 server in TypeScript backe | Service | Purpose | Touchpoint in code | |---|---|---| -| **SHKeeper** | Self-hosted crypto payment processor — issues wallets, watches for incoming USDT, pays out | `backend/src/services/payment/shkeeper/` | -| **Request Network** | On-chain payment request protocol — creates invoices, generates Secure Payment Pages, signs webhooks | `backend/src/services/payment/requestNetwork/` + adapters | -| **DePay** | Drop-in Web3 widget for wallet-to-wallet payment | `@depay/widgets` on frontend | +| **Request Network** | On-chain payment request protocol -- creates payment requests, supports in-house checkout metadata, signs webhooks | `backend/src/services/payment/requestNetwork/` + adapters | +| **Derived destination wallets** | Per-`(buyer, sellerOffer, chainId)` receiving addresses plus sweep orchestration | `backend/src/services/payment/wallets/` | +| **Transaction Safety Provider** | Confirms tx hash, recipient, token, amount, confirmation depth, and future AML result before escrow credit | `backend/src/services/payment/safety/` | +| **Trezor / future Safe multisig** | Hardware-backed admin signing today; Safe multisig target in custody roadmap | `backend/src/services/trezor/`, [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] | +| **SHKeeper** | Historical payment rail retained in documentation for migration context | legacy docs only | +| **DePay** | Historical/drop-in Web3 widget docs retained for context | frontend historical docs | | **EVM chains** (BSC, Ethereum mainnet, Sepolia, Polygon) | Settlement layer for stablecoin transfers | `frontend/src/web3/config.ts`, backend `blockchain/` | | **Alchemy RPC** | Hosted EVM RPC + transaction lookup | Frontend `alchemy-sdk`, backend `blockchainTxFetcher.ts` | | **MetaMask / WalletConnect** | Wallet connectors via Wagmi | `web3/config.ts` (WalletConnect commented out pending SSR fix) | diff --git a/01 - Architecture/Backend Architecture.md b/01 - Architecture/Backend Architecture.md index 74d7bc6..cc7e104 100644 --- a/01 - Architecture/Backend Architecture.md +++ b/01 - Architecture/Backend Architecture.md @@ -44,7 +44,8 @@ backend/src/ │ │ ├── migration/ # Legacy data backfill utilities │ │ ├── observability/ # Logging and incident controls │ │ ├── requestNetwork/ # Request Network pay-in, routes, webhook signature -│ │ └── shkeeper/ # SHKeeper API, webhook, payout +│ │ ├── safety/ # Transaction Safety Provider + confirmation thresholds +│ │ └── wallets/ # Derived destination wallets + sweep orchestration │ ├── points/ # Loyalty points, levels, redemption │ ├── redis/ # Redis client, cache helpers │ ├── telegram/ # Bot webhook, Mini App session, identity linking, notifications @@ -125,17 +126,19 @@ The full route table mounted by `app.ts`: | `/api/marketplace/categories` | `services/marketplace/controllerRoutes.ts` | public read | Category list | | `/api/marketplace/shop-settings` | `services/marketplace/shopSettingsController.ts` | JWT (seller) | Shop profile | | `/api/payment` | `services/payment/paymentControllerRoutes.ts` + `paymentRoutes.ts` | JWT | Payment CRUD, health, export | -| `/api/payment/decentralized` | `services/payment/decentralizedPaymentRoutes.ts` | mixed | Web3 save, verify, receiver | -| `/api/payment/shkeeper` | `services/payment/shkeeper/shkeeperRoutes.ts` | mixed | Intents, webhook, release, refund, config | -| `/api/payment/shkeeper/payout` | `services/payment/shkeeper/shkeeperPayoutRoutes.ts` | JWT (seller/admin) | Withdraw to wallet | -| `/api/payment/request-network` | `services/payment/requestNetwork/requestNetworkRoutes.ts` | HMAC sig | Request Network pay-in creation, Secure Payment Page, webhooks | +| `/api/payment/decentralized` | `services/payment/decentralizedPaymentRoutes.ts` | mixed | Legacy/manual Web3 save, verify, receiver | +| `/api/payment/request-network` | `services/payment/requestNetwork/requestNetworkRoutes.ts` | mixed + HMAC sig on webhook | Request Network pay-in creation, in-house checkout rehydrate, webhooks | +| `/api/payment/derived-destinations` | `services/payment/wallets/derivedDestinationRoutes.ts` | JWT (admin) | Derived address list, sweeps, cron, config health | +| `/api/admin/rn/networks` | `services/payment/requestNetwork/networkRegistryRoutes.ts` | JWT (admin) | Supported RN chain/token registry | +| `/api/admin/settings/confirmation-thresholds` | `services/admin/confirmationThresholdRoutes.ts` | JWT (admin) | Runtime min-confirmation thresholds | +| `/api/admin/payments/awaiting-confirmation` | `services/admin/awaitingConfirmationRoutes.ts` | JWT (admin) | Payments blocked on safety confirmations | | `/api/telegram` | `services/telegram/telegramRoutes.ts` | mixed (some JWT, webhook uses secret-token) | Mini App verify/session, identity link/unlink, bot webhook | | `/api/chat` | `services/chat/chatRoutes.ts` | JWT | Conversations, messages | | `/api/notification` | `services/notification/notificationRoutes.ts` + `notificationControllerRouter` | JWT | List, mark read | -| `/api/dispute` | `services/dispute/disputeRoutes.ts` | JWT | **Not implemented** — planned | -| `/api/blog` | `services/blog/blogRoutes.ts` | mixed | **Not implemented** — planned | -| `/api/admin` | `services/admin/adminRoutes.ts` | JWT (admin) | **Not implemented** — planned | -| `/api/points` | `services/points/pointsRoutes.ts` | JWT | **Not implemented** — planned | +| `/api/disputes` | `routes/disputeRoutes.ts` + `services/dispute/disputeRoutes.ts` | JWT | Dispute CRUD plus release-hold helpers | +| `/api/blog` | `services/blog/blogRoutes.ts` | mixed | Public reads, admin writes | +| `/api/admin/cleanup` | `services/admin/dataCleanupRoutes.ts` | JWT (admin) | Data cleanup operations | +| `/api/points` | `services/points/pointsRoutes.ts` | JWT | Points, levels, referrals | | `/api/ai` | `services/ai/aiRoutes.ts` | JWT | OpenAI-backed helpers | | `/api/files` | `services/file/fileRoutes.ts` | JWT | Multipart upload | | `/api/email` | `services/email/emailRoutes.ts` | JWT | Email dispatch | @@ -253,9 +256,12 @@ Full table in [[Environment Variables]]. Critical ones: | `JWT_EXPIRES_IN` | `7d` | | | `REFRESH_TOKEN_EXPIRES_IN` | `30d` | | | `FRONTEND_URL` | `http://localhost:3000` | CORS origin | -| `SHKEEPER_API_URL` | `https://pay.amn.gg` | | -| `SHKEEPER_API_KEY` | required | | -| `SHKEEPER_WEBHOOK_SECRET` | required | HMAC key | +| `REQUEST_NETWORK_API_BASE_URL` | `https://api.request.network` | Request Network API | +| `REQUEST_NETWORK_API_KEY` | required | Request Network API credential | +| `REQUEST_NETWORK_WEBHOOK_SECRET` | required | Webhook HMAC key | +| `PAYMENT_LEDGER_ENFORCEMENT` | `false` | Target `true` before launch-scale releases | +| `TRANSACTION_SAFETY_*` | required for payments | Confirmation, transfer-match, and AML controls | +| `DERIVED_DESTINATION_SWEEP_SIGNER` | `build-only` | Target hardware/Safe-backed signer | | `SMTP_*` | required | Nodemailer | | `OPENAI_API_KEY` | required | | @@ -279,7 +285,7 @@ Redis client (in `src/services/redis/`) provides: The codebase has no dedicated queue runner — scheduled / async work is triggered inline from request handlers and uses `setTimeout` / `setInterval` patterns where needed (e.g., delayed retries). Consider introducing Bull / BullMQ if you grow: -- Payment status reconciliation (polling SHKeeper for stragglers) +- Request Network webhook replay/reconciliation and derived-destination balance checks - Notification email digests - Auto-release escrow timers - Token / refresh-token cleanup @@ -295,7 +301,7 @@ Jest test suites in `backend/__tests__/`: | `models.test.ts` | Schema validation, virtuals, hooks | | `payment-services.test.ts` | Payment orchestration logic | | `complete-backend.test.ts` | Cross-service integration | -| `shkeeper-backend.test.ts` | SHKeeper service + webhook | +| Request Network / payment tests | Request Network adapter, webhook signature, ledger, release/refund orchestration | Run with `npm run test:all`. CI runs the same. Reach for `npm run test:models`, `npm run test:payment`, etc. when iterating on a slice. @@ -310,7 +316,8 @@ Run with `npm run test:all`. CI runs the same. Reach for `npm run test:models`, | `src/shared/utils/response-handler.ts` | Standard response shape | | `src/shared/middleware/auth.ts` | JWT verify + RBAC | | `src/infrastructure/socket/socketService.ts` | All socket plumbing | -| `src/services/payment/shkeeper/shkeeperWebhook.ts` | Webhook signature scheme | +| `src/services/payment/requestNetwork/requestNetworkRoutes.ts` | Request Network checkout and webhook route | +| `src/services/payment/ledger/fundsLedgerService.ts` | Immutable payment ledger writes | | `src/services/marketplace/PurchaseRequestService.ts` | Core marketplace state machine | | `src/services/auth/authService.ts` | Auth flows, lockout, hashing | | `src/models/User.ts` | Central entity with role/preferences | @@ -325,4 +332,4 @@ Run with `npm run test:all`. CI runs the same. Reach for `npm run test:models`, - [[Real-time Layer]] — Socket.IO room model - [[Security Architecture]] — JWT, passkeys, webhook HMAC - [[Data Model Overview]] — entity-relationship map -- [[Authentication Flow]] · [[Payment Flow - SHKeeper]] · [[Dispute Flow]] +- [[Authentication Flow]] · [[Escrow Flow]] · [[Dispute Flow]] diff --git a/01 - Architecture/Infrastructure.md b/01 - Architecture/Infrastructure.md index 4fc01b7..57ebe2a 100644 --- a/01 - Architecture/Infrastructure.md +++ b/01 - Architecture/Infrastructure.md @@ -190,7 +190,7 @@ See [[Monitoring]] for the full table of metrics & recommended alerts. | Browser → Backend | 5001 | HTTP + WS | via Nginx `/api`, `/socket.io` | | Backend → MongoDB | 27017 | TCP | Docker network | | Backend → Redis | 6379 | TCP | Docker network | -| Backend → SHKeeper | 443 | HTTPS | External | +| Backend → Request Network API | 443 | HTTPS | External payment provider | | Backend → SMTP | 587 | TLS | External | | Backend → OpenAI | 443 | HTTPS | External | | Browser → Blockchain RPC | 443 | HTTPS | Alchemy URLs | diff --git a/01 - Architecture/Real-time Layer.md b/01 - Architecture/Real-time Layer.md index 192e6e0..81dfcaa 100644 --- a/01 - Architecture/Real-time Layer.md +++ b/01 - Architecture/Real-time Layer.md @@ -215,6 +215,6 @@ Sticky sessions on the load balancer are also required so a given client always ## Related - [[Backend Architecture]] · [[Frontend Architecture]] -- [[Chat Flow]] · [[Notification Flow]] · [[Payment Flow - SHKeeper]] · [[Dispute Flow]] +- [[Chat Flow]] · [[Notification Flow]] · [[Escrow Flow]] · [[Dispute Flow]] - [[Security Architecture]] — socket auth concerns - [[Socket Events]] — full event reference (developer-facing API doc) diff --git a/01 - Architecture/Request Network Integration Constraints.md b/01 - Architecture/Request Network Integration Constraints.md index 0da0066..4e28072 100644 --- a/01 - Architecture/Request Network Integration Constraints.md +++ b/01 - Architecture/Request Network Integration Constraints.md @@ -8,15 +8,15 @@ This document captures payment-flow issues that surfaced while integrating Reque --- -## 1. RN does not support Rabby — show-stopper for our wallet user base +## 1. RN hosted UI does not support Rabby -- mitigated by Amanat in-house checkout ### Problem RN's hosted payment page (the `pay.request.network/?token=…` UI returned by `/v2/secure-payments`) does not detect / connect to Rabby. A meaningful slice of Amanat's user base pays from Rabby. Sending them to a screen that won't even let them connect is a hard block. -### Mitigation (designed, not yet implemented) +### Mitigation (implemented core path) -Skip the RN-hosted UI. We already call `/v2/secure-payments` and receive a `securePaymentUrl`, but we also receive `requestIds` and `token` — that's everything we need to know what the merchant request is. Behind that token there is a contract on the destination chain that anyone can fulfill. +Skip the RN-hosted UI. Amanat still calls `/v2/secure-payments`, stores the Request Network identifiers, and exposes an in-house checkout block. The frontend builds the same RN-compatible on-chain action from the buyer's wallet, so Rabby/MetaMask users stay inside the Amanat flow. So the new flow becomes: @@ -32,10 +32,11 @@ So the new flow becomes: - RN's value to us at that point is the *settlement bookkeeping*, not the UI. We use them as "did this address receive the expected amount before timeout?" — the wallet UX stays in our control. - Buyer never sees a third-party brand mid-checkout, which is a UX win regardless of Rabby. -### Open +### Remaining work -- Need to confirm RN settles a payment that arrives from a *proxy transaction we built*, not from their hosted page. The 2026-05-28 probe confirms RN webhook delivery to Amanat, but the app returned `404`; repeat the probe only after the confirmation repair is deployed. -- Need a fallback for the buyer who insists on the RN hosted UI (some users will already have the link copied). Keep `securePaymentUrl` exposed as a "advanced / pay with RN" link. +- Keep the RN hosted URL exposed as an escape hatch. +- Continue hardening timer/persistence/telemetry around the in-house checkout. +- Treat durable webhook ingress as a production gate, because the main Express app should not be the only landing zone for callback evidence. --- @@ -51,7 +52,7 @@ The visible costs: - Or seller gets less than they expected (worst — they'll dispute). - Plus settlement latency goes from seconds to minutes-hours depending on the bridge. -### Mitigation (designed) +### Mitigation (partially implemented) Take the chain choice away from RN's UI and bring it into ours, gated by what the *seller* will accept. @@ -62,11 +63,11 @@ Two-step UX: ### Side benefit -This composes cleanly with #1 (own checkout screen): we already have to render the wallet picker, so adding a chain selector before the wallet step costs almost nothing. +This composes cleanly with #1 (own checkout screen): we already render the wallet picker, so seller-accepted chain selection can happen before wallet connection. The chain/token registry and admin networks page exist; seller-side accepted-chain policy remains a separate product/data-model task. ### Open -- We need a per-seller config table for accepted chains. Today the env-level `REQUEST_NETWORK_MERCHANT_REFERENCE` hard-codes a single chain (`bsc`). Needs to become per-seller, per-offer. +- We need a per-seller/per-offer config table for accepted chains. Today the global merchant reference is still the fallback, while derived destination work handles recipient variation. - Does RN's API support creating a secure-payment that *rejects* off-chain payments rather than auto-bridging? Or do we have to enforce this purely on our side by never offering the cross-chain option to the buyer? **Confirm with RN docs/support.** --- @@ -83,7 +84,7 @@ Today the entire escrow stack receives funds into one (or a handful of) wallets This is a show-stopper for going live at scale. Same class of issue we already considered around SHKeeper. -### Mitigation (designed; needs RN feasibility check) +### Mitigation (implemented core path; operational probe pending) Per-`(buyer, merchant)`-pair ephemeral wallets. Each new escrow gets a freshly-generated address that only ever receives that one transaction. If those funds turn out to be dirty: @@ -93,23 +94,23 @@ Per-`(buyer, merchant)`-pair ephemeral wallets. Each new escrow gets a freshly-g ### What this requires (architectural work) -1. **Wallet abstraction layer** — service that on demand generates a fresh address (HD wallet derivation from a master seed kept in a hardware module / KMS) and returns it to the payment-intent flow. -2. **Address book / registry** — maps `(paymentId, chainId)` → derived address. Persists derivation path + sequence number so we can reproduce keys for sweeps later. -3. **Sweep job** — once a payment is confirmed AND has passed an on-chain screening check (Chainalysis API or similar), sweep the ephemeral wallet to the main treasury. If screening fails, the ephemeral wallet is quarantined and the payment refunded out of band. -4. **Key custody policy** — these are still our funds in custody briefly; need clear policy on who can sign sweeps, hot-key vs cold-key separation. +1. **Wallet abstraction layer** -- implemented in `backend/src/services/payment/wallets/derivedDestinations.ts` using xpub-only derivation. +2. **Address book / registry** -- implemented in `DerivedDestination`, keyed by `(buyerId, sellerOfferId, chainId)`. +3. **Sweep job** -- implemented with build-only/hot-key signer abstraction; production must keep build-only and move execution to Trezor/Safe. +4. **Key custody policy** -- still the important missing operational layer. See [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. ### Critical open question -**Does RN support creating a secure-payment with a destination wallet we specify per-request, rather than a static merchant reference?** If yes, this is straightforward — we generate a wallet, register it as the destination for one specific `/v2/secure-payments` call, done. If no (RN only allows pre-registered destinations), we have to either: +**Does RN support creating a secure-payment with a destination wallet we specify per request at production volume, rather than a static merchant reference?** The backend/frontend support the shape, but the live divergent-destination probe remains the operational proof point. If RN cannot support this reliably, fallback options are: - Pre-register a large pool of addresses with RN and rotate through them, or - Bypass RN's destination model and go full self-host (which is most of issue #4). -**Action: confirm with RN support whether per-request destinations are supported on the same API key.** +**Action: run the two-paid-intent divergent-destination probe and confirm with RN support whether this usage is supported on the same API key at expected volume.** --- -## 4. RN reduced to a notification service — viable, but not yet validated +## 4. RN reduced to a notification service -- viable, partially validated ### Problem statement @@ -131,19 +132,19 @@ Which is a *notification* primitive, not a payment platform. We'd be paying for - We're outsourcing the *one thing* RN is good at (settlement) and keeping the parts they don't help with (UX, wallet generation, compliance). - Alternative: do the same with our own chain watcher (Alchemy webhooks / Tenderly / Goldsky) and skip RN entirely. -### What needs testing before we commit +### What still needs testing before we commit at scale 1. **Webhook reliability at our volume.** What's RN's SLA for "address received funds → webhook delivered"? P50? P99? 2. **Custom destination support.** See open question in #3. 3. **Per-API-key rate limits.** If we end up calling `/v2/secure-payments` once per escrow, do we hit ceilings? 4. **Pricing for the notification-only flow** — is there a tier, or is it the same as the full-stack price? -5. **What happens when the payment arrives from a transaction WE built** (not theirs)? Does the webhook still fire? Is settlement still recognized? — this is the load-bearing test for the whole strategy. +5. **What happens when the payment arrives from a transaction WE built** (not theirs)? The 2026-05-28 in-house checkout probe proved the basic path for a real BSC USDC payment; this still needs repeated paid probes across tokens/chains and webhook durability coverage. -Until #5 is confirmed, the rest is just paper architecture. +Until webhook durability, destination divergence, pricing, and SLA are confirmed, treat RN as useful but not irreplaceable infrastructure. --- -## 5. Webhook durability and transaction safety are P0 before more paid probes +## 5. Webhook durability remains P0 before production rollout ### What the 2026-05-28 probe proved @@ -153,12 +154,12 @@ The dev test transaction `0x3a23febd9abd43d7e0851c1ea86c4ceaf08c11098852cb0425fa Do not treat the main Express app as the only webhook landing zone, and do not treat a signed provider callback as enough to credit escrow. -### Required mitigation +### Required mitigation and status -1. **Correlation repair:** lookup Request Network payments by every persisted reference shape, including `providerPaymentId`, top-level RN request id/payment reference, and nested raw RN data. -2. **Callback repair:** payment callback polling must unwrap the backend response shape, clear polling after terminal states, and avoid a 3-second loop that self-rate-limits. -3. **Transaction Safety Provider:** completion must pass configured safety checks: transaction hash present, minimum confirmations, token/recipient/amount transfer match, and future AML/sanctions provider approval. -4. **Durable ingress:** put a Cloudflare Worker in front of RN webhooks. The Worker stores raw delivery evidence durably, forwards to the backend, and supports replay. It is not the trust oracle; the backend still verifies, deduplicates, and applies safety/ledger transitions. +1. **Correlation repair:** implemented for the in-house checkout path; keep smoke coverage around every persisted RN reference shape. +2. **Callback repair:** implemented enough for the successful paid dev probe; keep polling/backoff hardening on the checkout roadmap. +3. **Transaction Safety Provider:** implemented for tx hash, confirmations, transfer match, and AML placeholder; real AML provider remains Task #10. +4. **Durable ingress:** not started. Put a Cloudflare Worker in front of RN webhooks. The Worker stores raw delivery evidence durably, forwards to the backend, and supports replay. It is not the trust oracle; the backend still verifies, deduplicates, and applies safety/ledger transitions. --- @@ -166,13 +167,13 @@ Do not treat the main Express app as the only webhook landing zone, and do not t | # | Action | Blocker / Owner | |---|---|---| -| 1 | Deploy confirmation repair and repeat the dev payment probe | Backend payments | -| 2 | Test: `/v2/secure-payments` accepts a per-request destination wallet | Backend payments | +| 1 | Run the live divergent-destination probe: two paid intents to two derived addresses | Backend payments | +| 2 | Confirm `/v2/secure-payments` per-request destination usage with RN support and pricing | Product / RN account manager | | 3 | Confirm RN doesn't auto-bridge when buyer pays on the destination chain natively | Backend payments | | 4 | Get RN's webhook P99 latency + delivery guarantees in writing | Product / RN account manager | -| 5 | Spec the wallet-abstraction layer (HD derivation + sweep job + key policy) | Backend, before going live | +| 5 | Move sweep/release/refund custody to Trezor/Safe, not backend hot keys | Backend + ops | | 6 | Spec the seller-side accepted-chains config | Backend + frontend | -| 7 | Add Cloudflare Worker durable webhook ingress to the roadmap | Backend / platform | +| 7 | Build Cloudflare Worker durable webhook ingress + replay | Backend / platform | | 8 | Add AML/sanctions adapter behind Transaction Safety Provider | Compliance / backend | -Actions 1–4 are *information-gathering* and should run in parallel before any more architectural commitment to RN. Actions 5–6 are blocked on 1–3 confirming RN can actually support this shape. +Actions 1-4 are information-gathering and should run in parallel before deeper RN commitment. Actions 5, 7, and 8 are production-safety work regardless of whether Amanat keeps RN long-term or replaces it with a direct chain watcher. diff --git a/01 - Architecture/Security Architecture.md b/01 - Architecture/Security Architecture.md index 938f929..d4a292e 100644 --- a/01 - Architecture/Security Architecture.md +++ b/01 - Architecture/Security Architecture.md @@ -9,7 +9,7 @@ created: 2026-05-23 How identity, authorization, transport, and integrity are handled across the platform. > [!important] -> Read alongside [[Authentication Flow]] (user-facing), [[Passkey (WebAuthn) Flow]], and [[Payment Flow - SHKeeper]] (webhook HMAC). +> Read alongside [[Authentication Flow]] (user-facing), [[Passkey (WebAuthn) Flow]], [[Escrow Flow]], and [[Request Network Integration Constraints]]. --- @@ -22,7 +22,7 @@ How identity, authorization, transport, and integrity are handled across the pla | CSRF | JWT in `Authorization` header (not cookie), CORS allow-list | | XSS | Helmet CSP, React auto-escaping, sanitize HTML before storage | | SQL/NoSQL injection | Mongoose parameterized queries, no `$where` strings, schema validation | -| Webhook spoofing | HMAC SHA-256 over body + secret (SHKeeper, Request Network, Telegram), constant-time compare | +| Webhook spoofing | HMAC SHA-256 over raw body + provider secret (Request Network, Telegram), constant-time compare | | File upload abuse | Multer MIME validation, 5 MB cap, non-executable storage, served by Nginx not Node | | Replay attacks | Per-payment idempotency on `providerPaymentId`; Telegram initData in-memory replay map; per-request `X-Request-Id` | | Account takeover | Email verification required, password reset code expiry (1h), passkey support | @@ -155,34 +155,36 @@ A single User may be `buyer` and `seller` simultaneously (combined role). ## 5. Webhook integrity -### 5.1 SHKeeper +### 5.1 Request Network ```mermaid sequenceDiagram - participant SHK + participant RN + participant WK as Durable ingress (roadmap) participant BE - SHK->>BE: POST /api/payment/shkeeper/webhook
X-Signature: sha256= - BE->>BE: hmac = HMAC_SHA256(SHKEEPER_WEBHOOK_SECRET, body) - BE->>BE: crypto.timingSafeEqual(hmac, providedSig) + RN->>WK: POST /api/payment/request-network/webhook
x-request-network-signature + WK->>WK: Store raw body + headers + delivery id + WK->>BE: Forward / replay raw webhook + BE->>BE: verifyRequestNetworkWebhookSignature(rawBody, headers) alt mismatch - BE-->>SHK: 401 Unauthorized + BE-->>WK: 401 Unauthorized else match - BE->>BE: process payment update - BE-->>SHK: 200 OK + BE->>BE: idempotency + Transaction Safety Provider + BE->>BE: process payment update / ledger entry + BE-->>WK: 200 OK end ``` -- Raw body must be used for HMAC — `express.raw({ type: 'application/json' })` is mounted on this route only (before the global `express.json()` parser). -- In dev (`NODE_ENV === 'development'`) signature verification can be bypassed for local testing — confirm this is gated and never reachable in prod. -- Idempotency: identical webhook delivered twice should be no-op. Check by `(providerPaymentId, status)` tuple before mutating. - -### 5.2 Request Network - - Webhooks arrive at `/api/payment/request-network/webhook` with an `x-request-network-signature` header. - The backend verifies the signature using `backend/src/services/payment/requestNetwork/signature.ts` before any state mutation. - The route is mounted **before** the global `express.json()` body parser so raw body bytes are available for signature computation. - The global rate-limit middleware is configured to skip this path to avoid blocking high-frequency payment events. - Reconciliation service (`requestNetworkReconciliationService.ts`) handles replayed or out-of-order webhooks idempotently. +- Durable ingress is the target production shape: the Worker stores delivery evidence and supports replay, but the backend remains the trust oracle. + +### 5.2 Legacy SHKeeper note + +SHKeeper-specific webhook docs are historical migration context. The current backend payment tree uses Request Network as the primary provider; do not reintroduce SHKeeper signature bypasses or fallback webhook heuristics without a new security review. ### 5.3 Telegram Bot webhook @@ -191,7 +193,7 @@ sequenceDiagram - A per-update-id in-memory replay map prevents duplicate processing within the configured window. - The global rate-limit middleware is configured to skip this path. -See [[Payment Flow - SHKeeper]] for the SHKeeper full flow. +See [[Escrow Flow]] and [[Request Network Integration Constraints]] for the current payment path. --- @@ -219,7 +221,7 @@ See [[Payment Flow - SHKeeper]] for the SHKeeper full flow. - Never log secrets — logger redaction recommended (winston/pino formatter). - `.env*` files in `.gitignore`. Repo includes only `.env.development` / `.env.production` templates with **public** values (NEXT_PUBLIC_*). - Rotate `JWT_SECRET` invalidates all existing JWTs — schedule a maintenance window. -- Rotate `SHKEEPER_WEBHOOK_SECRET` coordinated with SHKeeper dashboard (set new → verify → remove old). +- Rotate `REQUEST_NETWORK_WEBHOOK_SECRET` coordinated with Request Network configuration (set new → verify → remove old). See [[Environment Variables]] for the catalog. @@ -277,6 +279,6 @@ The codebase currently uses `morgan` (HTTP access logs) and ad-hoc `logger.info/ - [[Authentication Flow]] (includes Telegram first-class auth flow) · [[Google OAuth Flow]] · [[Passkey (WebAuthn) Flow]] · [[Password Reset Flow]] - [[Backend Architecture]] · [[Frontend Architecture]] · [[Real-time Layer]] -- [[Payment Flow - SHKeeper]] — webhook HMAC details +- [[Request Network Integration Constraints]] — payment webhook, checkout, and reconciliation constraints - [[Environment Variables]] — secret catalog - [[Incident Response]] — what to do when something goes wrong diff --git a/01 - Architecture/System Architecture.md b/01 - Architecture/System Architecture.md index eb447b1..ebce3db 100644 --- a/01 - Architecture/System Architecture.md +++ b/01 - Architecture/System Architecture.md @@ -24,7 +24,8 @@ flowchart LR BE[Express Backend
+ Socket.IO
:5001] Mongo[(MongoDB 8)] Redis[(Redis 8)] - SHK[SHKeeper
Crypto Gateway] + RN[Request Network
Pay-in + webhooks] + CFWorker[Durable webhook ingress
roadmap] SMTP[SMTP
Nodemailer] OAI[OpenAI API] BC[Blockchain RPC
Alchemy / WalletConnect] @@ -37,8 +38,9 @@ flowchart LR FE -.->|Socket.IO| BE BE --> Mongo BE --> Redis - BE -->|Pay-in / Pay-out| SHK - SHK -.->|Webhook HMAC| BE + BE -->|Pay-in intent / status| RN + RN -.->|Signed webhook| CFWorker + CFWorker -.->|Forward / replay| BE BE --> SMTP BE --> OAI FE -->|Wallet Connect| BC @@ -142,25 +144,29 @@ Mutations follow optimistic-then-confirm: ### 5.3 Webhook path (inbound) -External services (SHKeeper) POST to `/api/payment/shkeeper/webhook`. The backend verifies HMAC signature, updates the `Payment` document, advances any linked `PurchaseRequest`/`SellerOffer` state, and emits Socket.IO events to both buyer and seller rooms. +External services POST payment callbacks to provider-specific webhook routes. The current primary path is Request Network at `/api/payment/request-network/webhook`; the target architecture puts a durable ingress worker in front of the backend so raw delivery evidence can be replayed after outages. The backend remains the trust oracle: it verifies signatures, deduplicates deliveries, applies Transaction Safety Provider checks, updates ledger/payment state, and emits Socket.IO events to both buyer and seller rooms. ```mermaid sequenceDiagram - participant SHK as SHKeeper + participant RN as Request Network + participant WK as Durable ingress worker participant BE as Backend participant DB as MongoDB participant Buyer participant Seller - SHK->>BE: POST /api/payment/shkeeper/webhook
X-Signature: HMAC-SHA256 - BE->>BE: verifySignature(body, header, SHKEEPER_WEBHOOK_SECRET) - BE->>DB: Payment.updateOne({providerPaymentId}, {status:"completed"}) - BE->>DB: PurchaseRequest.updateOne(..., {status:"funded"}) + RN->>WK: POST signed webhook
delivery id + raw body + WK->>WK: Store immutable delivery evidence + WK->>BE: Forward / replay webhook + BE->>BE: Verify RN signature + idempotency + BE->>BE: Transaction Safety Provider checks tx hash, recipient, token, amount, confirmations + BE->>DB: Append ledger entry + Payment escrowState="funded" + BE->>DB: PurchaseRequest.updateOne(..., {status:"payment"}) BE-->>Buyer: socket emit "payment:status-updated" BE-->>Seller: socket emit "request:funded" - BE-->>SHK: 200 OK + BE-->>WK: 200 OK ``` -See [[Payment Flow - SHKeeper]] for the full sequence. +See [[PRD - Request Network In-House Checkout]] and [[Request Network Integration Constraints]] for the full Request Network sequence. --- diff --git a/02 - Data Models/Data Model Overview.md b/02 - Data Models/Data Model Overview.md index e3212ce..d1fc1fd 100644 --- a/02 - Data Models/Data Model Overview.md +++ b/02 - Data Models/Data Model Overview.md @@ -9,26 +9,17 @@ aliases: [Models Index, Schema Overview] This section documents every Mongoose model that backs the marketplace. The persistence layer lives in `backend/src/models/` and is exported through a single barrel file at `backend/src/models/index.ts`. All models target MongoDB via Mongoose, lean on `timestamps: true` for `createdAt` / `updatedAt`, and follow a consistent pattern: one schema per file, an exported `I` TypeScript interface, and named exports for the compiled model. > [!note] Scope -> Eighteen models are documented here. The "File" concept exists only at the service layer (`backend/src/services/file/`) and is not persisted as its own Mongoose collection, so it is not listed below. +> Twenty-two models are present in `backend/src/models/`. The "File" concept exists only at the service layer (`backend/src/services/file/`) and is not persisted as its own Mongoose collection, so it is not listed below. > -> [!warning] Implementation gap -> As of the 2026-05-24 audit, the following documented models **do not yet have Mongoose schema files** in `backend/src/models/`: -> - [[Dispute]] -> - [[BlogPost]] -> - [[Review]] -> - [[PointTransaction]] -> - [[LevelConfig]] -> - [[ShopSettings]] -> The following *are* implemented in code and are documented accurately: -> - [[User]], [[PurchaseRequest]], [[SellerOffer]], [[Payment]], [[Chat]], [[Notification]], [[RequestTemplate]], [[Address]], [[Category]], [[TempVerification]], [[TelegramLink]], [[TelegramSession]] -> Additionally, `FundsLedgerEntry.ts` and `TrezorAccount.ts` exist in `backend/src/models/` but are not yet documented in this vault. +> [!note] Documentation freshness +> The 2026-05-24 audit note that marked `Dispute`, `BlogPost`, `Review`, `PointTransaction`, `LevelConfig`, and `ShopSettings` as missing is now stale: schema files exist for those models. Newer operational models such as [[ConfigSetting]], [[DerivedDestination]], [[FundsLedgerEntry]], and [[TrezorAccount]] should be expanded into dedicated model pages when the docs are next deepened. ## Index of Models - [[User]] — Core identity. Stores credentials, profile, preferences, referral data, points, and WebAuthn passkeys. Every other model that records "who did what" points back at a `User._id`. Buyers, sellers, and admins all live in this collection, differentiated by a `role` enum. - [[PurchaseRequest]] — The buyer-side document at the heart of the marketplace. Captures what a buyer wants, the budget, urgency, delivery preferences, and the full lifecycle status (`pending_payment` → `seller_paid`). Aggregates [[SellerOffer]] references and tracks delivery codes. - [[SellerOffer]] — A seller's bid against a [[PurchaseRequest]]. Holds price, delivery ETA, attachments, and a small status machine (`pending` / `accepted` / `rejected` / `withdrawn`). -- [[Payment]] — Records every monetary movement: buyer pay-in, seller payout, refund. Integrates with the SHKeeper crypto gateway and tracks escrow state plus on-chain transaction metadata. +- [[Payment]] — Records monetary movement intent and state: buyer pay-in, seller release, and refund. The current primary provider path is Request Network plus in-house checkout, derived destinations, funds ledger entries, and Transaction Safety Provider metadata. - [[Chat]] — Conversation container with embedded messages, participants, unread counters, and reactions. Used for direct buyer-seller chats, group chats, and support tickets. Can be linked to a [[PurchaseRequest]] or [[SellerOffer]]. - [[Notification]] — Per-user notification with category, type, and 90-day TTL for automatic cleanup. References any related entity by stringified id. - [[RequestTemplate]] — A seller-authored, sharable template that pre-fills a [[PurchaseRequest]]. Carries a public shareable link, usage counter, and an optional default proposal. @@ -43,6 +34,10 @@ This section documents every Mongoose model that backs the marketplace. The pers - [[TempVerification]] — Short-lived signup record that holds candidate user data and a verification code. Auto-purges via TTL when `emailVerificationCodeExpires` passes. - [[TelegramLink]] — Permanent auditable association between a Telegram user ID and an Amanat [[User]]. Stores Telegram profile metadata, link source (`miniapp` / `bot` / `login_widget`), status (`active` / `blocked`), and last-seen timestamp. One per Telegram user (unique on both `userId` and `telegramUserId`). - [[TelegramSession]] — Short-lived Telegram Mini App session token issued when `initData` is verified. Carries the `initDataFingerprint` for replay protection and auto-expires via a MongoDB TTL index on `expiresAt`. +- [[ConfigSetting]] — Runtime configuration persisted in MongoDB for operational knobs that need an admin surface rather than a deploy. +- [[DerivedDestination]] — Per-payment derived wallet destination records used to reduce address reuse and reconcile on-chain pay-ins. +- [[FundsLedgerEntry]] — Immutable accounting ledger rows for pay-in, hold, release, refund, fee, adjustment, and reversal events. +- [[TrezorAccount]] — Hardware-wallet/safekeeping account metadata for custody operations and staged signer hardening. ## Relationship Diagram @@ -59,6 +54,7 @@ erDiagram USER ||--o{ REVIEW : "writes as reviewer" USER ||--o{ DISPUTE : "raises as buyer" USER ||--o{ USER : "referred by" + USER ||--o{ TREZOR_ACCOUNT : "controls custody account" PURCHASE_REQUEST }o--|| CATEGORY : "belongs to" PURCHASE_REQUEST ||--o{ SELLER_OFFER : "receives" @@ -72,6 +68,8 @@ erDiagram PAYMENT }o--|| USER : "buyer" PAYMENT }o--|| USER : "seller" + PAYMENT ||--o{ FUNDS_LEDGER_ENTRY : "accounted by" + PAYMENT ||--o| DERIVED_DESTINATION : "collects into" CHAT }o--o{ USER : "participants" CHAT ||--o{ DISPUTE : "support channel" @@ -109,11 +107,11 @@ The dominant happy-path flow exercises five collections in order: 1. A buyer (`User`) creates a `PurchaseRequest` with `status: 'pending'`. 2. Sellers (other `User`s) attach `SellerOffer` documents; the request transitions through `received_offers` → `in_negotiation` as the parties chat in a `Chat`. -3. The buyer accepts an offer; a `Payment` is opened against the SHKeeper provider with `escrowState: 'funded'`. +3. The buyer accepts an offer; a `Payment` is opened against the Request Network provider and, once verified by webhook/reconciliation and safety checks, advances to a funded escrow state. 4. The seller marks the request `delivery` → `delivered`; the buyer confirms with the 6-digit `deliveryCode` and the request becomes `completed`. -5. The escrow `Payment` flips to `released` and a payout `Payment` (`direction: 'out'`) is issued. Optionally the buyer writes a `Review` and earns a `PointTransaction`. +5. The escrow `Payment` flips to `released` after a ledger-gated custody transfer instruction. Optionally the buyer writes a `Review` and earns a `PointTransaction`. -If anything goes sideways, the buyer can open a `Dispute` (planned but not yet implemented), which would freeze the flow until an admin resolves it (refund, replacement, compensation, or no-action). +If anything goes sideways, the buyer can open a `Dispute`, which freezes release until an admin resolves it (refund, replacement, compensation, or no-action). ## How to Navigate diff --git a/02 - Data Models/Payment.md b/02 - Data Models/Payment.md index 239aede..1570c1e 100644 --- a/02 - Data Models/Payment.md +++ b/02 - Data Models/Payment.md @@ -6,7 +6,7 @@ aliases: [Payment Record, Escrow, IPayment] # Payment -Records every monetary movement in the marketplace: buyer pay-ins, seller payouts, and refunds. Designed around the SHKeeper crypto payment gateway with explicit fields for blockchain network, transaction hash, escrow state, and provider invoice ids. The `provider` and `direction` discriminators let one collection hold all four flow types (incoming buyer payment, outgoing seller payout, refund, and "other" provider integrations). +Records every monetary movement in the marketplace: buyer pay-ins, seller payouts, and refunds. The current model is centered on Request Network pay-in, in-house checkout metadata, on-chain transaction verification, escrow state, and provider request IDs. The `provider` and `direction` discriminators let one collection hold incoming buyer payments, outgoing seller releases, refunds, and legacy/other provider records. > [!note] Source > `backend/src/models/Payment.ts:3` — schema definition @@ -25,7 +25,7 @@ Records every monetary movement in the marketplace: buyer pay-ins, seller payout | `sellerId` | Mixed (ObjectId or String) | yes | — | — | yes (compound) | Seller receiving (or template seller). | | `amount.amount` | Number | yes | — | — | — | Numeric amount. | | `amount.currency` | String | yes | `USDT` | — | — | Settlement currency. | -| `provider` | String | no | `shkeeper` | enum: `shkeeper` / `request.network` / `request-network` / `other` | yes (compound, partial) | Payment processor. | +| `provider` | String | no | `request.network` | enum: `request.network` / `other` | yes (compound, partial) | Payment processor. | | `direction` | String | no | `in` | enum: `in` / `out` / `refund` | yes (compound, partial) | Flow direction. | | `blockchain.network` | String | no | — | — | — | Network identifier. | | `blockchain.transactionHash` | String | no | — | — | yes (sparse) | On-chain tx hash. | @@ -56,6 +56,7 @@ Records every monetary movement in the marketplace: buyer pay-ins, seller payout | `metadata.requestNetworkSecurePaymentUrl` | String | no | — | — | — | Request Network secure payment URL. | | `metadata.requestNetworkData` | Mixed | no | — | — | — | Raw Request Network payload. | | `metadata.transactionSafety` | Mixed | no | — | — | — | Last Transaction Safety Provider decision, checks, evidence, and blocker reason. | +| `metadata.derivedDestination` | Object | no | — | — | — | Snapshot of per-payment derived destination address/path/index/chain. | | `metadata.lastWebhookAt` | Date | no | — | — | — | Last webhook timestamp. | | `metadata.webhookPayload` | Mixed | no | — | — | — | Last webhook body. | | `metadata.createdVia` | String | no | — | — | — | Origin marker. | diff --git a/03 - API Reference/API Overview.md b/03 - API Reference/API Overview.md index 539a7fb..de153f2 100644 --- a/03 - API Reference/API Overview.md +++ b/03 - API Reference/API Overview.md @@ -12,13 +12,13 @@ This page is the entry point for the API. See the individual service pages for e - [[Authentication API]] - register/login/passkeys/Google OAuth - [[User API]] - profile, wallet, admin user management - [[Marketplace API]] - purchase requests, seller offers, templates, shop, reviews -- [[Payment API]] - SHKeeper, Web3, DePay, payouts +- [[Payment API]] - Request Network, in-house checkout, ledger-gated release/refund - [[Chat API]] - conversations and messages - [[Notification API]] - in-app notifications -- [[Dispute API]] - dispute resolution *(planned, not yet implemented)* -- [[Blog API]] - blog posts *(planned, not yet implemented)* -- [[Admin API]] - user management, data cleanup *(planned, not yet implemented)* -- [[Points API]] - loyalty points, levels, referrals *(planned, not yet implemented)* +- [[Dispute API]] - dispute creation, assignment, evidence, resolution +- [[Blog API]] - blog posts +- [[Admin API]] - user management, data cleanup, RN/admin payment settings +- [[Points API]] - loyalty points, levels, referrals - [[AI API]] - OpenAI-backed text endpoints - [[File API]] - upload, delete, serve - [[Socket Events]] - real-time events @@ -157,7 +157,7 @@ cors({ }) ``` -Only the configured `FRONTEND_URL` may make cross-origin requests with credentials. The SHKeeper configuration endpoint (`GET /api/payment/shkeeper/config`) overrides this with `Access-Control-Allow-Origin: *` because it is consumed by the SHKeeper payment widget hosted on another domain. +Only the configured `FRONTEND_URL` may make cross-origin requests with credentials. Provider webhooks and Telegram bot webhooks are server-to-server entrypoints and should be exempted through explicit route handling, not broad browser CORS. Uploaded files served from `/uploads/*` use `helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } })` so they can be embedded from the frontend domain. diff --git a/04 - Flows/Chat Flow.md b/04 - Flows/Chat Flow.md index 3f5ac33..da14b99 100644 --- a/04 - Flows/Chat Flow.md +++ b/04 - Flows/Chat Flow.md @@ -50,7 +50,7 @@ stateDiagram-v2 - Re-loads with `populate('participants.userId', 'firstName lastName profile.avatar email')` for the response. 3. **Group chat (dispute)** — same pattern, but `type: 'group'`, all three participants (buyer, seller, admin) added (admin is added later by `DisputeService.assignAdmin`). 4. **Support chat** — `ChatService.createSupportChat(userId)` (`:41-88`) auto-discovers `User.findOne({ email: 'support@amn.gg' })` and creates a `type: 'support'` chat with a welcome message. Idempotent. -5. **Post-payment auto-chat** — when SHKeeper confirms payment, `shkeeperWebhook.ts:606-618` calls `chatService.createChat` to ensure a direct chat exists between buyer and winning seller. +5. **Post-payment auto-chat** — when payment is confirmed, the payment-state cascade ensures a direct chat exists between buyer and winning seller. ### Joining the room (real-time) diff --git a/04 - Flows/Delivery Confirmation Flow.md b/04 - Flows/Delivery Confirmation Flow.md index 3b1265c..b45d494 100644 --- a/04 - Flows/Delivery Confirmation Flow.md +++ b/04 - Flows/Delivery Confirmation Flow.md @@ -7,7 +7,7 @@ related_apis: ["POST /api/marketplace/purchase-requests/:id/delivery-code", "POS # Delivery Confirmation Flow -After the escrow is funded ([[Payment Flow - SHKeeper]] / [[Payment Flow - DePay & Web3]]) and the seller has prepared the item, the seller **marks shipped**, the buyer **enters a delivery code** to confirm receipt, and the escrow becomes eligible for release ([[Payout Flow]]). +After the escrow is funded ([[PRD - Request Network In-House Checkout]] / [[Escrow Flow]]) and the seller has prepared the item, the seller **marks shipped**, the buyer **enters a delivery code** to confirm receipt, and the escrow becomes eligible for release ([[Payout Flow]]). ## Actors @@ -113,7 +113,7 @@ sequenceDiagram ## Linked flows -- [[Payment Flow - SHKeeper]] / [[Payment Flow - DePay & Web3]] — funding precondition. +- [[PRD - Request Network In-House Checkout]] / [[Escrow Flow]] — funding precondition. - [[Escrow Flow]] — state transitions triggered by confirmation. - [[Payout Flow]] — fires after confirmation (manual today). - [[Dispute Flow]] — escape hatch. diff --git a/04 - Flows/Dispute Flow.md b/04 - Flows/Dispute Flow.md index 0defe9a..bd72b33 100644 --- a/04 - Flows/Dispute Flow.md +++ b/04 - Flows/Dispute Flow.md @@ -15,9 +15,9 @@ When something goes wrong (item not delivered, wrong item, fraud), either party - **Seller** — party against whom the dispute is raised (or in rarer cases, initiator). - **Admin / Mediator** — assigned to investigate. - **Frontend** — buyer/seller "Report issue" buttons in the request detail view; admin dispute dashboard. -- **Backend** — `DisputeService` (`backend/src/services/dispute/DisputeService.ts` *(planned)*), `DisputeController` (`backend/src/controllers/disputeController.ts` *(planned)*), routes at `backend/src/routes/disputeRoutes.ts` *(planned)*. - > [!warning] Not implemented - > None of these files exist as of 2026-05-24. The dispute module is planned but not yet built. +- **Backend** — `DisputeService` (`backend/src/services/dispute/DisputeService.ts`), dashboard/controller routes at `backend/src/routes/disputeRoutes.ts`, and release-hold helpers in `backend/src/services/dispute/releaseHoldService.ts`. + > [!note] Alignment gap + > The module exists now, but it still uses the legacy status/action enum. [[Funds Ledger and Escrow State Machine Specification]] defines the canonical future dispute states and financial side effects. - **MongoDB** — `disputes`, `chats`, `purchaserequests`, `payments`. - **Socket.IO** — `new-notification`, `new-message`, `dispute-updated` (planned). @@ -57,8 +57,8 @@ Resolution actions (from `Dispute.resolution.action` enum, see `Dispute.ts` *(in - Persists `dispute.chatId = chat._id`. 5. Notifications (currently a `TODO` in the service — `:107-116`) should fire `new-notification` to the seller. Today the chat creation alone provides real-time presence via the `new-message` socket emit inside `Chat.create`'s lifecycle. -> [!warning] Dispute does not auto-pause escrow -> Today, opening a dispute does **not** flip `Payment.escrowState` away from `funded`. An admin could theoretically still release the escrow before resolving the dispute. Until a `disputed` flag is added to Payment, admins must check the dispute table before any release/refund action. +> [!note] Release hold behavior +> Opening a dispute now has backend release-hold support: `releaseHoldService.raiseDispute()` sets hold fields on the purchase request and related payments, and release/refund gates can consult those fields. The remaining work is to make this the single mandatory policy path for every release/refund/sweep operation and align it with the canonical `DISPUTED` escrow state. ### Phase 2 — Admin assignment @@ -84,7 +84,7 @@ Resolution actions (from `Dispute.resolution.action` enum, see `Dispute.ts` *(in - `dispute.closedAt = now` - Appends `timeline` entry `dispute_resolved`. - Saves. -13. **Financial side-effect (manual today):** depending on the action, the admin then triggers either the **payout** ([[Payout Flow]] with `kind: 'release'`) or the **refund** (`kind: 'refund'`, see [[Escrow Flow]]). The dispute service does not automatically dispatch the on-chain action. +13. **Financial side-effect (manual today):** depending on the action, the admin then triggers either the **release** ([[Payout Flow]] / [[Escrow Flow]]) or the **refund**. The dispute service records the resolution; full automatic dispatch through the release/refund policy engine is still a hardening item. 14. Both parties are notified (TODOs in code — planned: `notifyDisputeResolved`). ## Sequence diagram @@ -179,7 +179,7 @@ All require `authenticateToken` (router-level middleware). - **Initiator is neither buyer nor seller** → not enforced at service level — should be validated in `DisputeController` (recommended hardening). - **Same user opens multiple disputes for the same request** → no uniqueness constraint today. Consider adding `unique on (purchaseRequestId, status:'pending'|'in_progress')` to prevent duplicates. - **Evidence URL is hot-linked** → frontend uploads through `POST /api/files/upload` and the URL is served from `/uploads`. Ensure auth on the upload endpoint to prevent random users from polluting evidence. -- **Dispute resolved without financial follow-up** → the dispute is "resolved" in record only; the escrow stays in its previous state. Add automation that auto-fires the payout/refund when the admin selects `release` or `refund`. +- **Dispute resolved without financial follow-up** → the dispute is "resolved" in record only; the escrow stays in its previous state until the admin/custody operator completes release/refund. Add automation that dispatches the policy-checked release/refund instruction when the admin selects a financial resolution. - **Admin resigns mid-dispute** → no transfer-of-mediator endpoint today. Add `POST /api/disputes/:id/reassign`. > [!tip] Sort disputes by priority + age @@ -195,12 +195,10 @@ All require `authenticateToken` (router-level middleware). ## Source files -> [!warning] Not implemented -> None of the backend files below exist as of 2026-05-24. The dispute module is planned but not yet built. - -- Backend: `backend/src/services/dispute/DisputeService.ts` *(planned)* -- Backend: `backend/src/controllers/disputeController.ts` *(planned)* -- Backend: `backend/src/routes/disputeRoutes.ts` *(planned)* -- Backend: `backend/src/models/Dispute.ts` *(planned)* +- Backend: `backend/src/services/dispute/DisputeService.ts` +- Backend: `backend/src/services/dispute/releaseHoldService.ts` +- Backend: `backend/src/routes/disputeRoutes.ts` +- Backend: `backend/src/services/dispute/disputeRoutes.ts` +- Backend: `backend/src/models/Dispute.ts` - Frontend: `frontend/src/sections/request/components/report-problem-to-admin.tsx` - Frontend: admin dispute dashboard under `frontend/src/sections/admin/` (subject to organisation) diff --git a/04 - Flows/Escrow Flow.md b/04 - Flows/Escrow Flow.md index 8d39184..a3495ad 100644 --- a/04 - Flows/Escrow Flow.md +++ b/04 - Flows/Escrow Flow.md @@ -1,199 +1,226 @@ --- title: Escrow Flow -tags: [flow, escrow, payment, state-machine] -related_models: ["[[Payment]]", "[[PurchaseRequest]]"] -related_apis: ["POST /api/payment/release/:paymentId", "POST /api/payment/refund/:paymentId"] +tags: [flow, escrow, payment, state-machine, custody] +related_models: ["[[Payment]]", "[[PurchaseRequest]]", "[[Funds Ledger and Escrow State Machine Specification]]"] +related_apis: ["POST /api/payment/:id/release", "POST /api/payment/:id/refund", "POST /api/payment/:id/release/confirm", "POST /api/payment/:id/refund/confirm"] --- # Escrow Flow -The escrow is not a separate smart contract — it is a **state machine on the `Payment` document** combined with a **custodial wallet** (the platform-controlled BSC address `NEXT_PUBLIC_ESCROW_WALLET_ADDRESS`). Funds sit at that wallet once SHKeeper / Web3 verification completes, and are released to the seller or refunded to the buyer based on order outcome. +The current escrow is a **hybrid custody system**, not a custom Solidity escrow contract. + +Buyer funds move on-chain through Request Network-compatible wallet transactions. The backend verifies the payment through signed Request Network webhooks/reconciliation plus the Transaction Safety Provider, records state in `Payment`, and records money movement in the internal funds ledger. Release/refund/sweep actions are still administered by the platform, with optional Trezor proof today and a recommended move to Safe multisig custody in [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. ## Actors -- **System** — the backend, on receiving pay-in confirmation. -- **Buyer** — confirms delivery to authorise release; can open a dispute to block release. -- **Seller** — recipient of release. -- **Admin** — resolves disputes and signs payout transactions when manual control is required. -- **MongoDB** — `payments` document holds the canonical `escrowState`. +- **Buyer** -- pays from their wallet and confirms delivery. +- **Seller** -- fulfills the order and receives release. +- **Admin / mediator** -- resolves disputes and initiates release/refund when manual action is required. +- **Custody signer** -- Trezor today when enabled; target state is Safe multisig owners. +- **Request Network** -- emits payment evidence through signed webhooks and status APIs. +- **Transaction Safety Provider** -- verifies tx hash, confirmations, recipient, token, amount, and optional AML decision before funds are credited. +- **MongoDB** -- stores `Payment`, `FundsLedgerEntry`, `Dispute`, and `PurchaseRequest` state. -## Escrow state machine (`Payment.escrowState`) +## Current State Model -Enum from `Payment.ts:112-115`: `funded | releasable | released | refunded | releasing | failed | cancelled | partial`. +`Payment.status` remains the coarse provider/business state: + +- `pending` +- `processing` +- `confirmed` +- `completed` +- `failed` +- `cancelled` +- `refunded` + +`Payment.escrowState` currently supports: + +- `funded` +- `releasable` +- `releasing` +- `released` +- `refunded` +- `failed` +- `cancelled` +- `partial` + +The current model also has `Payment.disputed`, `disputeHoldReason`, and `holdUntil`. The canonical target state machine in [[Funds Ledger and Escrow State Machine Specification]] adds explicit `DISPUTED`, `REFUNDING`, and normalized uppercase enums. Treat that spec as the destination; this page describes the live hybrid implementation. ```mermaid stateDiagram-v2 - [*] --> Pending: Payment.status="pending"\nescrowState=undefined - Pending --> Partial: webhook PARTIAL\nescrowState="partial" - Pending --> Funded: webhook PAID/OVERPAID\nor on-chain verify success\nescrowState="funded" - Partial --> Funded: top-up reaches threshold - Funded --> Releasable: buyer confirms delivery\n(or auto-release timer) - Releasable --> Releasing: admin/system initiates payout\n[[Payout Flow]] - Releasing --> Released: payout tx confirmed\nescrowState="released" - Releasing --> Failed: payout tx reverted\nescrowState="failed" - Funded --> Refunded: dispute resolution = refund\nescrowState="refunded" - Funded --> Refunded: order cancelled\npre-shipment - Pending --> Cancelled: webhook EXPIRED/CANCELLED -escrowState="cancelled" - Failed --> Releasing: admin retries + [*] --> Pending : payment intent created + Pending --> Processing : funds detected / webhook received + Pending --> Cancelled : intent expired or buyer cancels + + Processing --> Funded : Transaction Safety Provider approved + Processing --> Failed : verification rejected + + Funded --> Releasable : delivery confirmed / release authorized + Funded --> DisputeHold : dispute opened + Releasable --> DisputeHold : dispute opened before payout + + DisputeHold --> Funded : dispute rejected / no financial action + DisputeHold --> Releasable : resolved for seller + DisputeHold --> Refunding : resolved for buyer + + Releasable --> Releasing : release instruction built + Releasing --> Released : tx hash confirmed + Releasing --> Failed : payout failed + + Refunding --> Refunded : refund tx hash confirmed + Refunding --> Failed : refund failed + + Failed --> Releasing : admin retries release + Failed --> Refunding : admin retries refund + Released --> [*] Refunded --> [*] Cancelled --> [*] ``` -`Payment.status` mirrors a coarser business state: -- `pending` → invoice issued, awaiting funds. -- `processing` → SHKeeper sees partial / confirmations in progress. -- `confirmed` → fully credited (intermediate; sometimes skipped). -- `completed` → escrow `funded` and onward. -- `failed`, `cancelled`, `refunded` → terminal. - -## Step-by-step narrative +## Step-by-step Narrative ### 1. Funding -- Triggered by either [[Payment Flow - SHKeeper]] (webhook `PAID`/`OVERPAID`) or [[Payment Flow - DePay & Web3]] (verified `eth_getTransactionReceipt`). -- Backend sets `Payment.status = "completed"` and `Payment.escrowState = "funded"` (`shkeeperWebhook.ts:388-391`, `shkeeperService.ts:600-602`). -- Cascade: `PurchaseRequest.status` → `payment`, then `processing` once the seller acknowledges; `SellerOffer.status` → `accepted`; chat created. -- Funds physically sit at the **custodial wallet** — SHKeeper's per-invoice deposit address (auto-swept to the merchant wallet) or directly at the escrow wallet in the Web3 path. +1. Buyer accepts a seller offer and starts Request Network checkout. +2. Backend creates a `Payment` and Request Network intent through `requestNetworkPayInService.ts`. +3. When configured, `getDestinationFor({ buyerId, sellerOfferId, chainId })` assigns a per-payment derived destination and stores it in `payment.metadata.derivedDestination`. +4. Frontend renders the in-house checkout block and the buyer signs RN-compatible on-chain transactions from their wallet. +5. Request Network webhook or reconciliation reports payment evidence. +6. The Transaction Safety Provider verifies: + - transaction hash exists, + - chain confirmations meet the runtime/env threshold, + - token, recipient, and amount match, + - AML/sanctions provider result when configured. +7. Only after safety approval does the backend mark the payment funded and append ledger entries. ### 2. Holding -- While `escrowState === "funded"` and the order is in `processing` / `delivery`, the funds are inert. No interest accrues; no on-chain action happens. -- The buyer cannot withdraw; the seller cannot collect. Only an admin/system action moves it forward. -- Visible in admin dashboard: `GET /api/payment/admin/funded?status=funded` (or similar — see admin payment view in `frontend/src/sections/payment/view/payment-list-admin-view.tsx`). +While escrow is funded, funds are represented in two places: -### 3. Releasing (happy path) +- **On chain:** in the derived destination or custody wallet until swept/released/refunded. +- **In app accounting:** in `FundsLedgerEntry` rows and `Payment.escrowState`. -- Trigger options: - - **Buyer confirms delivery** via the delivery-code flow ([[Delivery Confirmation Flow]]). - - **Auto-release timer** elapses (configurable; today a manual or scheduled job — `PurchaseRequestService` exposes status transitions through to `completed`). - - **Admin manual release** from the admin payment detail view. -- The system marks `Payment.escrowState = "releasable"` (intermediate). -- `shkeeperPayoutService.createPayoutTask` (or a manual EVM admin signature via `admin-wallet-payout.tsx`) starts the on-chain transfer to the seller's verified wallet address. State flips to `releasing`. -- On confirmation: `confirmAdminTx(paymentId, txHash, 'release')` (`shkeeperService.ts:628-647`) sets: - - `Payment.status = 'completed'` - - `Payment.escrowState = 'released'` - - `Payment.blockchain.transactionHash = ` -- Cascade: `PurchaseRequest.status` → `seller_paid` then `completed`. +Release/refund eligibility must be derived from ledger availability, not raw mutable `Payment.status` alone. In production the roadmap requires `PAYMENT_LEDGER_ENFORCEMENT=true` before custody decentralization. -### 4. Refunding (dispute / cancellation) +### 3. Release -- Trigger: dispute resolution with `action: 'refund'` or pre-shipment cancellation. -- Backend builds the refund tx via `buildAdminSignedTxPayload(paymentId, 'refund')` (`shkeeperService.ts:614-626`) — destination is `payment.blockchain.sender` (the buyer's verified wallet). -- Admin signs and broadcasts (currently a manual step in the admin UI). -- On confirmation: `confirmAdminTx(paymentId, txHash, 'refund')` sets: - - `Payment.status = 'refunded'` - - `Payment.escrowState = 'refunded'` -- Cascade: `PurchaseRequest.status` → `cancelled` (or remains in dispute-resolved state). +Release is triggered by delivery confirmation, auto-release policy, or dispute resolution for the seller. -### 5. Failed payout +1. Admin calls `POST /api/payment/:id/release`. +2. Backend loads the payment and validates ledger availability when enforcement is enabled. +3. Backend builds a provider payment instruction. +4. Custody signer executes the transaction: + - current optional control: Trezor proof when `TREZOR_SAFEKEEPING_REQUIRED=true`; + - roadmap control: Safe multisig transaction proposal/execution. +5. Admin confirms with `POST /api/payment/:id/release/confirm` and tx hash. +6. Backend validates Trezor proof when required, confirms adapter state, and appends a `release` ledger entry. -- If the payout tx reverts (insufficient gas, contract pause, wrong address), `escrowState = 'failed'`. Admin can retry by initiating a fresh payout. +### 4. Refund -## Sequence diagram (release path) +Refund follows the same instruction/confirmation pattern as release, but destination is the buyer/refund wallet and ledger entry type is `refund`. + +Refund can be triggered by dispute resolution for the buyer, pre-fulfillment cancellation, or an admin/manual recovery flow. A refund during an active dispute must be an explicit resolution path, not an accidental bypass. + +### 5. Dispute Hold + +Opening a dispute now has backend support through `releaseHoldService.ts`: it sets hold fields on the related purchase request and payments, and release/refund gates consult those holds. + +Remaining alignment work: + +- migrate from legacy dispute status enum to the canonical spec, +- make financial side effects automatic from final dispute resolution, +- ensure every release/refund path calls the same policy service, +- record immutable audit entries for dispute resolution and custody execution. + +## Sequence Diagram - Funding ```mermaid sequenceDiagram autonumber actor B as Buyer - actor A as Admin participant FE as Frontend participant BE as Backend + participant RN as Request Network + participant BC as EVM Chain participant DB as MongoDB - participant SK as SHKeeper Payout API - participant BC as BSC - B->>FE: Enter delivery code (or auto-timer fires) - FE->>BE: POST /api/marketplace/purchase-requests/:id/confirm-delivery - BE->>DB: PurchaseRequest.status="delivered"\nPayment.escrowState="releasable" - BE-->>FE: ok - A->>FE: Click "Release" in admin - FE->>BE: POST /api/payment/shkeeper/payout - BE->>DB: Payment.escrowState="releasing" - BE->>SK: createPayoutTask({recipient, amount}) - SK->>BC: signed payout tx - BC-->>SK: confirmed - SK->>BE: payout webhook / poll - BE->>BE: confirmAdminTx(paymentId, txHash, "release") - BE->>DB: Payment.escrowState="released"\nPurchaseRequest.status="completed" + B->>FE: Start Request Network checkout + FE->>BE: POST /api/payment/request-network/intents + BE->>DB: Payment.create(status="pending") + BE->>BE: Assign derived destination when configured + BE->>RN: Create Request Network intent + BE-->>FE: inHouseCheckout block + B->>BC: approve + transferFromWithReferenceAndFee + RN-->>BE: signed webhook / status evidence + BE->>BE: Transaction Safety Provider checks + BE->>DB: Payment.status="completed", escrowState="funded" + BE->>DB: append FundsLedgerEntry(payment_detected / hold) ``` -## Sequence diagram (refund path) +## Sequence Diagram - Release / Refund ```mermaid sequenceDiagram autonumber actor A as Admin + actor C as Custody signer participant BE as Backend participant DB as MongoDB - participant BC as BSC - actor B as Buyer + participant BC as EVM Chain - A->>BE: Dispute resolved with action="refund" - BE->>BE: buildAdminSignedTxPayload(paymentId, "refund") - BE-->>A: { to:buyerWallet, amount, token, network } - A->>BC: sign + broadcast tx - BC-->>A: txHash - A->>BE: confirmAdminTx(paymentId, txHash, "refund") - BE->>DB: Payment.status="refunded"\nescrowState="refunded" - BE->>B: notifyRefundCompleted + A->>BE: POST /api/payment/{id}/release or refund + BE->>DB: Load Payment + ledger balance + BE->>BE: Check dispute hold + ledger availability + BE-->>A: unsigned instruction + A->>C: Request signature / Safe execution + C->>BC: Broadcast tx + BC-->>C: txHash + A->>BE: POST /confirm { txHash, optional trezor proof } + BE->>BE: Verify signer proof when required + BE->>DB: append release/refund ledger entry + BE->>DB: escrowState="released" or "refunded" ``` -## API calls +## API Calls | Method | Endpoint | Purpose | |---|---|---| -| `POST` | `/api/payment/admin/release/:paymentId` | Initiate release | -| `POST` | `/api/payment/admin/refund/:paymentId` | Initiate refund | -| `POST` | `/api/payment/admin/confirm-tx/:paymentId` | Admin marks the signed tx confirmed | -| `GET` | `/api/payment/:paymentId/status` | Polled by both parties | +| `POST` | `/api/payment/request-network/intents` | Create Request Network pay-in intent | +| `GET` | `/api/payment/request-network/:paymentId/checkout` | Rehydrate in-house checkout block | +| `POST` | `/api/payment/request-network/webhook` | Receive signed RN webhook | +| `POST` | `/api/payment/:id/release` | Build release instruction | +| `POST` | `/api/payment/:id/release/confirm` | Confirm release tx hash / signer proof | +| `POST` | `/api/payment/:id/refund` | Build refund instruction | +| `POST` | `/api/payment/:id/refund/confirm` | Confirm refund tx hash / signer proof | +| `GET` | `/api/payment/:id` | Read payment details | +| `GET` | `/api/payment/derived-destinations` | Admin list of derived destinations | -## Database writes +## Side Effects And Risks -- **`payments`**: `status`, `escrowState`, `blockchain.transactionHash`, `completedAt`, `metadata.*` are mutated as the state progresses. -- **`purchaserequests`**: `status` cascades (`payment → processing → delivery → delivered → confirming → seller_paid → completed`). -- **`notifications`**: created on each terminal state. +- **No custom on-chain escrow contract yet.** This is deliberate; [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] recommends Safe/Trezor custody controls before a custom contract pilot. +- **Ledger enforcement is configurable.** `PAYMENT_LEDGER_ENFORCEMENT` must be enabled before real custody decentralization work is considered complete. +- **Trezor enforcement is configurable.** `TREZOR_SAFEKEEPING_REQUIRED=true` makes Trezor proof mandatory for release/refund confirmation, but target custody should be Safe multisig. +- **Durable webhook ingress is still roadmap work.** Until the Worker/replay layer is live, backend availability remains important for Request Network webhook delivery. +- **Dispute model is implemented but not fully canonical.** The current model works with legacy enum names; canonical status alignment remains required. -## Socket events emitted +## Linked Flows -- **`purchase-request-update`** `status-changed` on every cascading status flip. -- **`payment-status`** (planned/admin) — admin dashboard real-time feed. +- [[PRD - Request Network In-House Checkout]] -- current primary pay-in path. +- [[Dispute Flow]] -- can block or redirect escrow. +- [[Delivery Confirmation Flow]] -- happy-path release trigger. +- [[Payout Flow]] -- historical payout context and release mechanics. +- [[Trezor Safekeeping Flow]] -- hardware proof for admin actions. +- [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] -- custody decentralization and smart-contract decision plan. -## Side effects +## Source Files -- **Custodial risk** — the escrow wallet's private key sits with the platform. Lose it → lose all in-flight escrows. Operational controls: hardware wallet, multi-sig, cold storage of the recovery seed. -- **No on-chain escrow contract** — there is no Solidity escrow today. Migration toward a smart-contract escrow (e.g. OpenZeppelin's `Escrow.sol` pattern) would remove custodial trust at the cost of higher complexity and gas. - -## Error / edge cases - -- **Buyer never confirms delivery** → today requires admin intervention. An auto-release timer (e.g. 7 days after `delivered`) is a recommended addition. -- **Seller's wallet address invalid** → payout tx fails or sends to a black hole. Validate `recipientAddress` shape (`^0x[0-9a-fA-F]{40}$`) before signing (`shkeeperPayoutService.ts:62-64` checks `.startsWith('0x')`). -- **Partial payment** (`PARTIAL`) → escrow remains in `pending/partial`; release blocked until full payment arrives. -- **Overpaid** → currently treated as `completed/funded`; the surplus is not auto-refunded. -- **Concurrent release + refund** → blocked by `PaymentCoordinator` serialisation; whichever fires first wins, the other is rejected. -- **Payout fails on chain** → state stays in `releasing` until admin re-runs; consider auto-retry with exponential backoff. -- **Disputed payment** → `escrowState` is **not** auto-changed when a dispute is opened. Admin must explicitly resolve to refund/release. Add a `disputed` boolean or `escrowState='disputed'` to make this more obvious. - -> [!warning] Single custodial wallet = single point of failure -> Centralising all in-flight escrow at one BSC address is the platform's largest operational risk. Use a multi-sig (Gnosis Safe) for the escrow wallet, store one key in HSM, and require two admin signatures for any payout > a threshold. - -> [!tip] Recovering inconsistent state -> If `Payment.escrowState` looks stale (e.g. `released` but no on-chain tx hash), inspect with `Payment.find({ escrowState: 'released', 'blockchain.transactionHash': { $exists: false } })` and reconcile via the SHKeeper invoice or the `fix-transaction-hashes.js` script. - -## Linked flows - -- [[Payment Flow - SHKeeper]] — funds the escrow. -- [[Payment Flow - DePay & Web3]] — alternative funding path. -- [[Delivery Confirmation Flow]] — triggers release. -- [[Dispute Flow]] — can divert to refund. -- [[Payout Flow]] — executes the release transfer. - -## Source files - -- Backend: `backend/src/models/Payment.ts:96-145` (status + escrowState enums) -- Backend: `backend/src/services/payment/shkeeper/shkeeperService.ts:600-647` -- Backend: `backend/src/services/payment/shkeeper/shkeeperWebhook.ts:387-411` -- Backend: `backend/src/services/payment/paymentCoordinator.ts` -- Frontend: `frontend/src/sections/payment/view/payment-list-admin-view.tsx` -- Frontend: `frontend/src/sections/request/components/admin-steps/admin-wallet-payout.tsx` +- Backend: `backend/src/models/Payment.ts` +- Backend: `backend/src/models/FundsLedgerEntry.ts` +- Backend: `backend/src/services/payment/requestNetwork/requestNetworkPayInService.ts` +- Backend: `backend/src/services/payment/safety/transactionSafetyProvider.ts` +- Backend: `backend/src/services/payment/orchestration/releaseRefundService.ts` +- Backend: `backend/src/services/payment/wallets/derivedDestinations.ts` +- Backend: `backend/src/services/payment/wallets/sweepService.ts` +- Backend: `backend/src/services/dispute/releaseHoldService.ts` +- Backend: `backend/src/services/trezor/trezorService.ts` diff --git a/04 - Flows/Negotiation Flow.md b/04 - Flows/Negotiation Flow.md index 63d84c2..14a2c28 100644 --- a/04 - Flows/Negotiation Flow.md +++ b/04 - Flows/Negotiation Flow.md @@ -29,7 +29,7 @@ After an offer is submitted ([[Seller Offer Flow]]), the buyer and seller can ne 1. **Open negotiation chat** — when a buyer first clicks "Chat with seller" on an offer card, the frontend calls `POST /api/chat` to find-or-create a `direct` chat tied to the purchase request (`ChatService.createChat`, `chat.ts:90-192`). The chat's `relatedTo = { type: 'PurchaseRequest', id }` makes it discoverable from the request view. > [!tip] Pre-payment chats vs. post-payment chats -> A negotiation chat may exist **before** the SHKeeper webhook auto-creates the post-payment chat. The `ChatService.createChat` `direct` find-or-create logic (`ChatService.ts:95-108`) prevents duplicates — the same chat object is reused. +> A negotiation chat may exist **before** payment confirmation creates the post-payment chat. The `ChatService.createChat` `direct` find-or-create logic (`ChatService.ts:95-108`) prevents duplicates -- the same chat object is reused. 2. **Status flip to `in_negotiation`** — the first message in the negotiation chat triggers a backend hook (or a manual frontend PATCH) that calls `PurchaseRequestService.updatePurchaseRequest` with `{ status: 'in_negotiation' }`. The status-progression guard allows this (`received_offers → in_negotiation`). @@ -41,7 +41,7 @@ After an offer is submitted ([[Seller Offer Flow]]), the buyer and seller can ne - `SellerOffer.findByIdAndUpdate(id, { ...updateData, updatedAt: now }, { new: true })`. - Emits `purchase-request-update` with `eventType: 'offer-updated'` to `request-{requestId}` (`SellerOfferService.ts:284-288`) — both parties' open tabs refresh. -5. **Buyer accepts** — clicks "Accept this offer", which kicks off [[Payment Flow - SHKeeper]] with the (now-updated) `sellerOfferId`. The webhook flips offer → `accepted` and request → `payment`. +5. **Buyer accepts** -- clicks "Accept this offer", which kicks off [[PRD - Request Network In-House Checkout]] with the selected `sellerOfferId`. Payment confirmation flips offer -> `accepted` and request -> `payment`. 6. **Buyer rejects** — calls `PATCH /api/marketplace/offers/{id}` with `{ status: 'rejected' }`. `SellerOfferService.updateOfferStatus` (`:306-353`) sends `notifyOfferRejected` to the seller and stamps `rejectedAt` + `rejectionReason`. @@ -80,7 +80,7 @@ sequenceDiagram BE->>IO: emit request-{id} 'purchase-request-update' (offer-updated) IO-->>FE_B: refresh offer card alt Buyer accepts - B->>FE_B: Click "Pay" → [[Payment Flow - SHKeeper]] + B->>FE_B: Click "Pay" -> [[PRD - Request Network In-House Checkout]] Note over BE: Webhook PAID flips offer→accepted, request→payment else Buyer rejects B->>FE_B: Click "Reject" @@ -135,7 +135,7 @@ sequenceDiagram ## Linked flows - [[Seller Offer Flow]] — the prior step. -- [[Payment Flow - SHKeeper]] — closes the negotiation with an on-chain payment. +- [[PRD - Request Network In-House Checkout]] — closes the negotiation with an on-chain payment. - [[Chat Flow]] — message-level mechanics, attachments, read receipts. - [[Notification Flow]] — accept/reject notifications. diff --git a/04 - Flows/Payment Flow - DePay & Web3.md b/04 - Flows/Payment Flow - DePay & Web3.md index c4f7bb4..059688f 100644 --- a/04 - Flows/Payment Flow - DePay & Web3.md +++ b/04 - Flows/Payment Flow - DePay & Web3.md @@ -7,7 +7,10 @@ related_apis: ["POST /api/payment/decentralized/create", "POST /api/payment/dece # Payment Flow — DePay & Web3 (Wallet-Direct) -Alternative pay-in path: instead of routing through [[Payment Flow - SHKeeper]], the buyer connects their own wallet (MetaMask / WalletConnect / Coinbase Wallet) and signs an **on-chain transfer to the escrow wallet** directly. The backend then verifies the transaction against the BSC RPC. +> [!warning] Historical/legacy path +> This page describes the older wallet-direct payment path. The current primary checkout is [[PRD - Request Network In-House Checkout]] with Request Network metadata, derived destinations, and Transaction Safety Provider checks. Keep this page for migration and verification context only. + +Legacy alternative pay-in path: the buyer connects their own wallet (MetaMask / WalletConnect / Coinbase Wallet) and signs an **on-chain transfer to the escrow wallet** directly. The backend then verifies the transaction against the BSC RPC. ## Actors @@ -16,8 +19,8 @@ Alternative pay-in path: instead of routing through [[Payment Flow - SHKeeper]], - **Wagmi / WalletConnect / MetaMask** — wallet stack. - **Backend** — `decentralizedPaymentService.ts` (intent), `BSCTransactionVerifier` (on-chain verification), `decentralizedPaymentRoutes.ts`. - **Blockchain (BSC)** — verified via `https://bsc-dataseed.binance.org/` JSON-RPC. -- **MongoDB** — `payments` collection (same model as SHKeeper, different `provider` value). -- **Socket.IO** — `payment-created`, plus the cascade events from [[Payment Flow - SHKeeper]] when verification succeeds. +- **MongoDB** — `payments` collection, with `provider` distinguishing the legacy wallet-direct source from Request Network. +- **Socket.IO** — `payment-created`, plus the funded-escrow cascade events when verification succeeds. ## Preconditions @@ -132,7 +135,7 @@ sequenceDiagram ## Side effects -- **No SHKeeper involvement** — the escrow wallet is custodial; the platform admin holds the keys. Payouts from this wallet to sellers happen via [[Payout Flow]] (SHKeeper payouts API) or manual admin signing using `admin-wallet-payout.tsx` UI. +- **No provider custody** — the escrow wallet is custodial; the platform admin/custody signer controls the keys. Releases from this wallet to sellers should follow [[Payout Flow]] and the Safe/hardware-backed roadmap in [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. - **Verified-wallet field** — the buyer's connected wallet is also saved against `User.profile.walletAddress` (in `WalletConnectionCard`), which is later used to refund this same wallet if the order is disputed. ## Error / edge cases @@ -152,7 +155,8 @@ sequenceDiagram ## Linked flows -- [[Payment Flow - SHKeeper]] — sibling pay-in path; same downstream cascade. +- [[PRD - Request Network In-House Checkout]] — current primary checkout. +- [[Payment Flow - SHKeeper]] — historical sibling pay-in path retained for migration context. - [[Escrow Flow]] — funded state semantics. - [[Payout Flow]] — releasing the funded escrow to the seller. - [[Dispute Flow]] — refunds back to the buyer's verified wallet. diff --git a/04 - Flows/Payment Flow - SHKeeper.md b/04 - Flows/Payment Flow - SHKeeper.md index fbcf7be..52c2b63 100644 --- a/04 - Flows/Payment Flow - SHKeeper.md +++ b/04 - Flows/Payment Flow - SHKeeper.md @@ -7,6 +7,9 @@ related_apis: ["POST /api/payment/shkeeper/create", "POST /api/payment/shkeeper/ # Payment Flow — SHKeeper (Crypto Pay-In) +> [!warning] Historical migration document +> This page describes the older SHKeeper pay-in rail. It is retained for migration/reconciliation context only. The current primary pay-in path is [[PRD - Request Network In-House Checkout]], and the current escrow/custody model is [[Escrow Flow]] plus [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. + End-to-end **crypto pay-in** via the self-hosted [SHKeeper](https://github.com/vsys-host/shkeeper.io) gateway at `pay.amn.gg`. The buyer pays in stablecoins/crypto; SHKeeper monitors the blockchain, sends webhooks back, and the backend marks the escrow funded. ## Supported assets diff --git a/04 - Flows/Payout Flow.md b/04 - Flows/Payout Flow.md index 11d8a22..e826a3b 100644 --- a/04 - Flows/Payout Flow.md +++ b/04 - Flows/Payout Flow.md @@ -1,133 +1,130 @@ --- title: Payout Flow -tags: [flow, payment, payout, shkeeper, seller] -related_models: ["[[Payment]]"] -related_apis: ["POST /api/payment/shkeeper/payout", "GET /api/payment/shkeeper/payout/:taskId"] +tags: [flow, payment, payout, release, refund, custody] +related_models: ["[[Payment]]", "[[Funds Ledger and Escrow State Machine Specification]]"] +related_apis: ["POST /api/payment/:id/release", "POST /api/payment/:id/release/confirm", "POST /api/payment/:id/refund", "POST /api/payment/:id/refund/confirm"] --- # Payout Flow -How the **seller receives the escrowed crypto** once the order is complete. Two variants are implemented: +This page describes how escrowed funds leave Amanat custody after an order is complete or a dispute is resolved. -1. **SHKeeper Payouts API** (`shkeeperPayoutService.ts`) — the gateway signs and broadcasts on behalf of the platform. -2. **Manual admin wallet payout** (`admin-wallet-payout.tsx`) — an admin connects their own wallet and signs the transfer; the tx hash is reported back to the backend. +The current flow is no longer SHKeeper payout-task centric. Release and refund are instruction-based: -Both result in `Payment.escrowState = 'released'` and an outgoing `Payment` record with `direction: 'out'`. +1. Backend validates policy, dispute hold, and ledger availability. +2. Backend builds a release/refund instruction. +3. A custody signer executes the on-chain transaction. +4. Backend confirms the tx hash and appends the ledger entry. + +Today the custody signer can be an admin/Trezor path when enabled. The roadmap target is Safe multisig execution before any custom escrow contract pilot. See [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. ## Actors -- **Admin** (or scheduled system trigger) — initiates the payout. -- **Seller** — recipient, has saved their wallet address under `User.profile.walletAddress`. -- **Backend** — `shkeeperPayoutService.createPayoutTask` and the manual confirmation routes. -- **SHKeeper Payouts API** — `POST https://pay.amn.gg/api/v1/payout` (per SHKeeper docs). -- **Blockchain (BSC)** — final on-chain settlement. -- **MongoDB** — separate `Payment` document with `direction: 'out'`. +- **Admin / mediator** -- initiates release/refund after delivery confirmation or dispute resolution. +- **Custody signer** -- Trezor proof today when enabled; target state is Safe multisig owners. +- **Seller** -- recipient for release. +- **Buyer** -- recipient for refund. +- **Backend** -- `releaseRefundService.ts`, payment adapter, ledger service, Trezor service. +- **Blockchain** -- final on-chain settlement. +- **MongoDB** -- `Payment` and `FundsLedgerEntry`. ## Preconditions -- The original pay-in `Payment` has `escrowState = 'funded'` (or `releasable`). -- The seller has set `profile.walletAddress` (validated `^0x...` format). -- The corresponding `PurchaseRequest` is in a status that allows payout (`delivered`, `confirming`, `seller_paid`, or `completed`). +- The pay-in `Payment` is funded or releasable. +- The release/refund amount is positive and does not exceed available ledger balance. +- No active dispute hold blocks the operation, unless the operation is the explicit dispute resolution path. +- Recipient wallet is known and verified. +- If `TREZOR_SAFEKEEPING_REQUIRED=true`, the confirm step includes the expected Trezor operation signature. +- Production target: Safe multisig execution is required for custody movement. -## Step-by-step narrative +## Release Narrative -### SHKeeper-mediated payout +1. Buyer confirms delivery, an auto-release policy matures, or a dispute resolves for the seller. +2. Admin calls `POST /api/payment/:id/release` with optional partial amount. +3. Backend loads the `Payment`, validates ledger availability when `PAYMENT_LEDGER_ENFORCEMENT=true`, and returns an instruction payload. +4. Custody signer broadcasts the seller payment transaction. +5. Admin calls `POST /api/payment/:id/release/confirm` with `txHash` and optional Trezor proof. +6. Backend verifies signer proof when required, confirms adapter state, appends a `release` ledger entry, and marks escrow released. -1. Admin (or the auto-release scheduler — not yet implemented) hits `POST /api/payment/shkeeper/payout` with `{ purchaseRequestId, sellerOfferId, buyerId, sellerId, amount, recipientAddress, token?, network? }`. -2. Backend `shkeeperPayoutService.createPayoutTask` (`shkeeperPayoutService.ts:40-150`): - - Validates ObjectIds and the `recipientAddress` (`startsWith('0x')`). - - **Idempotency**: `Payment.findOne({ purchaseRequestId, sellerOfferId, sellerId, provider:'shkeeper', direction:'out', status: { $in:['pending','processing','completed'] } })` — if found, reuses it. - - Creates a new `Payment` document with `direction: 'out'`, `escrowState: 'releasing'`, `blockchain.receiver = recipientAddress`. - - Calls SHKeeper Payouts API (`POST /api/v1/payout`) with the body documented at . SHKeeper returns a `task_id`. - - Stores `Payment.providerPaymentId = task_id`, `metadata.shkeeperTaskId = task_id`, `metadata.payoutType = 'seller-payment'`. -3. Polling or webhook: when SHKeeper completes the payout, it pushes a webhook (or the backend polls `GET /api/v1/payout/{task_id}`) and the system flips `Payment.status = 'completed'`, `escrowState = 'released'`, populates `blockchain.transactionHash`. -4. The original pay-in `Payment` is updated in tandem: `escrowState = 'released'`, `PurchaseRequest.status = 'seller_paid'` → `completed`. -5. Notifications: `notifyPayoutSent` to the seller, internal admin log. +## Refund Narrative -### Manual admin payout +1. Dispute resolves for the buyer, order is cancelled before fulfillment, or support executes an approved recovery. +2. Admin calls `POST /api/payment/:id/refund`. +3. Backend validates available funds and policy. +4. Custody signer broadcasts the refund transaction. +5. Admin calls `POST /api/payment/:id/refund/confirm` with `txHash` and optional Trezor proof. +6. Backend appends a `refund` ledger entry and marks escrow refunded. -1. Admin opens the request detail in the admin view; the admin-step component `admin-wallet-payout.tsx` shows the recipient and amount. -2. Admin connects their wallet (`useWeb3` / `web3Service.connect()`). -3. Admin clicks "Send payout"; wagmi triggers `transfer(recipient, amount)` on the USDT contract. -4. After confirmation, the admin clicks "Confirm in system", which POSTs `POST /api/payment/admin/confirm-tx/:paymentId` with `{ txHash, kind: 'release' }`. -5. Backend `confirmAdminTx(paymentId, txHash, 'release')` (`shkeeperService.ts:628-647`) sets `status: 'completed'`, `escrowState: 'released'`, `blockchain.transactionHash = txHash`. - -### Sequence diagram (SHKeeper payout) +## Sequence Diagram ```mermaid sequenceDiagram autonumber - actor A as Admin/System + actor A as Admin + actor C as Custody signer participant BE as Backend participant DB as MongoDB - participant SK as SHKeeper Payout API - participant BC as BSC - actor S as Seller + participant BC as EVM Chain + actor R as Recipient - A->>BE: POST /api/payment/shkeeper/payout - BE->>DB: Payment.create({direction:"out", escrowState:"releasing"}) - BE->>SK: POST /api/v1/payout {to, amount, crypto} - SK-->>BE: { task_id, status:"pending" } - BE->>DB: Payment.providerPaymentId=task_id - SK->>BC: signed payout tx (managed wallet) - BC-->>SK: confirmed - SK->>BE: webhook payout-completed (or BE polls) - BE->>DB: Payment.status="completed"\nescrowState="released"\ntxHash - BE->>DB: pay-in Payment.escrowState="released"\nPurchaseRequest.status="seller_paid" - BE->>S: notifyPayoutSent + A->>BE: POST /api/payment/{id}/release or refund + BE->>DB: Load Payment + FundsLedger balance + BE->>BE: Check dispute hold + ledger availability + BE-->>A: unsigned release/refund instruction + A->>C: Request Trezor/Safe execution + C->>BC: Broadcast transfer + BC-->>C: txHash + A->>BE: POST /confirm { txHash, signer proof } + BE->>BE: Verify proof if required + BE->>DB: append release/refund ledger entry + BE->>DB: update Payment escrowState + BE-->>R: notification ``` -## API calls +## API Calls -| Method | Endpoint | Source | +| Method | Endpoint | Purpose | |---|---|---| -| `POST` | `/api/payment/shkeeper/payout` | `shkeeperPayoutRoutes.ts` → `createPayoutTask` | -| `GET` | `/api/payment/shkeeper/payout/:taskId` | Polls SHKeeper task status | -| `POST` | `/api/payment/admin/confirm-tx/:paymentId` | Manual admin confirmation | -| `GET` | `/api/payment/admin/payouts` | List payouts (admin dashboard) | +| `POST` | `/api/payment/:id/release` | Build release instruction | +| `POST` | `/api/payment/:id/release/confirm` | Confirm release transaction | +| `POST` | `/api/payment/:id/refund` | Build refund instruction | +| `POST` | `/api/payment/:id/refund/confirm` | Confirm refund transaction | +| `GET` | `/api/admin/payments/awaiting-confirmation` | Admin view of payments blocked on confirmation depth | +| `GET` | `/api/payment/derived-destinations` | Admin view of derived destination sweep state | -## Database writes +## Database Writes -- **`payments`** — new outgoing document; updates to `status`, `escrowState`, `blockchain.transactionHash` as the task progresses. -- **`payments`** (pay-in counterpart) — `escrowState = 'released'`. -- **`purchaserequests`** — `status` advances to `seller_paid` → `completed`. -- **`notifications`** — seller payout receipt. +- **`payments`** -- status, `escrowState`, `blockchain.transactionHash`, signer metadata. +- **`funds_ledger_entries`** -- append-only `release` or `refund` entry with idempotency key. +- **`purchaserequests`** -- terminal business state after release/refund completes. +- **`notifications`** -- release/refund receipt to the relevant party. -## Socket events emitted +## Error / Edge Cases -- **`payment-status`** (admin) on each transition. -- **`purchase-request-update`** `status-changed`. +- **Insufficient ledger balance** -- reject instruction build/confirm. +- **Active dispute hold** -- reject release/refund unless the operation is the explicit dispute outcome. +- **Missing signer proof** -- reject when `TREZOR_SAFEKEEPING_REQUIRED=true`. +- **Custody tx sent but not confirmed in app** -- reconcile by tx hash and append the missing ledger entry once verified. +- **Partial split** -- build separate release and refund instructions whose sum does not exceed available balance. +- **Payout reverted** -- leave escrow in failed/retryable state and do not append the terminal ledger entry. -## Side effects +## Legacy SHKeeper Note -- **`fix-transaction-hashes.js`** at repo root (`backend/fix-transaction-hashes.js`) — script used to backfill missing `blockchain.transactionHash` on payouts where the SHKeeper webhook arrived without the txid (e.g. signature length mismatch in dev). Run locally with the same Mongo URI to repair stale documents. Use it as the reference for the data-fix pattern — pull recent payouts, query SHKeeper for invoice/task details, write back the hash. -- **Hash repair** — periodic reconciliation against SHKeeper invoice GET endpoints ensures bookkeeping accuracy. +Older versions used SHKeeper payout tasks and scripts such as `fix-transaction-hashes.js`. Those references remain useful for historical reconciliation, but new release/refund work should use the instruction, ledger, and custody-signer flow described here. -## Error / edge cases +## Linked Flows -- **Invalid recipient address** → throws synchronously, no DB record created. -- **SHKeeper insufficient hot-wallet balance** → SHKeeper returns an error; payout task stays `pending`, backend logs. -- **Duplicate payout request** → idempotency: existing payment returned with no extra SHKeeper call. -- **Payout reverted on chain** → SHKeeper marks the task `failed`; backend sets `Payment.status = 'failed'`, `escrowState = 'failed'`. Admin retries. -- **Missing `transactionHash` after success** → use `fix-transaction-hashes.js` to backfill. -- **Manual payout signed but never confirmed in system** → on-chain transfer happened, but `Payment.escrowState` stays `releasing`. Admin can run a reconciliation script that scans the escrow wallet's outgoing txs and matches by amount/timestamp. -- **Seller changes wallet address mid-flight** → the saved `recipientAddress` is the snapshot taken at payout creation; subsequent profile changes do not affect in-flight payouts. +- [[Escrow Flow]] -- sets up the conditions under which release/refund is allowed. +- [[Delivery Confirmation Flow]] -- happy-path release trigger. +- [[Dispute Flow]] -- can divert release to refund or split. +- [[Trezor Safekeeping Flow]] -- hardware-backed operation approval. +- [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] -- Safe-first custody roadmap. -> [!warning] Auto-release is not yet implemented -> Today, payouts are admin-initiated. The flow is ready for an automatic trigger when [[Delivery Confirmation Flow]] completes — implement a cron job or queue worker that scans for `PurchaseRequest.status='delivered'` and auto-creates payouts after a configurable grace period. +## Source Files -## Linked flows - -- [[Escrow Flow]] — sets up the conditions under which payout is allowed. -- [[Delivery Confirmation Flow]] — green-lights the payout. -- [[Dispute Flow]] — can divert funds to a refund instead. -- [[Notification Flow]] — payout receipt to seller. - -## Source files - -- Backend: `backend/src/services/payment/shkeeper/shkeeperPayoutService.ts` -- Backend: `backend/src/services/payment/shkeeper/shkeeperPayoutRoutes.ts` -- Backend: `backend/src/services/payment/shkeeper/shkeeperService.ts:614-647` (build & confirm admin tx payload) -- Backend: `backend/fix-transaction-hashes.js` (reconciliation script) -- Frontend: `frontend/src/sections/request/components/admin-steps/admin-wallet-payout.tsx` -- Frontend: `frontend/src/web3/web3Service.ts` +- Backend: `backend/src/services/payment/orchestration/releaseRefundService.ts` +- Backend: `backend/src/services/payment/ledger/fundsLedgerService.ts` +- Backend: `backend/src/services/payment/adapters/requestNetworkAdapter.ts` +- Backend: `backend/src/services/trezor/trezorService.ts` +- Backend: `backend/src/services/dispute/releaseHoldService.ts` +- Frontend: admin payment/release/refund surfaces under `frontend/src/sections/` diff --git a/04 - Flows/Purchase Request Flow.md b/04 - Flows/Purchase Request Flow.md index 746d1e9..00f7f1b 100644 --- a/04 - Flows/Purchase Request Flow.md +++ b/04 - Flows/Purchase Request Flow.md @@ -34,7 +34,7 @@ stateDiagram-v2 pending --> received_offers: first SellerOffer saved\nSellerOfferService.createOffer received_offers --> in_negotiation: buyer/seller chat\n(counter-offer, see [[Negotiation Flow]]) in_negotiation --> received_offers: counter rejected - received_offers --> payment: SHKeeper webhook PAID\n(selected offer) + received_offers --> payment: Request Network payment confirmed\n(selected offer) in_negotiation --> payment: same payment --> processing: seller acknowledges processing --> delivery: seller marks shipped @@ -151,7 +151,7 @@ sequenceDiagram ## Database writes -- **`purchaserequests` collection**: full insert. Subsequent status transitions and `selectedOfferId` updates happen in [[Seller Offer Flow]], [[Payment Flow - SHKeeper]], and [[Delivery Confirmation Flow]]. +- **`purchaserequests` collection**: full insert. Subsequent status transitions and `selectedOfferId` updates happen in [[Seller Offer Flow]], [[PRD - Request Network In-House Checkout]], and [[Delivery Confirmation Flow]]. - **`notifications` collection**: one per notified seller plus one for the buyer. - **`users.referralStats`** is not touched at request creation. @@ -186,7 +186,7 @@ sequenceDiagram - [[Seller Offer Flow]] — sellers respond to the published request. - [[Negotiation Flow]] — counter-offer mechanics in `in_negotiation`. -- [[Payment Flow - SHKeeper]] — buyer pays for the accepted offer. +- [[PRD - Request Network In-House Checkout]] — buyer pays for the accepted offer. - [[Delivery Confirmation Flow]] — seller ships, buyer confirms. - [[Dispute Flow]] — escape hatch for failed deliveries. - [[Notification Flow]] — backbone of the seller fan-out. diff --git a/04 - Flows/Referral Flow.md b/04 - Flows/Referral Flow.md index f35f05a..65e4180 100644 --- a/04 - Flows/Referral Flow.md +++ b/04 - Flows/Referral Flow.md @@ -149,7 +149,7 @@ sequenceDiagram - [[Registration Flow]] — attribution point. - [[Google OAuth Flow]] — also supports `referralCode`. - [[Notification Flow]] — `referral-signup`, `level-up`, and points events surface here. -- [[Payment Flow - SHKeeper]] — completion of a purchase is the canonical trigger for awarding referral commission. +- [[PRD - Request Network In-House Checkout]] / [[Escrow Flow]] — completion of a purchase is the canonical trigger for awarding referral commission. ## Source files diff --git a/04 - Flows/Seller Offer Flow.md b/04 - Flows/Seller Offer Flow.md index 6cc2b6f..caec181 100644 --- a/04 - Flows/Seller Offer Flow.md +++ b/04 - Flows/Seller Offer Flow.md @@ -7,7 +7,7 @@ related_apis: ["POST /api/marketplace/offers", "GET /api/marketplace/offers/requ # Seller Offer Flow -A **seller** browses open purchase requests and submits an offer with a price, delivery time, and notes. The buyer is notified in real time and can accept (which moves the request to [[Payment Flow - SHKeeper]]) or reject. +A **seller** browses open purchase requests and submits an offer with a price, delivery time, and notes. The buyer is notified in real time and can accept (which moves the request to [[PRD - Request Network In-House Checkout]]) or reject. ## Actors @@ -34,7 +34,7 @@ stateDiagram-v2 pending --> active: (optional — manual seller activation) pending --> withdrawn: seller withdraws (only while pending) pending --> rejected: another offer accepted\nor buyer rejects this one - pending --> accepted: acceptOffer()\nor SHKeeper PAID webhook + pending --> accepted: acceptOffer()\nor payment confirmed accepted --> [*] rejected --> [*] withdrawn --> [*] @@ -79,14 +79,14 @@ The active enum values are `pending | accepted | rejected | withdrawn` (`SellerO ### Accept → Payment -14. The buyer's "Pay this offer" button kicks off [[Payment Flow - SHKeeper]] with `purchaseRequestId` and `sellerOfferId`. The offer is **not** immediately marked `accepted`; the SHKeeper webhook does that atomically when the on-chain payment is confirmed. -15. On `PAID`/`OVERPAID` webhook (see `backend/src/services/payment/shkeeper/shkeeperWebhook.ts:573-714`): +14. The buyer's "Pay this offer" button kicks off [[PRD - Request Network In-House Checkout]] with `purchaseRequestId` and `sellerOfferId`. The offer is **not** immediately marked `accepted`; payment confirmation does that atomically when the on-chain payment is confirmed. +15. On Request Network payment confirmation: - The selected offer's `status` → `accepted`. - All other offers on the same request → `rejected` via `SellerOffer.updateMany`. - The purchase request: `status = "payment"`, `selectedOfferId = sellerOfferId`. - A direct chat is created (see [[Chat Flow]]). - Notifications: `notifyOfferAccepted` to the winning seller, generic rejection notifications to the others (`SellerOfferService.acceptOffer` does the same in the manual path). - - Socket events: `seller-offer-update` `payment-completed` to the winner, `seller-offer-update` `offer-rejected` to losers (`shkeeperWebhook.ts:679-705`). + - Socket events notify the winner and reject/close competing offers. ### Withdrawal @@ -127,7 +127,7 @@ sequenceDiagram BE-->>FE_B: offers alt B->>FE_B: Click pay to finish selected offer - B->>FE_B: SHKeeper webhook handles payment result + B->>FE_B: Request Network payment confirms else B->>FE_B: Open chat to negotiate end @@ -171,7 +171,7 @@ sequenceDiagram - **Price = 0 or negative** → Mongoose validator on `SellerOffer.price.amount` rejects (`SellerOfferService.ts:55-60` logs the validation state). - **Seller withdraws an `accepted` offer** → blocked by the `{ status: 'pending' }` filter; returns `null`. - **`validUntil` in the past at creation** → schema-level validator should reject; otherwise the next `markExpiredOffersAsWithdrawn` cron run flips it to `withdrawn`. -- **Race condition: two payments to two different offers** → unlikely (frontend disables payment buttons once one is chosen); even if both arrive, the SHKeeper webhook coordinator (`PaymentCoordinator`) is idempotent and the first PAID wins. +- **Race condition: two payments to two different offers** → unlikely (frontend disables payment buttons once one is chosen); even if both arrive, `PaymentCoordinator` and provider idempotency decide which confirmed payment wins. - **Offer for a deleted request** → orphan; the webhook handler logs `"Purchase request not found"` and continues. Periodic cleanup should remove orphans. > [!tip] Real-time UX @@ -181,7 +181,7 @@ sequenceDiagram - [[Purchase Request Flow]] — produces the requests sellers offer on. - [[Negotiation Flow]] — counter-offer in `in_negotiation`. -- [[Payment Flow - SHKeeper]] — locks in the accepted offer. +- [[PRD - Request Network In-House Checkout]] — locks in the accepted offer. - [[Chat Flow]] — direct chat opened after payment. - [[Notification Flow]] — channels for offer events. - [[Rating Flow]] — seller's average rating displayed in the offer card. @@ -191,7 +191,7 @@ sequenceDiagram - Backend: `backend/src/services/marketplace/SellerOfferService.ts` - Backend: `backend/src/services/marketplace/marketplaceController.ts` - Backend: `backend/src/models/SellerOffer.ts` -- Backend: `backend/src/services/payment/shkeeper/shkeeperWebhook.ts:573-714` (acceptance via webhook) +- Backend: `backend/src/services/payment/paymentCoordinator.ts` (payment-state cascade) - Frontend: `frontend/src/sections/request/components/seller-steps/step-1-send-proposal.tsx` - Frontend: `frontend/src/sections/request/components/buyer-steps/step-3-select-and-pay.tsx` - Frontend: `frontend/src/app/dashboard/seller/marketplace/` diff --git a/04 - Flows/Trezor Safekeeping Flow.md b/04 - Flows/Trezor Safekeeping Flow.md index d0c8067..c5dc9a3 100644 --- a/04 - Flows/Trezor Safekeeping Flow.md +++ b/04 - Flows/Trezor Safekeeping Flow.md @@ -8,7 +8,7 @@ Default mode: optional. Existing release/refund flows do not require Trezor proo - Generate a fresh receive address per user/payment from a registered Trezor xpub. - Require a Trezor-produced signature before release/refund confirmation when safekeeping enforcement is enabled. -- Keep SHKeeper and Request Network optional provider paths intact. +- Keep the Request Network payment adapter and legacy provider abstractions intact while adding custody controls. - Preserve the existing `Payment` model and orchestration surface. ## Registration @@ -95,7 +95,7 @@ When `TREZOR_SAFEKEEPING_REQUIRED=true`, `confirmReleaseRefundInstruction` verif TREZOR_SAFEKEEPING_REQUIRED=false ``` -Default is permissive so existing SHKeeper and Request Network flows continue to work. Set it to `true` only after registering the operating admin's Trezor account and testing the signing path. Any value other than the literal string `true` is treated as disabled. +Default is permissive so existing Request Network release/refund flows continue to work. Set it to `true` only after registering the operating admin's Trezor account and testing the signing path. Any value other than the literal string `true` is treated as disabled. ## Safety Rules @@ -108,7 +108,7 @@ Default is permissive so existing SHKeeper and Request Network flows continue to ## Upgrade Path To Multisig -The current design stores a single `trezor-eoa` signer. Later, replace the signer policy with: +The current design stores a single `trezor-eoa` signer. The recommended production path is to replace the signer policy with: - `addressType: safe-multisig` - a Safe address per tenant/admin group @@ -116,4 +116,4 @@ The current design stores a single `trezor-eoa` signer. Later, replace the signe - Trezor owners as Safe signers - release/refund flow creates a Safe transaction and records collected signatures before execution -The payment orchestration API should stay the same: build instruction, collect hardware-backed approval, confirm release/refund, append ledger entry. +The payment orchestration API should stay the same: build instruction, collect hardware-backed approval, confirm release/refund, append ledger entry. See [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] for the staged Safe-first path before any custom escrow contract. diff --git a/06 - Usage/Admin Guide.md b/06 - Usage/Admin Guide.md index 334167a..7be9b89 100644 --- a/06 - Usage/Admin Guide.md +++ b/06 - Usage/Admin Guide.md @@ -190,9 +190,9 @@ If you see repeat disputes against the same seller (or repeat frivolous disputes **Dashboard → Payment → List** shows all payments with filters by status, provider, network, time range. Watch for: -- **Stuck payments** (pending > 1h) — SHKeeper webhook may have failed; check logs. -- **Failed webhooks** — SHKeeper retried but signature didn't verify; see [[Payment API]]. -- **Missing tx hashes** on completed payments — run the repair script (see §6.3). +- **Stuck payments** (pending > 1h) — Request Network webhook/reconciliation may not have completed; check webhook logs and derived-destination balances. +- **Failed webhooks** — Request Network signature verification or payload validation failed; see [[Payment API]] and [[Request Network Integration Constraints]]. +- **Missing tx hashes** on completed payments — use the payment console or reconciliation job to fetch and verify the on-chain transaction before any release. ### 6.2 Manual payout @@ -202,18 +202,18 @@ For sellers who can't access self-service or for one-off ops: 2. Fields: recipient address, amount, token (USDT…), network (BSC…), reference, description. 3. Submit → ts-node script also exists at `backend/manual-payout-test.ts` for local testing. -Behind the scenes this calls SHKeeper's payout endpoint. See [[Payout Flow]]. +Behind the scenes this should create a release/refund instruction and ledger entry, then route signing through the configured custody signer. See [[Payout Flow]] and [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]]. ### 6.3 Fix missing transaction hashes -Some completed payments may lack the on-chain tx hash (webhook race, partial confirmation). Run: +Some completed payments may lack the on-chain tx hash (webhook race, callback delay, or partial reconciliation). Prefer the admin payment console or Request Network reconciliation tooling. For older SHKeeper records only, use the historical repair script: ```bash cd /Users/mojtabaheidari/code/backend node fix-transaction-hashes.js ``` -The script polls SHKeeper for each affected invoice and patches `transactionHash` + `blockchain.transactionHash` in MongoDB. +The legacy script polls SHKeeper for each affected historical invoice and patches `transactionHash` + `blockchain.transactionHash` in MongoDB. Do not use it for new Request Network payments. See [[Scripts]] for the full inventory. diff --git a/06 - Usage/Support Guide.md b/06 - Usage/Support Guide.md index bbdf051..07f4be4 100644 --- a/06 - Usage/Support Guide.md +++ b/06 - Usage/Support Guide.md @@ -304,7 +304,7 @@ Bookmark these for instant reference: - [[Seller Guide]] — common Seller questions - [[Glossary]] — terminology reference - [[Authentication Flow]] · [[Password Reset Flow]] · [[Passkey (WebAuthn) Flow]] — how auth actually works -- [[Payment Flow - SHKeeper]] · [[Payment Flow - DePay & Web3]] — how payments flow +- [[Escrow Flow]] · [[Request Network Integration Constraints]] · [[Payout Flow]] — how payments flow - [[Dispute Flow]] — when refund requests need to go to dispute - [[Notification Flow]] — why a user might not have received an email - [[Error Codes]] — interpret HTTP errors / app-specific codes the user reports diff --git a/08 - Operations/Incident Response.md b/08 - Operations/Incident Response.md index 62826fa..afebaed 100644 --- a/08 - Operations/Incident Response.md +++ b/08 - Operations/Incident Response.md @@ -13,7 +13,7 @@ Runbooks for the most likely production incidents, plus communication templates | Sev | Meaning | Response time | Examples | |-----|---------|---------------|----------| -| **Sev 1** | Site fully down or unable to process payments | 15 min | Backend container in crashloop; Mongo unreachable; SHKeeper API permanently failing | +| **Sev 1** | Site fully down or unable to process payments | 15 min | Backend container in crashloop; Mongo unreachable; Request Network API/webhooks failing | | **Sev 2** | Major feature broken for a large share of users | 1 hour | Email sending broken; Redis disk full; chat undelivered | | **Sev 3** | Minor / cosmetic issue, isolated user reports | next business day | Single failed webhook; one user can't upload PDF | | **Sev 4** | No user impact, hygiene item | backlog | Backup older than 24h; disk > 80%; missed deploy | @@ -133,35 +133,34 @@ The app gracefully degrades when Redis is unreachable for short windows — don' --- -### 3.4 SHKeeper API down (payments blocked) +### 3.4 Request Network API/webhook down (payments degraded) -**Symptoms.** Backend logs show repeated `SHKeeper request failed: ECONNREFUSED` or non-2xx responses from `$SHKEEPER_API_URL`. Buyers see "Payment unavailable" in checkout. Sev 1 — money is involved. +**Symptoms.** Backend logs show repeated Request Network API failures, webhook delivery failures, or payments stuck in pending/safety-pending. Buyers see "Payment unavailable" or a checkout that never confirms. Sev 1 — money is involved. **Runbook.** ```bash -# 1. Confirm SHKeeper itself is reachable -curl -fsS -H "X-Shkeeper-Api-Key: $SHKEEPER_API_KEY" \ - "$SHKEEPER_API_URL/api/v1/healthcheck" +# 1. Confirm Request Network API is reachable +curl -fsS -H "Authorization: Bearer $REQUEST_NETWORK_API_KEY" \ + "$REQUEST_NETWORK_API_BASE_URL" -# 2. If 5xx from SHKeeper → it's their side +# 2. If 5xx from Request Network -> provider/API side # - Check their status page / contact provider # - Toggle a banner in the frontend warning buyers -# - Consider switching SHKEEPER_FORCE_PAYOUT_DEMO=true so QA still works -# (do NOT do this for real customer money) +# - Pause new checkout creation if confirmations cannot be reconciled # 3. If our network can't reach it: # - test from the host: curl from the host vs from inside the container -docker exec nickapp-backend curl -v "$SHKEEPER_API_URL" +docker exec nickapp-backend curl -v "$REQUEST_NETWORK_API_BASE_URL" # - DNS / firewall changes? # 4. While blocked, monitor stuck payments docker exec nickapp-mongodb mongosh --eval \ "use marketplace; db.payments.find({status:'pending', createdAt:{\$lt: new Date(Date.now() - 30*60*1000)}}).count()" -# 5. Once SHKeeper is back, the app retries automatically. Verify the -# backlog drains. If a payment is stuck > 24h, manually verify against -# SHKeeper and use fix-transaction-hashes.js if needed. +# 5. Once Request Network/webhook delivery is back, replay or reconcile +# pending events. If a payment is stuck > 24h, manually verify the +# on-chain transfer, Transaction Safety Provider result, and ledger state. ``` **Always communicate.** Even short payment outages erode trust — post a status update. @@ -422,7 +421,7 @@ Store postmortems alongside this vault — suggested path `/Users/mojtabaheidari | Payments lead | | | DM | | Infrastructure | | | DM | | Product / customer comms | | | #customer-comms | -| SHKeeper provider contact | | — | email | +| Request Network/provider contact | | — | email | | SMTP provider | | — | email | --- diff --git a/08 - Operations/Monitoring.md b/08 - Operations/Monitoring.md index 16cc5fd..69ecf36 100644 --- a/08 - Operations/Monitoring.md +++ b/08 - Operations/Monitoring.md @@ -134,7 +134,7 @@ Notable log lines to look for: | `🚀 Server running on port 5001` | App fully started | | `🔌 User connected: ` | Socket.IO connection | | `📥` | Inbound HTTP request log | -| `💳 SHKeeper` | SHKeeper webhook / API call | +| `💳 Request Network` | Request Network webhook / API call | | `🔐 Webhook verification` | Webhook signature check result | | `❌ Error` | Manual error log (also captured by Sentry) | @@ -183,7 +183,7 @@ Today these are read manually from logs / Sentry. As Prometheus is added, encode | Webhook signature failures | log `Webhook verification failed` | 0 | > 0 | | Request Network webhook 4xx | nginx access log `/api/payment/request-network/webhook` | 0 | any real provider delivery returning 4xx | | Request Network safety-pending payments | `db.payments.find({"metadata.transactionSafety.status":"pending"})` | explained/short-lived | pending > 10 min without operator note | -| SHKeeper API errors (5xx) | log + Sentry | 0 | > 5/min sustained | +| Request Network API errors (5xx) | log + Sentry | 0 | > 5/min sustained | | Payouts stuck in `pending` > 30 min | `db.payments.find({type:'payout',status:'pending',createdAt:{$lt:ISODate(30 min ago)}})` | empty | non-empty | | Missing `transactionHash` after `completed` | the same query that drives `fix-transaction-hashes.js` | empty | non-empty | diff --git a/08 - Operations/Payment and Trezor Verification Report.md b/08 - Operations/Payment and Trezor Verification Report.md index 544466c..29e1f8c 100644 --- a/08 - Operations/Payment and Trezor Verification Report.md +++ b/08 - Operations/Payment and Trezor Verification Report.md @@ -10,7 +10,7 @@ Date: 2026-05-24 Scope: - Task 3 provider-neutral payment migration. -- Request Network optional pay-in, webhook, and reconciliation support. +- Request Network primary pay-in, webhook, and reconciliation support. - Internal funds ledger and release/refund ledger gates. - Optional Trezor safekeeping support. @@ -84,7 +84,7 @@ Before enabling Request Network for a non-test cohort: 2. Run backend typecheck. 3. Test one Request Network sandbox pay-in with webhook callback. 4. Confirm reconciliation dry-run output is empty or expected. -5. Keep `PAYMENT_ROLLBACK_PROVIDER=shkeeper`. +5. Keep the Request Network rollback/support runbook current; SHKeeper is historical context, not the current primary rollback target. Before enabling Trezor safekeeping enforcement: diff --git a/09 - Audits/Activity Log.md b/09 - Audits/Activity Log.md index 526fdc2..a415299 100644 --- a/09 - Audits/Activity Log.md +++ b/09 - Audits/Activity Log.md @@ -11,6 +11,18 @@ entries on top. Maintained by agents per the rule in `../AGENTS.md`. --- +### 2026-05-28 — backend@19f7eb9, frontend@60ee6fb — Task #10: AML screening (Chainalysis, seller-paid, seller opt-in) + +**Commits:** backend `441c8be` → `80ba046` → `19f7eb9` (2.6.46 → 2.6.47), frontend `717d5c8` → `b7540f5` → `60ee6fb` (2.6.46 → 2.6.47) +**Touched:** +- Backend: `src/services/payment/safety/amlProvider.ts`, `src/services/payment/safety/chainalysisProvider.ts`, `src/services/payment/safety/amlScreeningService.ts`, `src/services/payment/safety/transactionSafetyProvider.ts`, `src/services/payment/paymentCoordinator.ts`, `src/services/admin/amlConfigRoutes.ts`, `src/models/SellerOffer.ts`, `src/app.ts`, `.env.example` +- Frontend: `src/sections/request/components/seller-steps/step-1-send-proposal.tsx`, `src/types/marketplace.ts` +**Why:** Task #10 implementation. Chainalysis Public Sanctions API integration for seller-paid AML screening. Seller can opt-in per-offer via `requireAmlCheck` + `amlBlockOnFailure` toggles. `TransactionSafetyProvider` screens buyer source address after on-chain transfer verification. `paymentCoordinator` deducts `AML_CHECK_COST_USD` (default 0, API is free) from seller escrow on payment completion. Admin routes for AML config. +**Verification:** Frontend `tsc --noEmit` clean. Backend relevant tests pass (module resolution issues in unrelated test files). +**Linked docs updated:** [[02 - Data Models/SellerOffer]], [[03 - API Reference/Admin API]], [[04 - Flows/Escrow Flow]] + +--- + ### 2026-05-28 — backend@441c8be, frontend@717d5c8 — Task #9: Per-chain confirmation thresholds + admin UI **Commits:** backend `4a85737` → `441c8be` (2.6.47 → 2.6.48), frontend `0ebb2f1` → `717d5c8` (2.6.46 → 2.6.48) diff --git a/PRD - Decentralized Custody and Smart-Contract Escrow Roadmap.md b/PRD - Decentralized Custody and Smart-Contract Escrow Roadmap.md new file mode 100644 index 0000000..822ad24 --- /dev/null +++ b/PRD - Decentralized Custody and Smart-Contract Escrow Roadmap.md @@ -0,0 +1,195 @@ +--- +title: PRD - Decentralized Custody and Smart-Contract Escrow Roadmap +tags: [prd, escrow, custody, smart-contracts, governance, payments, roadmap] +created: 2026-05-28 +status: draft-for-review +owner: payments + security + operations +--- + +# PRD - Decentralized Custody and Smart-Contract Escrow Roadmap + +## Executive decision + +Do **not** move the whole Amanat escrow flow into a custom smart contract as the next step. + +The current architecture already uses on-chain settlement through Request Network, in-house wallet checkout, derived destination wallets, Transaction Safety Provider checks, and an internal funds ledger. The bigger risk is not "lack of blockchain"; it is **custody and administration centralization**: one backend/admin path can still become too powerful if hot keys, release/refund confirmation, sweep authority, and dispute decisions are not split across independent signers and delayed controls. + +Recommended path: + +1. Harden the current hybrid escrow. +2. Move custody to multisig/hardware signers. +3. Add timelocked governance for global settings. +4. Pilot a minimal on-chain escrow only for opt-in/high-value flows after the above is stable. + +## Current baseline + +| Area | Current state | Risk | +|---|---|---| +| Pay-in | Request Network in-house checkout, direct wallet txs, signed webhooks, transaction-safety checks | Still depends on webhook durability and reconciliation | +| Funds tracking | `FundsLedgerEntry` exists and is append-only | Release/refund ledger enforcement is env-gated by `PAYMENT_LEDGER_ENFORCEMENT` | +| Deposit routing | Derived destinations per `(buyer, sellerOffer, chainId)` | Live divergent-destination probe still needs production-grade evidence | +| Sweep custody | Build-only / hot-key signer abstraction | Production must avoid hot-key signing | +| Release/refund | Admin endpoints build and confirm instructions | Needs mandatory multisig/hardware proof and stronger dispute gating | +| Disputes | Model/service/routes exist with legacy status enum | Needs alignment with canonical dispute/escrow state machine | +| Admin control | App RBAC plus optional Trezor proof | No non-centralized admin quorum yet | + +## Goals + +- Remove single-admin and backend-hot-key custody risk. +- Make release/refund/sweep authority require independent signers. +- Preserve the marketplace's human dispute workflow. +- Keep buyer UX close to the current Request Network in-house checkout. +- Add on-chain escrow only where it creates trust benefit larger than audit and UX cost. + +## Non-goals + +- No token-voting DAO for individual buyer/seller disputes. +- No full rewrite of payments into Solidity before ledger, signing, and webhook controls are stable. +- No custom bridge, cross-chain settlement contract, or generalized DeFi protocol. +- No removal of the internal funds ledger; the ledger remains the application accounting source even if custody moves on-chain. + +## Target trust model + +```mermaid +flowchart LR + Buyer["Buyer wallet"] --> PayIn["Request Network / in-house checkout"] + PayIn --> Dest["Per-payment derived destination"] + Dest --> Safety["Transaction Safety Provider\nconfirmations + token/recipient/amount + AML"] + Safety --> Ledger["Internal append-only funds ledger"] + Ledger --> Policy["Release/refund policy engine"] + Policy --> Safe["Safe multisig custody"] + Safe --> Seller["Seller wallet"] + Safe --> BuyerRefund["Buyer refund wallet"] + + Admin["Admin UI"] --> Policy + Arb["Arbitrator quorum"] --> Safe + Guardian["Guardian"] --> Pause["Pause / cancel dangerous ops"] + Timelock["Timelock / AccessManager"] --> Policy +``` + +## Phase 0 - Stabilize The Hybrid Escrow + +**Timebox:** 1-2 weeks +**Purpose:** Make the current system safe enough that decentralizing custody does not hide application bugs under contract ceremony. + +| Work | Owner | Exit criteria | +|---|---|---| +| Turn on `PAYMENT_LEDGER_ENFORCEMENT=true` in dev, then staging | Backend | Release/refund cannot exceed ledger available balance | +| Backfill/verify ledger entries for active Request Network payments | Backend | Reconciliation report has no unexplained funded payments without ledger rows | +| Enforce dispute hold in every release/refund path | Backend | Opening a dispute blocks release and refund until explicit resolution/override | +| Require Trezor proof in staging with `TREZOR_SAFEKEEPING_REQUIRED=true` | Backend + frontend | Release/refund without proof is rejected; valid Trezor proof succeeds | +| Add audit entries for release/refund/sweep instruction build and confirm | Backend | Each operation records actor, before/after state, tx hash, signer, and reason | +| Complete RN durable webhook ingress design | Platform | Worker storage/replay design approved; backend remains the trust oracle | + +**Decision gate:** no Safe migration until release/refund is ledger-gated and dispute-gated in staging. + +## Phase 1 - Move Custody To Safe Multisig + +**Timebox:** 2-4 weeks +**Purpose:** Remove single-key custody without changing core escrow semantics. + +| Work | Owner | Exit criteria | +|---|---|---| +| Create Safe accounts per supported chain | Ops + security | 2-of-3 minimum for dev/staging, 3-of-5 preferred for production | +| Register hardware-backed owners | Ops | At least two owners use Trezor or equivalent hardware wallets | +| Route release/refund/sweep builds to Safe transaction proposals | Backend + frontend | Admin UI builds a Safe transaction instead of direct hot-key tx | +| Confirm Safe execution before ledger release/refund append | Backend | Ledger terminal entry requires verified Safe tx hash | +| Remove production hot-key sweep mode | Ops | `DERIVED_DESTINATION_SWEEP_SIGNER=build-only` in production | +| Add break-glass policy | Security | Time-limited, alarmed, documented; cannot silently bypass quorum | + +**Administration model:** admins can propose, but custody owners execute. A compromised app admin cannot move funds alone. + +## Phase 2 - Durable Payment Evidence And Quarantine + +**Timebox:** parallel with Phase 1 +**Purpose:** Make payment evidence durable and make tainted-funds isolation real. + +| Work | Owner | Exit criteria | +|---|---|---| +| Cloudflare Worker receives RN webhooks first | Platform | Raw body, headers, delivery ID, payment reference, timestamp durably stored | +| Replay endpoint/tool for stored webhook deliveries | Backend + ops | Operator can replay by delivery ID/time window/payment reference | +| AML provider behind Transaction Safety Provider | Backend + compliance | `clean` allows funding; sanctions/mixer verdict blocks or quarantines | +| Derived destination quarantine workflow | Backend + admin UI | Failed AML or transfer mismatch prevents sweep into treasury/Safe | +| Live divergent-destination probe | Payments | Two real paid intents to two derived addresses both complete and reconcile | + +**Decision gate:** no custom escrow contract until webhook replay and per-payment quarantine work operationally. + +## Phase 3 - Decentralize Administrative Control + +**Timebox:** 4-6 weeks +**Purpose:** Split operational permissions without making daily support impossible. + +| Control | Recommended design | +|---|---| +| Custody movement | Safe multisig threshold | +| Global escrow settings | Timelock or OpenZeppelin AccessManager-managed roles | +| Contract upgrades, if any | Timelocked multisig, no instant admin upgrade | +| Emergency pause | Guardian role can pause, not withdraw | +| Dispute financial outcome | App records decision; Safe quorum executes release/refund/split | +| Confirmation thresholds | App admin can propose; high-risk decreases require timelock or second approval | +| Break-glass | Time-limited, high-severity alert, postmortem required | + +Use token/DAO voting only for protocol-level parameters if the platform later has a real governance community. Do not use broad token voting for individual disputes; it leaks private commercial facts and is slow/manipulable. + +## Phase 4 - Minimal Smart-Contract Escrow Pilot + +**Timebox:** 6-10 weeks after Phases 0-3 +**Purpose:** Test whether on-chain escrow improves trust enough for a specific cohort. + +Pilot only when one of these is true: + +- Average escrow value is high enough that users ask for contract custody. +- Sellers/buyers explicitly demand on-chain proof that funds cannot be swept. +- The platform wants a premium "contract escrow" mode. +- Regulated partners require provable segregation of funds. + +Minimal contract shape: + +| Function | Notes | +|---|---| +| `fund(orderId, token, amount, buyer, seller, deadline)` | Buyer funds ERC-20 escrow; order ID is app-generated and hashed | +| `confirmDelivery(orderId)` | Buyer can release without admin | +| `openDispute(orderId)` | Either party can pause auto-release before deadline | +| `resolve(orderId, releaseAmount, refundAmount, reasonHash)` | Arbitrator/multisig executes split | +| `claimAfterTimeout(orderId)` | Seller can claim after timeout if no dispute | +| `refundAfterExpiry(orderId)` | Buyer can recover if seller never starts/accepts | +| `pause()` / `unpause()` | Guardian pause only; no fund extraction | + +Security requirements: + +- Use audited libraries for `SafeERC20`, reentrancy protection, pausing, and access control. +- Avoid upgradeability unless the timelock and upgrade policy are production-ready. +- External audit before mainnet funds. +- Formal state-transition tests mirroring [[Funds Ledger and Escrow State Machine Specification]]. +- Fuzz tests for double release, split math, fee rounding, pausing, and timeout races. + +## Phase 5 - Go / No-Go Criteria + +Proceed from hybrid multisig custody to custom escrow only if all are true: + +- Safe/Trezor flow has processed real releases/refunds without operational pain. +- Ledger enforcement has run for at least one complete payment cycle. +- Dispute hold cannot be bypassed in tests or manual review. +- Durable webhook ingress and replay are live. +- Per-payment destination quarantine is live. +- Contract audit budget and maintenance owner are approved. +- The user trust/compliance benefit is explicitly documented. + +If these are not true, continue improving the hybrid model. A contract that encodes immature off-chain policy will make the system harder to fix, not safer. + +## Stale documentation corrected in this pass + +This roadmap was created together with a focused documentation alignment pass: + +- [[System Overview]] now reflects Request Network as the primary payment rail, derived destinations, ledger, and existing dispute service. +- [[System Architecture]] now shows Request Network webhooks and durable ingress as the target, instead of SHKeeper-only webhook flow. +- [[Backend Architecture]] no longer lists removed SHKeeper service folders/routes as the current module map. +- [[Escrow Flow]] now reflects hybrid custody, ledger gates, derived destinations, and the recommended multisig-before-contract direction. +- [[Dispute Flow]] no longer says the backend dispute service/model/routes do not exist. +- [[Request Network Integration Constraints]] now marks the in-house checkout and derived-destination work as implemented/partially implemented rather than only designed. + +## External references + +- Safe threshold custody model: +- OpenZeppelin AccessManager and timelock guidance: +- Request Network payment contracts overview: diff --git a/README.md b/README.md index cbe2277..8961798 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ How the system is composed at every layer. - [[System Architecture]] — end-to-end topology + request lifecycle - [[Backend Architecture]] — Express 5 + Mongoose + Socket.IO module map - [[Frontend Architecture]] — Next.js 16 App Router + provider tree +- [[Request Network Integration Constraints]] — current RN integration constraints and rollout gates +- [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] — custody decentralization and smart-contract decision roadmap - [[Infrastructure]] — Docker images, compose stacks, registry, Watchtower - [[Real-time Layer]] — Socket.IO rooms, events, scaling notes - [[Security Architecture]] — auth layers, RBAC, HMAC, hardening checklist @@ -88,7 +90,7 @@ End-to-end narratives for every user-visible interaction, with Mermaid sequence/ - [[Purchase Request Flow]] · [[Seller Offer Flow]] · [[Negotiation Flow]] **Money** -- [[Payment Flow - SHKeeper]] · [[Payment Flow - DePay & Web3]] · [[Escrow Flow]] · [[Payout Flow]] +- [[PRD - Request Network In-House Checkout]] · [[Payment Flow - DePay & Web3]] · [[Escrow Flow]] · [[Payout Flow]] · [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] **Resolution** - [[Dispute Flow]] · [[Delivery Confirmation Flow]] · [[Rating Flow]] @@ -151,7 +153,8 @@ For engineers / SREs running the system in production. | Topic | Start here | |---|---| -| **Payments** | [[Payment Flow - SHKeeper]] → [[Payment API]] → [[Payment]] → [[Payout Flow]] | +| **Payments** | [[PRD - Request Network In-House Checkout]] → [[Payment API]] → [[Payment]] → [[Payout Flow]] | +| **Custody / escrow strategy** | [[PRD - Decentralized Custody and Smart-Contract Escrow Roadmap]] → [[Escrow Flow]] → [[Funds Ledger and Escrow State Machine Specification]] | | **Auth** | [[Authentication Flow]] → [[Authentication API]] → [[Security Architecture]] | | **Backend security / refactor** | [[Backend Stack Security and Refactor Assessment - 2026-05-24]] → [[Platform Logical Audit - 2026-05-24]] → [[PRD - Platform Audit Remediation Plan (2026-05-24)]] | | **Developer task queue** | `.taskmaster/README.md` → `.taskmaster/tasks/tasks.json` → root `PRD - *.md` files | @@ -204,8 +207,8 @@ These are documented in their respective sections but worth highlighting: > [!warning] > - Backend rate-limit middleware is currently **disabled** (`backend/src/app.ts:227`). Enable before any public traffic — see [[Security Architecture]]. > - Passkey service is partly **stubbed** — see [[Passkey (WebAuthn) Flow]] for production-hardening checklist. -> - Auto-release of escrow on delivery confirmation **not yet automated** — admin runs manual payouts. See [[Delivery Confirmation Flow]] + [[Payout Flow]]. -> - Opening a dispute does **not pause** the escrow until admin intervention. See [[Dispute Flow]] + [[Escrow Flow]]. +> - Auto-release of escrow on delivery confirmation **not yet automated** — admin/custody operators run release flows. See [[Delivery Confirmation Flow]] + [[Payout Flow]]. +> - Dispute holds exist in code, but the Dispute model/docs still need full canonical state-machine alignment. See [[Dispute Flow]] + [[Escrow Flow]]. > - Several development env values committed as public — see [[Environment Variables]] for rotation list. > - Single-host deployment; horizontal scaling requires Redis adapter for Socket.IO — see [[Real-time Layer]] §8. > - Request Network webhooks currently land on the main app. Roadmap: Cloudflare Worker durable ingress + replay, with backend Transaction Safety Provider checks before escrow is credited. See [[Request Network Integration Constraints]].