Skip to content

feat(payin): verification-gated admin recovery for SEND_UNCERTAIN pay-in sends (#4263 follow-up) #4264

Description

@TaprootFreak

Context

#4263 extended the fail-closed designate-before-broadcast boundary to all non-EVM pay-in send strategies (and #4249 did the same for EVM). When a send fails ambiguously at or after the broadcast, the pay-in is left in SENDING and the processStrandedSendingPayIns cron escalates it to SEND_UNCERTAIN after the grace window. This is correct and fund-safe: the transaction may be in flight, so re-broadcasting would double-pay the customer.

Problem

There is currently no recovery path for a SEND_UNCERTAIN pay-in:

  • No cron re-reads SEND_UNCERTAIN (checkOutputConfirmations / checkReturnConfirmations only touch FORWARDED / RETURNED).
  • The pay-in controller has no retry endpoint.
  • returnPayIn / triggerBuyFiatReturn reject an in-flight-send status, so the customer's funds are blocked — neither forwarded/returned nor retryable — until someone edits the database by hand.

The payout side already solved the equivalent problem: #4231 added a verification-gated admin retry for PAYOUT_UNCERTAIN orders. The pay-in side has no equivalent, and #4263 widened this exposure from EVM-only to all non-EVM chains.

Expected behavior

A verification-gated admin endpoint (mirroring the payout retry from #4231) that returns a confirmed-not-broadcast SEND_UNCERTAIN pay-in to the send flow:

  • Admin-guarded (same guards as the existing admin pay-in/payout routes).
  • Gated on an explicit operator attestation that the transaction was not broadcast (checked strictly, e.g. noBroadcastVerified === true), because the whole point of SEND_UNCERTAIN is that the broadcast outcome is unknown — a blind reset would re-introduce the exact double-send fix(payin): fail closed on ambiguous non-EVM pay-in sends #4263 prevents.
  • Reject the reset if the pay-in already carries an outTxId / returnTxId (it did broadcast) — reconcile that separately, never re-send.
  • On success, an atomic conditional transition (compare-and-set on status = SEND_UNCERTAIN) back to the status the send cron re-selects (ACKNOWLEDGED/PREPARED for forward, TO_RETURN/PREPARED for return), so the next cron run re-runs it under the full designate-before-broadcast protection.
  • Fail closed if the lookup/verification cannot be completed — deny the reset, never proceed.

Acceptance criteria

  • Admin endpoint resets a verified-not-broadcast SEND_UNCERTAIN pay-in to a send-cron-selectable status via an atomic conditional update; a pay-in that changed state concurrently is not resurrected.
  • A pay-in that has an outTxId/returnTxId set is rejected (no reset).
  • The operator attestation is required and strictly checked; the request fails closed if verification cannot complete.
  • Authz matches the existing admin routes.
  • Regression test: a reset pay-in re-enters the send flow and is not double-sent; a non-attested / already-broadcast pay-in is rejected.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions