From 4368dee2f5962a3343c5abed2358c27d9289e6aa Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 20 May 2026 11:46:00 +0200 Subject: [PATCH 1/4] fix(swift-example): prevent Create Identity submit button from hiding when prefill rounds above balance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The amount field's default-string formatter used `String(format: "%g", dash)`, whose 6-sig-fig rounding can push the prefilled value above the actual account balance (e.g. 0.99984186 DASH → "0.999842" → 99,984,200,000 credits vs. 99,984,186,000 available). `canSubmit` requires `credits <= balance`, so the form silently hid the submit section and the user saw no way to register the identity. Format the prefill via integer arithmetic on the smallest-units value so the string is an exact decimal representation of the underlying credits/duffs, guaranteeing a clean round-trip through `parsedAmountCredits` / `parsedAmountDuffs`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Views/CreateIdentityView.swift | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift index 5b9088424c8..b04a9293cdf 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift @@ -1273,19 +1273,46 @@ struct CreateIdentityView: View { case 14: let balance = accountBalance(account) if balance == 0 { return "" } - let dash = Double(balance) / Double(Self.creditsPerDash) - return String(format: "%g", dash) + return Self.amountString( + smallestUnits: balance, + decimalsPerWhole: Self.creditsPerDash + ) case 0, 1: let available = coreAccountBalanceDuffs(account) let defaultDuffs = min(available, Self.defaultCoreFundingDuffs) if defaultDuffs == 0 { return "" } - let dash = Double(defaultDuffs) / Double(Self.duffsPerDash) - return String(format: "%g", dash) + return Self.amountString( + smallestUnits: defaultDuffs, + decimalsPerWhole: Self.duffsPerDash + ) default: return "" } } + private static func amountString( + smallestUnits: UInt64, + decimalsPerWhole: UInt64 + ) -> String { + let whole = smallestUnits / decimalsPerWhole + let fractional = smallestUnits % decimalsPerWhole + if fractional == 0 { + return "\(whole)" + } + let decimalPlaces = String(decimalsPerWhole).count - 1 + var fractionalStr = String(fractional) + if fractionalStr.count < decimalPlaces { + fractionalStr = String( + repeating: "0", + count: decimalPlaces - fractionalStr.count + ) + fractionalStr + } + while fractionalStr.last == "0" { + fractionalStr.removeLast() + } + return fractionalStr.isEmpty ? "\(whole)" : "\(whole).\(fractionalStr)" + } + /// Parse the amount text back into credits. Returns `nil` on /// invalid / negative / overflow input. private var parsedAmountCredits: UInt64? { From dbb9613a90cf952a8867f02f4f6cb062b28b5d5d Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 20 May 2026 11:47:58 +0200 Subject: [PATCH 2/4] fix(swift-example): auto-dismiss Create Identity sheet after Platform Payment success The Platform Payment success branch persisted the new identity but left the form mounted. SwiftUI then re-evaluated the registration index picker against `usedIdentityIndices(for:)`, which now contained the just-used slot, so the user saw a red "Index #N is already taken" error sitting on top of a successful registration with no clear next step. Dismiss the sheet immediately after the save completes. The Identities tab's `@Query` picks up the new row automatically, so there's nothing left for the form to show. Also drop the dead `createdIdentityId` state. Both submit paths assigned it on success but nothing ever read it; the doc comment claimed it drove a success banner / auto-dismiss that didn't exist. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../SwiftExampleApp/Views/CreateIdentityView.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift index b04a9293cdf..c0946e15f37 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift @@ -146,10 +146,6 @@ struct CreateIdentityView: View { /// User-facing error surfaced via the `.alert` modifier. @State private var submitError: SubmitError? = nil - /// Success payload. Populated after the identity is persisted; - /// the submit section swaps to a success banner and auto-dismiss. - @State private var createdIdentityId: Data? = nil - /// Active registration controller for the Core-funded path. /// Stored only so `submitCoreFunded` has a local reference /// after spawning it; the canonical lifetime owner is @@ -892,8 +888,8 @@ struct CreateIdentityView: View { identityIndex: identityIndex ) try modelContext.save() - self.createdIdentityId = created.identityId self.isCreating = false + dismiss() } } catch { await MainActor.run { @@ -990,7 +986,7 @@ struct CreateIdentityView: View { } /// Bridge a controller's phase transitions to this view's - /// `createdIdentityId` / `submitError` / `isCreating` state. + /// `submitError` / `isCreating` state. /// The observer task auto-cancels when this view deallocates /// (Swift task lifecycle on the captured `self`), but the /// controller itself outlives the view. @@ -1014,7 +1010,6 @@ struct CreateIdentityView: View { identityIndex: identityIndex ) try modelContext.save() - self.createdIdentityId = identityId } catch { self.submitError = .init( message: error.localizedDescription From d66b84a75f0f1cd14270beb317f815ef34b21489 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 20 May 2026 18:02:09 +0200 Subject: [PATCH 3/4] fix(swift-example): simplify Create Identity amount prefill to a 4-decimal truncate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the integer-arithmetic `amountString` helper with a four-line truncate-to-4-decimals on the `Double` itself. Same effect for any realistic balance — the truncate guarantees the prefilled value is `<= balance` — at a fraction of the surface area. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Views/CreateIdentityView.swift | 39 ++++--------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift index c0946e15f37..50b45797e17 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift @@ -1264,48 +1264,25 @@ struct CreateIdentityView: View { else { return "" } + let dash: Double switch account.accountType { case 14: let balance = accountBalance(account) if balance == 0 { return "" } - return Self.amountString( - smallestUnits: balance, - decimalsPerWhole: Self.creditsPerDash - ) + dash = Double(balance) / Double(Self.creditsPerDash) case 0, 1: let available = coreAccountBalanceDuffs(account) let defaultDuffs = min(available, Self.defaultCoreFundingDuffs) if defaultDuffs == 0 { return "" } - return Self.amountString( - smallestUnits: defaultDuffs, - decimalsPerWhole: Self.duffsPerDash - ) + dash = Double(defaultDuffs) / Double(Self.duffsPerDash) default: return "" } - } - - private static func amountString( - smallestUnits: UInt64, - decimalsPerWhole: UInt64 - ) -> String { - let whole = smallestUnits / decimalsPerWhole - let fractional = smallestUnits % decimalsPerWhole - if fractional == 0 { - return "\(whole)" - } - let decimalPlaces = String(decimalsPerWhole).count - 1 - var fractionalStr = String(fractional) - if fractionalStr.count < decimalPlaces { - fractionalStr = String( - repeating: "0", - count: decimalPlaces - fractionalStr.count - ) + fractionalStr - } - while fractionalStr.last == "0" { - fractionalStr.removeLast() - } - return fractionalStr.isEmpty ? "\(whole)" : "\(whole).\(fractionalStr)" + let truncated = (dash * 10_000).rounded(.down) / 10_000 + var s = String(format: "%.4f", truncated) + while s.last == "0" { s.removeLast() } + if s.last == "." { s.removeLast() } + return s } /// Parse the amount text back into credits. Returns `nil` on From 50ef4727761347c7887add8395e90f0629da05f4 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 21 May 2026 09:55:02 +0200 Subject: [PATCH 4/4] fix(swift-example-app): name the Create Identity prefill granularity constant Replaces the inline `10_000` literals with a named `prefillDecimalPlaces = 4` constant, addressing review feedback that the magic number's intent wasn't obvious at the call site. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../SwiftExampleApp/Views/CreateIdentityView.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift index 50b45797e17..76878f387f7 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/CreateIdentityView.swift @@ -77,6 +77,12 @@ struct CreateIdentityView: View { /// processing fee is deducted. private static let defaultCoreFundingDuffs: UInt64 = 250_000 + /// Decimal places the amount prefill is truncated to. Capping + /// the prefilled value at `floor(balance, 0.0001 DASH)` keeps + /// the parsed-back credits at or below the actual balance so + /// `canSubmit` doesn't trip on a `%g`-style rounding overflow. + private static let prefillDecimalPlaces: Int = 4 + /// All locally-persisted wallets. Drives the Source Wallet /// picker along with the synthetic "no wallet" sentinel. @Query(sort: \PersistentWallet.createdAt) private var wallets: [PersistentWallet] @@ -1278,8 +1284,9 @@ struct CreateIdentityView: View { default: return "" } - let truncated = (dash * 10_000).rounded(.down) / 10_000 - var s = String(format: "%.4f", truncated) + let scale = pow(10.0, Double(Self.prefillDecimalPlaces)) + let truncated = (dash * scale).rounded(.down) / scale + var s = String(format: "%.\(Self.prefillDecimalPlaces)f", truncated) while s.last == "0" { s.removeLast() } if s.last == "." { s.removeLast() } return s