Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,26 @@ public final class PlatformAddressSyncCoordinator: NSObject, ObservableObject {

// MARK: - Core ↔ Platform balance movement

/// SDK-owned affordability preflight for Platform Payment → Shielded.
///
/// The Platform balance shown by this coordinator is the sum of every
/// derived address, but the shield transition can only spend the suffix
/// selected by the Rust wallet. Keep that selection rule in the SDK and
/// expose its exact result to the internal-transfer UI instead of
/// reimplementing it from `derivedAddresses` here.
public func preflightShield(
paymentAccount: UInt32 = 0
) async throws -> PlatformWalletManager.ShieldedShieldPreflight {
guard isRunning,
let manager = walletManager,
let walletId = wallet?.walletId
else { throw SendError.coordinatorNotReady }

return try await manager.shieldedShieldPreflight(
walletId: walletId,
paymentAccount: paymentAccount)
}

/// Preflight of the full-balance Platform → Core withdrawal for the
/// account holding the highest Platform address balance (the same
/// account `transfer` spends from). See `withdrawAllToCore` for why
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@ struct InternalTransferConfirmSheet: View {
/// Shielded reverse routes only: execute the note-aware Max plan and
/// revalidate it immediately before proving.
var isFullShieldedSweep: Bool = false
/// Frozen with the submitted amount so a capacity refresh can update only
/// a value the user explicitly derived via Platform Shield Max.
var platformShieldAmountWasMax: Bool = false
var onCancel: () -> Void
var onCompleted: () -> Void
var onPlatformShieldCapacityChanged: (UInt64?, Bool) -> Void

@StateObject private var coordinator = ShieldedTransferCoordinator()
@State private var handledPlatformShieldCapacityChange = false

var body: some View {
VStack(spacing: 0) {
Expand All @@ -67,6 +72,9 @@ struct InternalTransferConfirmSheet: View {
}
.background(Color.dash.primaryBackground)
.interactiveDismissDisabled(isInFlight)
.onChange(of: coordinator.phase) { phase in
handlePlatformShieldCapacityChange(phase)
}
}

private var isInFlight: Bool {
Expand Down Expand Up @@ -483,6 +491,21 @@ struct InternalTransferConfirmSheet: View {
coordinator.reset()
confirm()
}

private func handlePlatformShieldCapacityChange(
_ phase: ShieldedTransferCoordinator.Phase
) {
guard !handledPlatformShieldCapacityChange,
case .failed = phase,
let error = coordinator.lastFailure as? ShieldedTransferCoordinator.CoordinatorError,
case .platformShieldCapacityChanged(let maxShieldableCredits) = error
else { return }

handledPlatformShieldCapacityChange = true
onPlatformShieldCapacityChanged(
maxShieldableCredits,
platformShieldAmountWasMax)
}
}

/// Recovery sheet for a stuck "to Shielded" transfer (Core→Shielded). The
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,22 @@
import SwiftUI
import DashUIKit

/// Immutable values the user saw when tapping Continue. Async balance/preflight
/// refreshes may keep updating the form underneath the sheet, but they must not
/// rewrite the amount already presented for confirmation.
private struct InternalTransferConfirmation: Identifiable {
let id = UUID()
let route: InternalTransferRoute
let dashDuffs: Int64
let amountDuffsUnsigned: UInt64
let creditsAmount: UInt64
let fiatText: String
let withdrawalFeeCredits: UInt64?
let isFullPlatformWithdrawal: Bool
let isFullShieldedSweep: Bool
let platformShieldAmountWasMax: Bool
}

struct InternalTransferScreen: View {
@ObservedObject var viewModel: InternalTransferViewModel

Expand All @@ -30,7 +46,7 @@ struct InternalTransferScreen: View {
/// precedence over `receiveInto`.
var sendFrom: ChainNetwork? = nil

@State private var showConfirm: Bool = false
@State private var confirmation: InternalTransferConfirmation?

var body: some View {
VStack(spacing: 0) {
Expand Down Expand Up @@ -77,20 +93,27 @@ struct InternalTransferScreen: View {
keyboardSection
}
.background(Color.dash.primaryBackground)
.sheet(isPresented: $showConfirm) {
.sheet(item: $confirmation) { submission in
InternalTransferConfirmSheet(
route: viewModel.route,
dashDuffs: viewModel.dashDuffs,
amountDuffsUnsigned: viewModel.dashDuffsUnsigned,
creditsAmount: viewModel.creditsPreview,
fiatText: viewModel.fiatAmountString,
withdrawalFeeCredits: viewModel.withdrawalPreflight?.estimatedFee,
isFullPlatformWithdrawal: viewModel.isFullPlatformWithdrawal,
isFullShieldedSweep: viewModel.isFullShieldedSweep,
onCancel: { showConfirm = false },
route: submission.route,
dashDuffs: submission.dashDuffs,
amountDuffsUnsigned: submission.amountDuffsUnsigned,
creditsAmount: submission.creditsAmount,
fiatText: submission.fiatText,
withdrawalFeeCredits: submission.withdrawalFeeCredits,
isFullPlatformWithdrawal: submission.isFullPlatformWithdrawal,
isFullShieldedSweep: submission.isFullShieldedSweep,
platformShieldAmountWasMax: submission.platformShieldAmountWasMax,
onCancel: { confirmation = nil },
onCompleted: {
showConfirm = false
confirmation = nil
onCompleted()
},
onPlatformShieldCapacityChanged: { maxCredits, amountWasMax in
confirmation = nil
viewModel.handlePlatformShieldCapacityChanged(
maxShieldableCredits: maxCredits,
submittedAmountWasMax: amountWasMax)
})
.presentationDetents([.large])
.presentationDragIndicator(.hidden)
Expand Down Expand Up @@ -133,7 +156,7 @@ struct InternalTransferScreen: View {
actionButtonText: NSLocalizedString("Continue", comment: ""),
actionEnabled: viewModel.canContinue,
inProgress: false,
actionHandler: { showConfirm = true }
actionHandler: presentConfirmation
)
.padding(.top, 12)
.padding(.horizontal, 16)
Expand All @@ -143,6 +166,20 @@ struct InternalTransferScreen: View {
.background(Color.dash.secondaryBackground, ignoresSafeAreaEdges: .bottom)
}

private func presentConfirmation() {
guard viewModel.canContinue else { return }
confirmation = InternalTransferConfirmation(
route: viewModel.route,
dashDuffs: viewModel.dashDuffs,
amountDuffsUnsigned: viewModel.dashDuffsUnsigned,
creditsAmount: viewModel.creditsPreview,
fiatText: viewModel.fiatAmountString,
withdrawalFeeCredits: viewModel.withdrawalPreflight?.estimatedFee,
isFullPlatformWithdrawal: viewModel.isFullPlatformWithdrawal,
isFullShieldedSweep: viewModel.isFullShieldedSweep,
platformShieldAmountWasMax: viewModel.platformShieldAmountWasMax)
}

// MARK: - From / To cards

@ViewBuilder
Expand Down
Loading
Loading