From da9e68eea739d2cc18fe7038569970fe6f675872 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 21 Jun 2026 20:43:33 +0300 Subject: [PATCH 1/2] fix(swift-example-app): load document price on Purchase sheet appear The production document Purchase flow was blocked: the on-chain price never loaded, so "Purchase / Broadcast" stayed permanently disabled. `DocumentWithPriceView` fetched the price only via the TextField's `onChange(of: documentId)`. `PurchaseDocumentView` seeds `documentIdField` as the binding mounts (and disables the field), so `onChange` never fires and the user can't trigger it by editing -> `fetchDocument()` never ran, `documentPrice` stayed nil, and submit was gated off forever. Fix: trigger the fetch from `.onAppear` when the id is already populated (`.disabled(true)` blocks hit-testing, not lifecycle events, so it fires in the Purchase flow). The editable TransitionInputView path starts empty, so this is a no-op there and `onChange` still handles typing. Also restore the relaxed Purchase gating so the action surfaces for a for-sale, tradeable doc whenever the wallet holds a controlled identity other than the owner (buyer != owner) -- matching the file's own doc-comment. Both of these were fixed once on the #3945 branch (588435aff4) but the v3.1-dev merge (96cba166ba) dropped that commit, so the bug recurred. Verified end-to-end on the iOS simulator (testnet): created a card doc owned by one identity, set price 50000 credits, opened Purchase as a second controlled identity (price loaded + button enabled), broadcast the purchase, and confirmed ZOWNERID flipped to the buyer (rev 2->3, $price cleared). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Views/DocumentWithPriceView.swift | 15 +++++++++++++++ .../SwiftExampleApp/Views/DocumentsView.swift | 15 +++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentWithPriceView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentWithPriceView.swift index 850adabc681..3c71427663a 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentWithPriceView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentWithPriceView.swift @@ -117,6 +117,21 @@ struct DocumentWithPriceView: View { .foregroundColor(.secondary) } } + .onAppear { + // `onChange(of: documentId)` does NOT fire when the id is + // established as the binding mounts — e.g. PurchaseDocumentView + // seeds `documentIdField` in its own `onAppear` and disables this + // view, so relying on onChange alone the price probe would never + // run and Purchase would stay gated off forever. Kick the fetch + // here so a pre-seeded id loads its price without a user edit. + // `.disabled(true)` only blocks hit-testing, not lifecycle events, + // so this still fires in the Purchase flow. When the field starts + // empty (the editable transition flow) this is a no-op and the + // existing onChange path continues to handle typing. + if !documentId.isEmpty { + handleDocumentIdChange(documentId) + } + } } private func handleDocumentIdChange(_ newValue: String) { diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift index 7afcbc6b432..b4e006288ea 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift @@ -275,6 +275,7 @@ struct DocumentDetailView: View { private var availableActions: [DocumentAction] { var actions: [DocumentAction] = [] let docType = documentTypeRow + let tradeable = (docType?.tradeMode ?? 0) > 0 if ownerIsControlled { actions.append(.replace) @@ -282,12 +283,18 @@ struct DocumentDetailView: View { if docType?.documentsTransferable == true { actions.append(.transfer) } - if (docType?.tradeMode ?? 0) > 0 { + if tradeable { actions.append(.setPrice) } - } else if (docType?.tradeMode ?? 0) > 0 && !nonOwnerControlledIdentities.isEmpty { - // Not the owner, but the wallet holds another identity that - // could buy a for-sale, tradeable document. + } + // Surface Purchase whenever the doc type is tradeable and the wallet + // holds a controlled identity that isn't the owner (the buyer ≠ + // owner, and the buyer signs). This intentionally also covers a doc + // owned by another *controlled* identity — the two-identities-in-one + // -app flow — not just externally-owned docs, matching the doc + // comment above. The Purchase sheet resolves the real on-chain price + // / for-sale state and disables the button when it isn't for sale. + if tradeable && !nonOwnerControlledIdentities.isEmpty { actions.append(.purchase) } return actions From ed85f556c7fddd302b4de85f0e35f1524d2bfa80 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Sun, 21 Jun 2026 20:53:08 +0300 Subject: [PATCH 2/2] fix(swift-example-app): gate marketplace actions on tradeMode == 1 Align the Purchase/Set Price gating with the DirectPurchase mode (`tradeMode == 1`) used elsewhere for marketplace operations (TransitionInputView), instead of the looser `> 0`, so the actions are not exposed for any future non-DirectPurchase trade mode. Functionally identical today (only modes 0/1 exist). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../SwiftExampleApp/Views/DocumentsView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift index b4e006288ea..dd4ddff5352 100644 --- a/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift +++ b/packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift @@ -275,7 +275,10 @@ struct DocumentDetailView: View { private var availableActions: [DocumentAction] { var actions: [DocumentAction] = [] let docType = documentTypeRow - let tradeable = (docType?.tradeMode ?? 0) > 0 + // `tradeMode == 1` is DirectPurchase — the only mode that supports + // listing/buying — matching the marketplace gating in + // TransitionInputView (`$0.tradeMode == 1`). + let tradeable = (docType?.tradeMode ?? 0) == 1 if ownerIsControlled { actions.append(.replace)