Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
added undocumented endpoints (ton-proof challenge, profile email verify,
GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
90-day notification TTL, soft-delete semantics, wallet fields
Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation
Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
title, tags, related_models, related_apis
| title | tags | related_models | related_apis | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Payout Flow |
|
|
|
Payout Flow
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
This page describes how escrowed funds leave Amanat custody after an order is complete or a dispute is resolved.
The current flow is no longer SHKeeper payout-task centric. Release and refund are instruction-based:
- Backend validates policy, dispute hold, and ledger availability.
- Backend builds a release/refund instruction.
- A custody signer executes the on-chain transaction.
- 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 / 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 --
PaymentandFundsLedgerEntry.
Preconditions
- The pay-in
Paymentis 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 must include the expected Trezor operation signature (see gate below). - Production target: Safe multisig execution is required for custody movement.
Release Narrative
- Buyer confirms delivery, an auto-release policy matures, or a dispute resolves for the seller.
- Admin calls
POST /api/payment/:id/releasewith optional partial amount. - Backend loads the
Payment, validates ledger availability whenPAYMENT_LEDGER_ENFORCEMENT=true, and returns an instruction payload. - Custody signer broadcasts the seller payment transaction.
- Admin calls
POST /api/payment/:id/release/confirmwithtxHashand (when safekeeping is enabled) a Trezor signature proof. - Backend verifies signer proof when required, confirms adapter state, appends a
releaseledger entry, and marks escrow released.
Refund Narrative
- Dispute resolves for the buyer, order is cancelled before fulfillment, or support executes an approved recovery.
- Admin calls
POST /api/payment/:id/refund. - Backend validates available funds and policy.
- Custody signer broadcasts the refund transaction.
- Admin calls
POST /api/payment/:id/refund/confirmwithtxHashand (when safekeeping is enabled) a Trezor signature proof. - Backend appends a
refundledger entry and marks escrow refunded.
Sequence Diagram
sequenceDiagram
autonumber
actor A as Admin
actor C as Custody signer
participant BE as Backend
participant DB as MongoDB
participant BC as EVM Chain
actor R as Recipient
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, trezor proof if safekeeping }
BE->>BE: Verify proof if required
BE->>DB: append release/refund ledger entry
BE->>DB: update Payment escrowState
BE-->>R: notification (no realtime socket listener — see gap below)
API Calls
Release / Refund (custody) — correct paths
These are mounted on paymentControllerRouter at /api/payment (backend/src/services/payment/paymentControllerRoutes.ts:23-26). Note: no /shkeeper/ segment.
| Method | Endpoint | Purpose |
|---|---|---|
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 |
Request Network — actually implemented routes
Mounted at /api/payment/request-network (app.ts:428 → requestNetwork/requestNetworkRoutes.ts). Only these exist:
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/api/payment/request-network/pay-in |
Create a pay-in intent (authenticated) — requestNetworkRoutes.ts:111 |
POST |
/api/payment/request-network/intents |
Create checkout intent — requestNetworkRoutes.ts:289 |
GET |
/api/payment/request-network/:paymentId/checkout |
In-house checkout block fetcher — requestNetworkRoutes.ts:152 |
POST |
/api/payment/request-network/webhook |
Provider webhook (raw body) — requestNetworkRoutes.ts:330 |
[!warning] ⚠️ NOT IMPLEMENTED — Request Network payout/release/refund sub-routes The following routes are not registered anywhere and return 404:
POST /api/payment/request-network/:id/payout/initiatePOST /api/payment/request-network/:id/payout/confirmPOST /api/payment/request-network/:id/release/confirmPOST /api/payment/request-network/:id/refund/confirmRelease and refund are handled exclusively by the custody routes under
/api/payment/:id/...listed above — not under therequest-networknamespace.
Custody-signer / Trezor safekeeping gate
[!warning] Safekeeping gate blocks the legacy non-custodial helpers When
TREZOR_SAFEKEEPING_REQUIRED=true(backend/src/services/trezor/trezorService.ts:214), the release/refundconfirmendpoints require a Trezor operation signature in the request body.
- The active admin UI path uses
TrezorSignDialog(frontend/src/components/trezor-sign-dialog/TrezorSignDialog.tsx), wired into the awaiting-confirmation list view. It builds the signed payload viagetTrezorOperationMessage+trezorSignMessageand posts{ txHash, amount, trezor: { message, signature } }throughconfirmRelease/confirmRefund(frontend/src/actions/trezor.ts:108,133). This path satisfies the gate.- The legacy helpers
confirmReleaseTx/confirmRefundTx(frontend/src/actions/payment.ts:487,503) post only{ txHash, ...extra }— by default no Trezor proof. They have no UI callers today, but if used with safekeeping enabled the backend will reject the payout. Prefer theTrezorSignDialogflow; remove or retrofit the legacy helpers to attach the signature.
Derived-destinations sweep
HD-wallet derived-destination sweep infrastructure exists but is admin-tooling only:
- Routes:
GET /api/payment/derived-destinations(app.ts:546→wallets/derivedDestinationRoutes). - Cron:
startSweepCron()auto-starts only whenDERIVED_DESTINATION_SWEEP_AUTOSTART=true(app.ts:578-582,wallets/sweepService.ts). - Model:
DerivedDestinationwith statusesactive/swept/sweeping/quarantined(models/DerivedDestination.ts:35).
This is not part of the buyer/seller payout UX; it consolidates funds from per-payment derived addresses.
Database Writes
payments-- status,escrowState,blockchain.transactionHash, signer metadata.funds_ledger_entries-- append-onlyreleaseorrefundentry with idempotency key.purchaserequests-- terminal business state after release/refund completes.notifications-- release/refund receipt to the relevant party.
Socket events emitted
[!warning] Real-time payout/payment events have NO frontend listeners Two seller-facing socket events are emitted by the backend but no frontend code subscribes to them, so sellers receive no real-time notification:
payout-completed→user-{sellerId}, emitted after admin wallet payout (backend/src/services/payment/decentralizedPaymentService.ts:911). No frontend listener.payment-received→user-{sellerId}, emitted on Web3 verify (backend/src/services/payment/paymentRoutes.ts:622) and frommarketplace/routes.ts:2611. No frontend listener.Until the frontend socket layer registers handlers for these, sellers must refresh / poll to see payout and incoming-payment state. Persisted DB notifications still surface through the standard notification channel.
Error / Edge Cases
- 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 confirm when
TREZOR_SAFEKEEPING_REQUIRED=true(legacyconfirmReleaseTx/confirmRefundTxhelpers omit it — see gate above). - 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.
- Wrong namespace -- calling release/refund under
/api/payment/request-network/:id/...returns 404 (those routes do not exist).
Legacy SHKeeper Note
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.
Linked Flows
- 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.
Source Files
- Backend:
backend/src/services/payment/paymentControllerRoutes.ts:23-26(release/refund routes) - Backend:
backend/src/services/payment/requestNetwork/requestNetworkRoutes.ts:111,152,289,330(implemented RN routes) - 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:214(safekeeping gate) - Backend:
backend/src/services/dispute/releaseHoldService.ts - Backend:
backend/src/services/payment/decentralizedPaymentService.ts:911(payout-completedemit) - Backend:
backend/src/services/payment/paymentRoutes.ts:622(payment-receivedemit) - Backend:
backend/src/services/payment/wallets/sweepService.ts,models/DerivedDestination.ts(sweep infra) - Frontend:
frontend/src/components/trezor-sign-dialog/TrezorSignDialog.tsx,frontend/src/actions/trezor.ts:108,133(active Trezor confirm path) - Frontend:
frontend/src/actions/payment.ts:487,503(legacyconfirmReleaseTx/confirmRefundTx, no Trezor proof)