Skip to content

Commit 56f37ac

Browse files
fix(swift-example-app): resume orphaned Broadcast identity-registration locks (#4010)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent eae653e commit 56f37ac

1 file changed

Lines changed: 29 additions & 24 deletions

File tree

packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/IdentitiesContentView.swift

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -570,32 +570,37 @@ private struct ResumableRegistrationRow: View {
570570
.padding(.vertical, 2)
571571
}
572572

573-
/// Trailing view that depends on the lock's stage. At Broadcast
574-
/// (`!canFundIdentity`) the lock isn't usable yet — SPV is waiting
575-
/// on the masternodes to sign an InstantSendLock — so we show
576-
/// a spinner instead of a button. SwiftData `@Query` is
577-
/// reactive, so when the persister flips the row to
578-
/// `InstantSendLocked` this view re-renders into the Resume
579-
/// button without any extra plumbing.
580-
@ViewBuilder
573+
/// Trailing Resume button. Every lock reaching this row is
574+
/// `statusRaw` 1...3 (per `crossWalletResumableLocks`) AND — by the
575+
/// list's `(walletId, identityIndex)` slot anti-join, which unions
576+
/// active-controller slots — is NOT owned by an in-flight
577+
/// registration. So every row is a genuinely-orphaned lock with no
578+
/// driver, and offering Resume is always safe:
579+
/// - InstantSendLocked / ChainLocked (`canFundIdentity`) submits the
580+
/// IdentityCreate as soon as it's tapped.
581+
/// - Broadcast (statusRaw 1) has no proof yet; resuming re-enters the
582+
/// finality wait via `resume_asset_lock` (which re-broadcasts the
583+
/// tx, then waits for the ChainLock — guaranteed finality, however
584+
/// long it takes). This previously rendered a permanent spinner,
585+
/// stranding a broadcast-but-interrupted registration lock (app
586+
/// killed mid-wait, or an aged lock whose IS quorum rotated) with
587+
/// no in-app recovery path.
588+
///
589+
/// Unlike the address-funding sibling, no `hasActiveFunding` gate is
590+
/// needed here: identity locks carry their `identityIndex`, so the
591+
/// anti-join excludes the in-flight lock precisely, where the
592+
/// address flow (whose lock doesn't know its recipient) cannot.
593+
///
594+
/// SwiftData `@Query` re-renders this row as the persister advances
595+
/// the lock's status.
581596
private var trailingAffordance: some View {
582-
if lock.canFundIdentity {
583-
Button(action: onResume) {
584-
Label("Resume", systemImage: "arrow.clockwise")
585-
.labelStyle(.titleAndIcon)
586-
.font(.callout)
587-
}
588-
.buttonStyle(.borderedProminent)
589-
.controlSize(.small)
590-
} else {
591-
HStack(spacing: 6) {
592-
ProgressView()
593-
.controlSize(.small)
594-
Text("Waiting for InstantSendLock or ChainLock…")
595-
.font(.caption)
596-
.foregroundColor(.secondary)
597-
}
597+
Button(action: onResume) {
598+
Label("Resume", systemImage: "arrow.clockwise")
599+
.labelStyle(.titleAndIcon)
600+
.font(.callout)
598601
}
602+
.buttonStyle(.borderedProminent)
603+
.controlSize(.small)
599604
}
600605

601606
private func formatDuffs(_ amountDuffs: Int64) -> String {

0 commit comments

Comments
 (0)