Skip to content

Expired vaults whose Pre-PegIn never reached Bitcoin persist forever in the dashboard #1710

@jrwbabylonlab

Description

@jrwbabylonlab

Problem

When a vault expires without its Pre-PegIn Bitcoin transaction ever reaching the network, the depositor's BTC was never locked — there is genuinely nothing to recover. #1701 fixed the action: opening the refund modal for such a vault now shows "nothing to refund" instead of walking the user through a doomed signing flow.

But the vault still occupies a permanent slot in the dashboard's Expired Deposits section, with a "Refund" button, indefinitely. There is no retention limit or cleanup.

Why it persists

  • services/vault/src/services/vault/fetchVaults.ts queries vaults(where: { depositor: $depositor }) — no status filter, no time/recency cutoff, no pagination. Every vault the depositor ever registered is returned on every load.
  • services/vault/src/hooks/usePendingDeposits.ts builds expiredActivities from every activity with contractStatus === EXPIRED && !!unsignedPrePeginTx. For rows that reach the dashboard, unsignedPrePeginTx is effectively always present — the fetch path validates it as required and drops malformed/null rows — so expiredActivities includes every expired vault.
  • services/vault/src/components/simple/ExpiredDepositSection.tsx renders every expired activity; the list is visually scroll-capped (max-h-[400px]) but has no count limit and no age-based filtering.
  • There is no client-side cleanup of indexer-sourced expired vaults — shouldRemoveFromLocalStorage only prunes localStorage pending entries, not confirmed/expired indexer rows. Once a vault is EXPIRED, the dashboard never ages it out.

Net effect: a depositor who registers a vault on Ethereum, never broadcasts the Pre-PegIn, and lets it lapse accumulates a permanent dead entry in "Expired Deposits" — accurate but useless, since nothing was ever locked on Bitcoin. On the list itself it is indistinguishable from a genuinely-refundable expired vault; the "nothing to refund" distinction only surfaces inside the modal.

Notes

  • Distinct from Refund button shown for expired vaults whose Pre-PegIn was never broadcast #1701: that fixed the doomed signing flow; this is the leftover list persistence.
  • The "is the Pre-PegIn actually on Bitcoin" signal is only resolved when the refund modal opens (a per-vault mempool probe). Hiding such a vault from the list up front would need that result available at list-render time — i.e. persisting it, or a per-row probe (the polling anti-pattern the codebase deliberately avoids).
  • Affects single-vault and batched deposits equally.

Scope

Decide how a never-broadcast expired vault should age out of the dashboard — for example a way to dismiss/acknowledge it, an age-based cutoff for expired entries, or accepting it as a permanent ledger entry. Where the on-chain "is it on Bitcoin" signal is available (modal-open only) constrains the cleaner options and is the main thing to design around.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions