Files
nick-doc/04 - Flows/Payout Flow.md

6.3 KiB

title, tags, related_models, related_apis
title tags related_models related_apis
Payout Flow
flow
payment
payout
release
refund
custody
Payment
Funds Ledger and Escrow State Machine Specification
POST /api/payment/:id/release
POST /api/payment/:id/release/confirm
POST /api/payment/:id/refund
POST /api/payment/:id/refund/confirm

Payout Flow

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:

  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 / 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 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.

Release Narrative

  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.

Refund Narrative

  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.

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, 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

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

Database Writes

  • 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.

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 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.

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

Source Files

  • 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/