Skip to content

feat: handle deep links from different Mostro instances#552

Merged
grunch merged 17 commits into
mainfrom
feat/deep-link-mostro-switch
Apr 1, 2026
Merged

feat: handle deep links from different Mostro instances#552
grunch merged 17 commits into
mainfrom
feat/deep-link-mostro-switch

Conversation

@mostronatorcoder
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot commented Mar 27, 2026

Implements #541

Summary

When a mostro: deep link includes a mostro=<pubkey> parameter identifying a different Mostro instance than the currently connected one, the app now shows a confirmation dialog before switching.

How It Works

mostro:<order-id>?relays=<relay1>,<relay2>&mostro=<pubkey>
  1. parseMostroUrl extracts the optional mostro query parameter
  2. DeepLinkHandler compares the link pubkey with settings.mostroPublicKey
  3. If same (or absent) -> navigate directly (no change from current behavior)
  4. If different -> show confirmation dialog with truncated pubkeys
  5. If user confirms -> updateMostroInstance(newPubkey) then navigate to order
  6. If user cancels -> do nothing

Changes

File Change
lib/shared/utils/nostr_utils.dart Extract mostro query param in parseMostroUrl
lib/services/deep_link_service.dart Add mostroPubkey field to OrderInfo, pass through
lib/core/deep_link_handler.dart Pubkey comparison + switch confirmation dialog
lib/l10n/intl_en.arb English strings for dialog
lib/l10n/intl_es.arb Spanish strings for dialog
test/shared/utils/deep_link_parsing_test.dart Unit tests for URL parsing
docs/DEEP_LINK_MOSTRO_SWITCH.md Implementation documentation

What This Does NOT Change

  • Deep links without mostro= param work exactly as before (backward compatible)
  • No new dependencies
  • No UI changes to existing screens
  • Settings/Mostro switching logic unchanged (reuses updateMostroInstance)

Testing

  • URL parsing tested with and without mostro param
  • Same/different pubkey comparison tested
  • Empty mostro= param handled (treated as absent)
  • Multiple relays + pubkey combination tested

Closes #541

Summary by CodeRabbit

  • New Features

    • Deep-links may include an optional Mostro pubkey; app prompts "Switch & View" when it differs and will switch instance on confirmation. Duplicate deep-links are suppressed and a loading indicator is shown.
  • UI Changes

    • Order screen shows loader/placeholder when order data is missing; countdown shown only for pending orders; safer handling when order is null.
  • Bug Fixes

    • Submission state reliably resets; improved loading/dialog lifecycle and error handling for deep-link flow.
  • Localization

    • Added EN/ES/DE/FR/IT strings for the Mostro switch flow.
  • Documentation

    • Added docs describing the Mostro deep-link behavior.
  • Tests

    • Added parsing tests for Mostro pubkey extraction and validation.

Implements #541

When a deep link contains mostro=<pubkey> targeting a different Mostro
instance, the app shows a confirmation dialog before switching.

## Changes

- parseMostroUrl: extract optional 'mostro' query parameter
- OrderInfo: add mostroPubkey field
- DeepLinkHandler: compare link pubkey with current settings,
  show switch dialog if different, call updateMostroInstance on confirm
- l10n: add EN/ES strings for the switch dialog
- Tests: URL parsing with/without mostro param
- Docs: DEEP_LINK_MOSTRO_SWITCH.md

Backward compatible: links without mostro= param work as before.

Closes #541
@grunch
Copy link
Copy Markdown
Member

grunch commented Mar 27, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Parses optional mostro pubkey from Mostro deep links, propagates it to order resolution, adds duplicate/loading guards in the deep-link handler, prompts the user to confirm switching Mostro instances when the link targets a different pubkey, updates the instance on confirm, then navigates to the order. Adds i18n keys, docs, and tests.

Changes

Cohort / File(s) Summary
Deep Link Parsing & OrderInfo
lib/services/deep_link_service.dart, lib/shared/utils/nostr_utils.dart
OrderInfo gains nullable mostroPubkey. parseMostroUrl extracts/normalizes/validates optional mostro query param (64-char hex). _fetchOrderInfoById filters candidates by mostroPubkey, retries with default relays if needed, and returns null when no matching candidate exists for a specified pubkey.
Deep Link Handler & Dialog/Loading Logic
lib/core/deep_link_handler.dart
Added concurrency/duplicate suppression, loading-dialog lifecycle tracking, 2s duplicate suppression window, and _showMostroSwitchDialog. Handler compares deep-linked mostroPubkey to settings.mostroPublicKey; if different, prompts user and on accept calls updateMostroInstance(...) before navigating; ensures flags/dialog hidden in finally.
Order Screen & Action enum
lib/features/order/screens/take_order_screen.dart, lib/data/models/enums/action.dart
take_order_screen: improved submit flow with robust _isSubmitting resets, conditional rendering when order == null, and countdown only for pending orders. action.dart: minor map initializer formatting change.
Localization (i18n)
lib/l10n/intl_en.arb, lib/l10n/intl_es.arb, lib/l10n/intl_de.arb, lib/l10n/intl_fr.arb, lib/l10n/intl_it.arb
Added five i18n keys for the Mostro-instance switch dialog across locales and small JSON/indentation tweaks.
Documentation & Tests
docs/DEEP_LINK_MOSTRO_SWITCH.md, test/shared/utils/deep_link_parsing_test.dart
New docs describing Mostro deep-link switch behavior. Tests for parseMostroUrl/isValidMostroUrl cover pubkey normalization, valid/invalid pubkeys, relay handling, and comparison semantics versus current instance.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as DeepLinkHandler
    participant Parser as DeepLinkService/NostrUtils
    participant Settings as SettingsProvider
    participant Dialog as MostroSwitchDialog
    participant Router as GoRouter

    User->>App: Open deep link (orderId, relays, optional mostroPubkey)
    App->>Parser: parseMostroUrl(url) / processMostroLink(...)
    Parser-->>App: OrderInfo(orderId, relays, mostroPubkey)
    App->>Settings: read current mostroPublicKey
    Settings-->>App: currentPubkey

    alt mostroPubkey missing or equals currentPubkey
        App->>Router: Navigate to order using relays/orderId
    else mostroPubkey differs
        App->>Dialog: Show MostroSwitchDialog(linkPubkey, currentPubkey)
        Dialog->>User: Prompt "Switch & View" or "Cancel"
        alt User confirms
            User->>Dialog: Confirm
            Dialog-->>App: true
            App->>Settings: updateMostroInstance(mostroPubkey)
            Settings-->>App: updated
            App->>Router: Navigate to order
        else User cancels
            User->>Dialog: Cancel
            Dialog-->>App: false
            App-->>App: Abort navigation
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Catrya
  • AndreaDiazCorreia

Poem

🐰 I sniffed a link in moonlit code,
A pubkey far from my usual road.
"Switch and view?" I twitched, then hopped—
Through relays quick, the order popped.
A tiny hop, a big decode.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR introduces one out-of-scope change: take_order_screen.dart receives unrelated improvements (handling null orders, canceling behavior) beyond the Mostro deep-link feature. Remove take_order_screen.dart changes unrelated to Mostro deep links, or move them to a separate focused PR to maintain clear change scope.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature: handling deep links from different Mostro instances. It is directly related to the changeset's primary objective.
Linked Issues check ✅ Passed All key requirements from #541 are met: parsing mostro parameter [#541], showing confirmation dialog [#541], switching instance on confirmation [#541], preserving current behavior [#541], and backward compatibility [#541].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deep-link-mostro-switch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/services/deep_link_service.dart (1)

91-113: ⚠️ Potential issue | 🟠 Major

Don't drop the relay list after this lookup.

The PR objective explicitly says relays from the URL must be honored, but here relays are only used to resolve the initial order type. The returned OrderInfo still doesn't carry them, and DeepLinkHandler later only navigates with that model, so the order screen has no way to keep using the relay list from the link. A valid deep link can therefore resolve successfully here and still fail once the destination screen refetches against the current settings relays.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 91 - 113, The code
currently discards the relays parsed from the deep link after calling
_fetchOrderInfoById; ensure the relay list from the URL is preserved in the
returned model so downstream navigation uses the link relays. Update the logic
around fetchedOrderInfo (from _fetchOrderInfoById) to attach or merge the parsed
relays (relays variable) and mostroPubkey into the returned OrderInfo (or
whichever model DeepLinkHandler receives) so that DeepLinkHandler and the order
screen can continue using the link-provided relay list instead of falling back
to current settings.
🧹 Nitpick comments (2)
lib/core/deep_link_handler.dart (1)

167-205: Open this dialog on the next frame.

This new showDialog side effect is invoked directly from the handler, right after popping the loading dialog. The repo convention is to schedule dialogs and snackbars from a post-frame callback to avoid lifecycle/build races, which would also keep this flow aligned with the deferred navigation below.

As per coding guidelines, **/{lib,test}/**/*.dart: Use post-frame callbacks for side effects like SnackBars and dialogs to avoid state management issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/core/deep_link_handler.dart` around lines 167 - 205, The dialog is being
shown synchronously; change the showDialog call so it runs inside a post-frame
callback (e.g. WidgetsBinding.instance.addPostFrameCallback or
SchedulerBinding.instance.addPostFrameCallback) to match project convention and
avoid lifecycle/build races — wrap the existing showDialog<bool>(context:
context, builder: ...) invocation (the AlertDialog that references
truncatedLink, truncatedCurrent, s, dialogContext, etc.) inside that post-frame
callback and keep the same actions and returned bool, adding the needed import
if missing.
test/shared/utils/deep_link_parsing_test.dart (1)

63-88: These tests don't exercise the production switch logic.

Lines 67-86 only restate Dart == and null semantics, so they stay green even if the real deep-link comparison flow regresses. Replace them with coverage around a real helper/predicate or the handler path so malformed or normalized pubkeys are actually tested.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/shared/utils/deep_link_parsing_test.dart` around lines 63 - 88, Replace
the three trivial equality/null tests with tests that exercise the actual
deep-link parsing/comparison logic: use DeepLinkService (or the parsing/handler
that constructs OrderInfo) to parse a link into an OrderInfo and compare its
mostroPubkey against a current pubkey (and null) so normalization/malformed
variants are covered; specifically, call the real parser/handler that returns
OrderInfo (e.g., DeepLinkService.parse or OrderInfo.fromDeepLink) and assert
that mostroPubkey equality behaves correctly for identical hex, different hex,
normalized (mixed-case/leading zeros) and null values rather than using direct
`==` on raw strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/core/deep_link_handler.dart`:
- Around line 183-190: The dialog currently always shows truncated pubkeys
(using truncatedLink and truncatedCurrent) but should show node labels when
available; change the UI to accept two new string inputs (e.g., targetName and
currentName) or resolve them before building the Text widgets and render
"${s.deepLinkDifferentMostroFrom}\n${targetName.isNotEmpty ? targetName :
truncatedLink}" and
"${s.deepLinkDifferentMostroCurrent}\n${currentName.isNotEmpty ? currentName :
truncatedCurrent}"; update the caller(s) that build this dialog to pass the
resolved node labels (or empty string/null) so the Text widgets fallback to
truncatedLink/truncatedCurrent only when no label exists.

In `@lib/l10n/intl_en.arb`:
- Around line 1508-1512: Add the five missing deep-link localization keys to the
Italian ARB by inserting entries for deepLinkDifferentMostroTitle,
deepLinkDifferentMostroBody, deepLinkDifferentMostroFrom,
deepLinkDifferentMostroCurrent, and deepLinkSwitchAndView with appropriate
Italian translations (matching the English meanings) so Italian falls back no
longer; ensure each key uses the same key names and string values in Italian and
follows the existing ARB formatting/conventions.

In `@lib/services/deep_link_service.dart`:
- Around line 177-179: The code maps any non-'sell' k tag to OrderType.buy,
which misclassifies unknown k values; change the parsing so that when
orderTypeValue is not exactly 'sell' or 'buy' the function returns null (or
otherwise fails fast) instead of defaulting to OrderType.buy. Locate the
assignment using orderTypeValue and OrderType (in deep_link_service.dart) and
implement a strict branch that maps 'sell' -> OrderType.sell, 'buy' ->
OrderType.buy, and returns null on any other value (mirroring the behavior of
NostrService.fetchOrderInfoByEventId()).

In `@lib/shared/utils/nostr_utils.dart`:
- Around line 177-181: Validate and normalize the extracted mostro pubkey before
adding it to result: in the block that reads uri.queryParameters['mostro']
(variable mostroPubkey) only accept a 64-character hexadecimal string (e.g.
apply a /^[0-9a-f]{64}$/i check after trimming and removing any leading 0x, then
normalize to lowercase) and set result['mostroPubkey'] to that normalized value;
otherwise ignore the parameter (do not propagate malformed values) so downstream
code like updateMostroInstance(...) never receives invalid keys.

---

Outside diff comments:
In `@lib/services/deep_link_service.dart`:
- Around line 91-113: The code currently discards the relays parsed from the
deep link after calling _fetchOrderInfoById; ensure the relay list from the URL
is preserved in the returned model so downstream navigation uses the link
relays. Update the logic around fetchedOrderInfo (from _fetchOrderInfoById) to
attach or merge the parsed relays (relays variable) and mostroPubkey into the
returned OrderInfo (or whichever model DeepLinkHandler receives) so that
DeepLinkHandler and the order screen can continue using the link-provided relay
list instead of falling back to current settings.

---

Nitpick comments:
In `@lib/core/deep_link_handler.dart`:
- Around line 167-205: The dialog is being shown synchronously; change the
showDialog call so it runs inside a post-frame callback (e.g.
WidgetsBinding.instance.addPostFrameCallback or
SchedulerBinding.instance.addPostFrameCallback) to match project convention and
avoid lifecycle/build races — wrap the existing showDialog<bool>(context:
context, builder: ...) invocation (the AlertDialog that references
truncatedLink, truncatedCurrent, s, dialogContext, etc.) inside that post-frame
callback and keep the same actions and returned bool, adding the needed import
if missing.

In `@test/shared/utils/deep_link_parsing_test.dart`:
- Around line 63-88: Replace the three trivial equality/null tests with tests
that exercise the actual deep-link parsing/comparison logic: use DeepLinkService
(or the parsing/handler that constructs OrderInfo) to parse a link into an
OrderInfo and compare its mostroPubkey against a current pubkey (and null) so
normalization/malformed variants are covered; specifically, call the real
parser/handler that returns OrderInfo (e.g., DeepLinkService.parse or
OrderInfo.fromDeepLink) and assert that mostroPubkey equality behaves correctly
for identical hex, different hex, normalized (mixed-case/leading zeros) and null
values rather than using direct `==` on raw strings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 88974119-1f47-4ad9-b1be-448320ab6d68

📥 Commits

Reviewing files that changed from the base of the PR and between 3e276a2 and 392327f.

📒 Files selected for processing (7)
  • docs/DEEP_LINK_MOSTRO_SWITCH.md
  • lib/core/deep_link_handler.dart
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
  • lib/services/deep_link_service.dart
  • lib/shared/utils/nostr_utils.dart
  • test/shared/utils/deep_link_parsing_test.dart

Comment thread lib/core/deep_link_handler.dart Outdated
Comment thread lib/l10n/intl_en.arb
Comment thread lib/services/deep_link_service.dart Outdated
Comment thread lib/shared/utils/nostr_utils.dart Outdated
…n, dialog

- Validate mostro pubkey: must be 64-char hex, normalized to lowercase
  (rejects malformed/short/non-hex values silently)
- Strict k-tag parsing: unknown order types return null instead of
  defaulting to OrderType.buy
- Add l10n strings for IT, DE, FR (deep link switch dialog)
- Dialog: accept optional targetName/currentName for node labels,
  fall back to truncated pubkeys. Uses post-frame callback to avoid
  lifecycle/build races.
- Tests: pubkey validation (short, non-hex, uppercase normalization)
Tests now exercise NostrUtils.parseMostroUrl for instance comparison:
- Same pubkey matches current instance
- Different pubkey does not match
- Mixed-case normalized and matches lowercase
- Absent mostro param treated as same instance
- Malformed pubkey silently dropped (treated as same instance)
Copy link
Copy Markdown
Member

@Catrya Catrya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment I tap “Switch & View,” a red error screen appears for a few seconds with the message “Null check operator used on a null value.” Then, after switching to Mostro and viewing the order, it gets stuck on “Loading order...” and does not disappear unless the user manually presses the back button. I’m sharing all the logs.

logs.txt

Image Image

@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 30, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Catrya
Catrya previously approved these changes Mar 30, 2026
Copy link
Copy Markdown
Member

@Catrya Catrya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/services/deep_link_service.dart (1)

93-113: ⚠️ Potential issue | 🟠 Major

Bind the link's Mostro pubkey to the fetched event.

mostroPubkey is passed through from the query string, but _fetchOrderInfoById() still selects events.first purely by #d. A crafted or ambiguous link can therefore resolve order A, switch the app to node B, and then open the order under the wrong instance. When mostroPubkey is present, only accept an event from that author, or reject the link on mismatch.

Also applies to: 166-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 93 - 113, The fetched order
selection must respect the optional mostroPubkey from the query string: update
_fetchOrderInfoById (and any secondary fetch path used around the other block)
to, when a non-null mostroPubkey is provided, only accept an event whose
author/pubkey equals mostroPubkey rather than unconditionally using
events.first; if no matching event exists return
DeepLinkResult.error(S.of(context)!.deepLinkInvalidOrderId) (or the existing
error for mismatch) and log the mismatch. Locate usages of mostroPubkey and
events.first in _fetchOrderInfoById (and the follow-up fetch path referenced in
the other block) and add the author check/filter, rejecting the link when the
author does not match.
🧹 Nitpick comments (1)
test/shared/utils/deep_link_parsing_test.dart (1)

69-79: Lock in the full pubkey-normalization contract.

The parser also trims whitespace and strips a leading 0x before validating the pubkey, but this suite only locks lowercase normalization. Please add one case like " mostro=0xABC... " so those behaviors do not regress silently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/shared/utils/deep_link_parsing_test.dart` around lines 69 - 79, Add a
new unit test for NostrUtils.parseMostroUrl that asserts the parser trims
surrounding whitespace and strips a leading "0x" before validating/normalizing
the pubkey: create a test (e.g., 'normalizes pubkey with 0x and whitespace to
lowercase') that calls NostrUtils.parseMostroUrl with a URL containing mostro="
0xABC... " (uppercased, with spaces and 0x) and assert result!['mostroPubkey']
equals the lowercase hex string without the 0x prefix; this ensures the trimming
and 0x-stripping behavior is locked in alongside the existing lowercase
normalization test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Around line 88-89: The _CountdownWidget is being rendered for all orders —
change the rendering so _CountdownWidget(order: order) is only included when
order.status indicates pending (e.g., OrderStatus.pending or equivalent) and for
non-pending waiting orders render the separate waiting countdown system (or
nothing) as per guidelines; also add a pending-status guard inside
_buildCountDownTime so it returns a meaningful value only for pending orders
(and use the alternate countdown logic for waiting orders); apply the same
change where similar countdowns are rendered in the code block around the other
mentioned section (the block referenced by the reviewer).
- Around line 258-259: After awaiting showDialog<int>(...) in the
TakeOrderScreen state, guard any post-await UI updates by checking the widget is
still mounted; specifically, before calling setState(...) after the await, add
`if (!mounted) return;` to avoid calling setState on a disposed State. Apply the
same mounted check for the other post-await update block around the showDialog
result handling (the code that reads enteredAmount and then calls setState) so
all BuildContext/use of setState after async awaits in this State are protected.
- Around line 137-141: The code is doing brittle English-only surgery by calling
replaceAll(' Sats', '') on localized titles; update localization to add
dedicated keys with placeholders for amount (e.g., someoneIsSellingWithAmount
and someoneIsBuyingWithAmount) and use those keys with the amount plus the
hardcoded "Sats" suffix when rendering (referencing
S.of(context)!.someoneIsSellingWithAmount / someoneIsBuyingWithAmount,
widget.orderType and order.amount) so you avoid manipulating localized strings
directly and preserve correct translations across locales.
- Around line 66-67: The UI currently shows a spinner whenever order == null,
which hides error/not-found states; update the body to only show
CircularProgressIndicator when the provider indicates a loading state (e.g.,
check eventProvider.isLoading or AsyncValue.isLoading), and render an
error/empty-state widget when order == null but provider is in error or
not-found state; specifically modify the conditional around the order variable
in take_order_screen.dart to use eventProvider's status (or
AsyncValue.hasError/whenData/when) instead of just order == null so loading,
error, and empty are handled separately.

In `@lib/l10n/intl_de.arb`:
- Around line 1508-1512: The new German strings use formal "Sie" and the word
"Bestellung" which is inconsistent with the repo's informal tone and existing
term "Order"; update the keys deepLinkDifferentMostroBody,
deepLinkDifferentMostroFrom, deepLinkDifferentMostroCurrent (and any related
deepLinkDifferentMostroTitle / deepLinkSwitchAndView if needed) to use informal
"du"/"dich" phrasing and replace "Bestellung" with the project's established
German term "Order" so the dialog matches the rest of intl_de.arb.

---

Outside diff comments:
In `@lib/services/deep_link_service.dart`:
- Around line 93-113: The fetched order selection must respect the optional
mostroPubkey from the query string: update _fetchOrderInfoById (and any
secondary fetch path used around the other block) to, when a non-null
mostroPubkey is provided, only accept an event whose author/pubkey equals
mostroPubkey rather than unconditionally using events.first; if no matching
event exists return DeepLinkResult.error(S.of(context)!.deepLinkInvalidOrderId)
(or the existing error for mismatch) and log the mismatch. Locate usages of
mostroPubkey and events.first in _fetchOrderInfoById (and the follow-up fetch
path referenced in the other block) and add the author check/filter, rejecting
the link when the author does not match.

---

Nitpick comments:
In `@test/shared/utils/deep_link_parsing_test.dart`:
- Around line 69-79: Add a new unit test for NostrUtils.parseMostroUrl that
asserts the parser trims surrounding whitespace and strips a leading "0x" before
validating/normalizing the pubkey: create a test (e.g., 'normalizes pubkey with
0x and whitespace to lowercase') that calls NostrUtils.parseMostroUrl with a URL
containing mostro=" 0xABC... " (uppercased, with spaces and 0x) and assert
result!['mostroPubkey'] equals the lowercase hex string without the 0x prefix;
this ensures the trimming and 0x-stripping behavior is locked in alongside the
existing lowercase normalization test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ddb978c-0ce7-409f-abba-651a5a88d732

📥 Commits

Reviewing files that changed from the base of the PR and between 392327f and 327b710.

📒 Files selected for processing (8)
  • lib/core/deep_link_handler.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/l10n/intl_de.arb
  • lib/l10n/intl_fr.arb
  • lib/l10n/intl_it.arb
  • lib/services/deep_link_service.dart
  • lib/shared/utils/nostr_utils.dart
  • test/shared/utils/deep_link_parsing_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/shared/utils/nostr_utils.dart
  • lib/core/deep_link_handler.dart

Comment thread lib/features/order/screens/take_order_screen.dart Outdated
Comment thread lib/features/order/screens/take_order_screen.dart Outdated
Comment thread lib/features/order/screens/take_order_screen.dart
Comment thread lib/features/order/screens/take_order_screen.dart
Comment thread lib/l10n/intl_de.arb
- Distinguish loading vs error/not-found state when order == null
  (show spinner only while loading, icon when not found)
- Guard _CountdownWidget to only render for pending orders
- Add mounted check before setState after awaited showDialog
- Fix German locale to use informal du/dich and 'Order' instead of
  'Bestellung' to match existing intl_de.arb tone and terminology
- Filter fetched NIP-69 events by event.pubkey when mostroPubkey is
  present in the deep link
- Reject the link if no event from that specific Mostro node is found
- Log a warning with event count from other nodes for easier debugging
- Prevents a crafted link from switching the app to a fraudulent Mostro
  instance by exploiting a legitimate order published by a different node
- Falls back to existing behavior (events.first) when mostroPubkey is
  absent from the link
@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 31, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
lib/features/order/screens/take_order_screen.dart (1)

145-149: ⚠️ Potential issue | 🟡 Minor

Stop editing translated titles with replaceAll(' Sats', '').

This only works for the current English copy. Please add dedicated localized templates for the amount variants instead of mutating an already-translated title at runtime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/screens/take_order_screen.dart` around lines 145 - 149,
The code is mutating translated titles with replaceAll(' Sats', ''); instead add
dedicated localized templates and use them when hasFixedSatsAmount is true:
create new i18n entries (e.g., someoneIsSellingWithAmount and
someoneIsBuyingWithAmount) that accept an amount parameter, then update the Text
call in take_order_screen.dart to call
S.of(context)!.someoneIsSellingWithAmount(order.amount) for OrderType.sell (and
someoneIsBuyingWithAmount for the buy case) instead of manipulating
someoneIsSellingTitle/someoneIsBuyingTitle with replaceAll; keep the existing
titles when hasFixedSatsAmount is false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Around line 47-58: The listener currently only clears _isSubmitting for
dialog-cancel and actions.Action.cantDo, leaving the CTA disabled for other
terminal outcomes; update the
ref.listen(mostroMessageStreamProvider(widget.orderId)) handler to reset
_isSubmitting for every terminal submit path by checking msg.action against all
terminal outcomes (not just actions.Action.cantDo) and calling setState(() =>
_isSubmitting = false) when a terminal action is observed (use _lastSeenAction
to avoid duplicates), and also add finally blocks or catch/finally handling to
the takeBuyOrder and takeSellOrder call sites to ensure _isSubmitting is cleared
when those futures complete or throw. Ensure you reference and update the same
symbols: ref.listen, mostroMessageStreamProvider, _lastSeenAction,
_isSubmitting, actions.Action.cantDo, takeBuyOrder, and takeSellOrder.

In `@lib/services/deep_link_service.dart`:
- Around line 172-176: The warning message in deep_link_service.dart is escaping
the interpolation so it logs the literal "\${events.length}" instead of the
actual count; update the logger.w call (the block that checks
candidateEvents.isEmpty && mostroPubkey != null) to use real string
interpolation for events.length (e.g., include ${events.length}) so the log
shows how many mismatched-node events were seen for orderId and mostroPubkey.
- Around line 165-170: Filter candidate events only after verifying signatures:
when building candidateEvents (using mostroPubkey, events, and e.pubkey) first
filter events by event.isVerified() and then apply the mostroPubkey equality
check so you only trust verified authors; update the logic that assigns
candidateEvents to use events.where((e) => e.isVerified() && (mostroPubkey ==
null || e.pubkey == mostroPubkey)). Also fix the log interpolation by changing
the escaped string "\${events.length}" to use real interpolation
"${events.length}" so the event count is printed.

---

Duplicate comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Around line 145-149: The code is mutating translated titles with replaceAll('
Sats', ''); instead add dedicated localized templates and use them when
hasFixedSatsAmount is true: create new i18n entries (e.g.,
someoneIsSellingWithAmount and someoneIsBuyingWithAmount) that accept an amount
parameter, then update the Text call in take_order_screen.dart to call
S.of(context)!.someoneIsSellingWithAmount(order.amount) for OrderType.sell (and
someoneIsBuyingWithAmount for the buy case) instead of manipulating
someoneIsSellingTitle/someoneIsBuyingTitle with replaceAll; keep the existing
titles when hasFixedSatsAmount is false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 916c46ba-0904-4f7f-9e77-90072dd9c9d7

📥 Commits

Reviewing files that changed from the base of the PR and between 327b710 and 79fbe72.

📒 Files selected for processing (3)
  • lib/features/order/screens/take_order_screen.dart
  • lib/l10n/intl_de.arb
  • lib/services/deep_link_service.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/l10n/intl_de.arb

Comment thread lib/features/order/screens/take_order_screen.dart
Comment thread lib/services/deep_link_service.dart Outdated
Comment thread lib/services/deep_link_service.dart
… state

- Add isVerified() check per event before accepting it as a candidate
  in _fetchOrderInfoById; log a warning on failure but do not reject
  the event, consistent with how mostro_nodes_notifier handles kind-0
  events (known dart_nostr limitation)
- Fix escaped string interpolation in logger.w so ${events.length}
  now correctly prints the actual event count instead of the literal text
- Expand the terminal-action set in mostroMessageStreamProvider listener
  to include canceled, paymentFailed and holdInvoicePaymentCanceled
  alongside cantDo so _isSubmitting is cleared for every terminal outcome
- Wrap takeBuyOrder and takeSellOrder call sites in try/catch blocks to
  ensure _isSubmitting is reset when those futures throw, preventing the
  CTA button from being permanently disabled after an error
@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 31, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/services/deep_link_service.dart (1)

148-188: ⚠️ Potential issue | 🟠 Major

Run the default-relay fallback after the Mostro filter, not before it.

The retry is still gated by events.isEmpty. With mostroPubkey present, a non-empty batch of same-d events from other nodes skips the fallback and returns null immediately, even though the correct order may still exist on the app's default relays. This should retry when candidateEvents is empty too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 148 - 188, The fallback to
default relays is currently conditioned on events.isEmpty which skips retry when
events exist but none match mostroPubkey; change the flow so that after calling
nostrService.fetchEvents with specific relays and computing candidateEvents (the
filtered list using mostroPubkey), if candidateEvents.isEmpty and default relays
are available then call nostrService.fetchEvents(filter) to fetch defaultEvents,
merge into events, and recompute candidateEvents (applying the same mostroPubkey
filter and verification logging) before returning or logging the final warning
for orderId; use the existing symbols events, candidateEvents, relays,
mostroPubkey, nostrService.fetchEvents, filter and logger to locate and
implement this adjustment.
♻️ Duplicate comments (3)
lib/services/deep_link_service.dart (1)

165-180: ⚠️ Potential issue | 🔴 Critical

Reject unverified events before trusting e.pubkey.

mostro= only protects this flow if the matching event is authenticated. Right now a failed isVerified() just logs and the event still qualifies, so an attacker-controlled relay can fabricate a matching pubkey and drive the wrong Mostro-switch path. If this dart_nostr version does false-negative on valid events, this path needs a stronger verification source rather than a silent bypass.

🔒 Suggested guard
-      final candidateEvents = mostroPubkey != null
-          ? events.where((e) {
-              if (!e.isVerified()) {
-                logger.w(
-                  'Event ${e.id} from pubkey ${e.pubkey} failed signature '
-                  'verification (possible dart_nostr limitation).',
-                );
-              }
-              return e.pubkey == mostroPubkey;
-            }).toList()
+      final candidateEvents = mostroPubkey != null
+          ? events.where((e) {
+              final verified = e.isVerified();
+              if (!verified) {
+                logger.w(
+                  'Ignoring unverified event ${e.id} for order $orderId '
+                  'from pubkey ${e.pubkey}.',
+                );
+              }
+              return verified && e.pubkey == mostroPubkey;
+            }).toList()
           : events;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 165 - 180, The current
filter for candidateEvents lets events with failing e.isVerified() pass as long
as e.pubkey matches mostroPubkey; update the predicate used in events.where so
that unverified events are rejected before comparing e.pubkey (i.e., if
!e.isVerified() then log a warning and return false), ensuring candidateEvents
only includes events where e.isVerified() is true and e.pubkey == mostroPubkey;
touch the filter closure that builds candidateEvents and keep the existing
logger.w call but move it to the rejection branch.
lib/features/order/screens/take_order_screen.dart (2)

47-66: ⚠️ Potential issue | 🟠 Major

_isSubmitting can still stick on uncovered terminal actions.

This listener now handles more cases, but the inline set still only covers four outcomes. lib/data/models/enums/action.dart, Lines 1-44 contains other terminal states like adminCanceled, adminSettled, cooperativeCancelAccepted, released, and rateReceived; if any of those end the flow on this screen, the CTA stays disabled. This should be centralized instead of maintained as a partial set here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/screens/take_order_screen.dart` around lines 47 - 66, The
listener in TakeOrderScreen (the ref.listen block using
mostroMessageStreamProvider and the _isSubmitting flag) only resets
_isSubmitting for a hard-coded four-action set, leaving other terminal actions
in actions.Action unhandled; add a centralized predicate (e.g.,
Action.isTerminal or a helper function isTerminalAction(Action a) placed with
the enum in lib/data/models/enums/action.dart) that returns true for all
terminal outcomes (adminCanceled, adminSettled, cooperativeCancelAccepted,
released, rateReceived, cantDo, canceled, paymentFailed,
holdInvoicePaymentCanceled, etc.), then replace the inline
terminalActions.contains check in the listener with that predicate so all
terminal states automatically reset _isSubmitting.

153-159: ⚠️ Potential issue | 🟡 Minor

Don't splice localized titles with English text.

replaceAll(' Sats', '') depends on the current English translation shape. A locale change or copy edit will either do nothing or remove the wrong text. Add dedicated l10n keys with an amount placeholder and append the hardcoded Sats suffix instead of rewriting localized output at runtime.

Based on learnings: Do not localize the term 'sats' (satoshis) in the Mostro mobile app. Treat 'sats' as a universal Bitcoin unit and keep it as hardcoded text in Dart UI code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/screens/take_order_screen.dart` around lines 153 - 159,
The current code splices localized strings with English text by using
replaceAll(' Sats', '') on S.of(context)!.someoneIsSellingTitle /
someoneIsBuyingTitle; instead add new l10n keys with an amount placeholder
(e.g., someoneIsSellingWithAmount and someoneIsBuyingWithAmount) that accept the
amount, use those via S.of(context)!.someoneIsSellingWithAmount(order.amount) /
someoneIsBuyingWithAmount(order.amount) when hasFixedSatsAmount is true, and
then append the hardcoded " Sats" suffix in the UI (do not attempt to localize
or strip "Sats"); remove any replaceAll usage and keep the branch using
widget.orderType and order.amount to select the correct localized key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Around line 75-84: The empty-state fires too early because you only check
order == null and orderEventsAsync.isLoading; instead show the search icon only
after the stream/provider has actually emitted and confirmed "no value". Change
the ternary in take_order_screen (the body that reads order and
orderEventsAsync) to display the loading spinner while the provider/stream has
not yet emitted, and only show the search Icon when the stream has
emitted/settled with no order (e.g. replace the isLoading check with a guard
like !orderEventsAsync.hasValue or !orderEventsAsync.hasData / snapshot.hasData
depending on your provider API), so switching Mostro via updateMostroInstance()
won’t trigger the empty-state prematurely.

---

Outside diff comments:
In `@lib/services/deep_link_service.dart`:
- Around line 148-188: The fallback to default relays is currently conditioned
on events.isEmpty which skips retry when events exist but none match
mostroPubkey; change the flow so that after calling nostrService.fetchEvents
with specific relays and computing candidateEvents (the filtered list using
mostroPubkey), if candidateEvents.isEmpty and default relays are available then
call nostrService.fetchEvents(filter) to fetch defaultEvents, merge into events,
and recompute candidateEvents (applying the same mostroPubkey filter and
verification logging) before returning or logging the final warning for orderId;
use the existing symbols events, candidateEvents, relays, mostroPubkey,
nostrService.fetchEvents, filter and logger to locate and implement this
adjustment.

---

Duplicate comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Around line 47-66: The listener in TakeOrderScreen (the ref.listen block using
mostroMessageStreamProvider and the _isSubmitting flag) only resets
_isSubmitting for a hard-coded four-action set, leaving other terminal actions
in actions.Action unhandled; add a centralized predicate (e.g.,
Action.isTerminal or a helper function isTerminalAction(Action a) placed with
the enum in lib/data/models/enums/action.dart) that returns true for all
terminal outcomes (adminCanceled, adminSettled, cooperativeCancelAccepted,
released, rateReceived, cantDo, canceled, paymentFailed,
holdInvoicePaymentCanceled, etc.), then replace the inline
terminalActions.contains check in the listener with that predicate so all
terminal states automatically reset _isSubmitting.
- Around line 153-159: The current code splices localized strings with English
text by using replaceAll(' Sats', '') on S.of(context)!.someoneIsSellingTitle /
someoneIsBuyingTitle; instead add new l10n keys with an amount placeholder
(e.g., someoneIsSellingWithAmount and someoneIsBuyingWithAmount) that accept the
amount, use those via S.of(context)!.someoneIsSellingWithAmount(order.amount) /
someoneIsBuyingWithAmount(order.amount) when hasFixedSatsAmount is true, and
then append the hardcoded " Sats" suffix in the UI (do not attempt to localize
or strip "Sats"); remove any replaceAll usage and keep the branch using
widget.orderType and order.amount to select the correct localized key.

In `@lib/services/deep_link_service.dart`:
- Around line 165-180: The current filter for candidateEvents lets events with
failing e.isVerified() pass as long as e.pubkey matches mostroPubkey; update the
predicate used in events.where so that unverified events are rejected before
comparing e.pubkey (i.e., if !e.isVerified() then log a warning and return
false), ensuring candidateEvents only includes events where e.isVerified() is
true and e.pubkey == mostroPubkey; touch the filter closure that builds
candidateEvents and keep the existing logger.w call but move it to the rejection
branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9bcac224-7dd8-43d1-9e5b-8edd235d197c

📥 Commits

Reviewing files that changed from the base of the PR and between 79fbe72 and bb3aeb7.

📒 Files selected for processing (2)
  • lib/features/order/screens/take_order_screen.dart
  • lib/services/deep_link_service.dart

Comment thread lib/features/order/screens/take_order_screen.dart
…re empty-state

- Add Action.isTerminal getter to the Action enum covering all terminal
  outcomes (cantDo, canceled, adminCanceled, adminSettled,
  cooperativeCancelAccepted, released, rateReceived, paymentFailed,
  holdInvoicePaymentCanceled, holdInvoicePaymentSettled, purchaseCompleted)
  so the UI never needs to maintain a partial hard-coded set
- Replace inline terminalActions set in TakeOrderScreen listener with
  msg.action.isTerminal for automatic coverage of future terminal actions
- Fix relay fallback logic in _fetchOrderInfoById: retry with default
  relays when candidateEvents is empty (not just when events is empty),
  which previously skipped the fallback when link relays returned events
  from other Mostro nodes that did not match the requested mostroPubkey
- Fix premature empty-state icon after Mostro switch by checking
  !orderEventsAsync.hasValue instead of orderEventsAsync.isLoading;
  the StreamProvider does not re-enter loading when the repository
  updates in place via updateMostroInstance so isLoading was unreliable
@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 31, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
lib/services/deep_link_service.dart (1)

162-171: ⚠️ Potential issue | 🔴 Critical

Reject unverified events before matching the linked Mostro.

Line 165 logs a failed signature check but still falls through to e.pubkey == mostroPubkey. A forged event from a link relay can then satisfy the new mostro gate and suppress the default-relay fallback, so this flow can resolve or switch against an unverified author. If isVerified() has false negatives in the current dart_nostr version, that needs a separate fix; this filter should fail closed here.

🐛 Proposed fix
       List<NostrEvent> buildCandidates(List<NostrEvent> raw) {
         if (mostroPubkey == null) return raw;
         return raw.where((e) {
           if (!e.isVerified()) {
             logger.w(
-              'Event ${e.id} from pubkey ${e.pubkey} failed signature '
-              'verification (possible dart_nostr limitation).',
+              'Ignoring event ${e.id} from pubkey ${e.pubkey}: '
+              'signature verification failed.',
             );
+            return false;
           }
           return e.pubkey == mostroPubkey;
         }).toList();
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 162 - 171, buildCandidates
currently logs failed signature checks but still returns events where e.pubkey
== mostroPubkey, allowing unverified/forged events to pass; change
buildCandidates to immediately reject events where !e.isVerified() (i.e., filter
them out before comparing e.pubkey to mostroPubkey) so only verified events are
considered for the mostroPubkey match; keep the existing logger.w call for
telemetry but do not let unverified events reach the e.pubkey == mostroPubkey
check in buildCandidates.
🧹 Nitpick comments (1)
lib/features/order/screens/take_order_screen.dart (1)

430-435: Consider logging caught exceptions.

The caught exception is silently discarded. Adding a log statement would aid debugging without changing user-facing behavior.

♻️ Suggested improvement
+import 'package:mostro_mobile/services/logger_service.dart';
 ...
                        } catch (e) {
+                         logger.e('Failed to take order', error: e);
                          if (!mounted) return;
                          setState(() {
                            _isSubmitting = false;
                          });
                        }

Apply the same pattern to the other catch block at lines 463-468.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/screens/take_order_screen.dart` around lines 430 - 435,
The catch blocks that only check mounted and setState to clear _isSubmitting
swallow exceptions; change both catch clauses to capture the exception and stack
trace (e.g., catch (e, stackTrace)) and log them before calling setState—use an
existing logger or Flutter's debugPrint/developer.log to print a clear message
including the exception and stackTrace; update both occurrences around the
setState({_isSubmitting = false}) in the TakeOrderScreen so errors are recorded
while preserving the current user-facing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Line 7: The code uses msg.action.isTerminal but only imports status.dart
(which defines Status); add the missing import for the Action enum so the
compiler can resolve Action and its isTerminal getter by importing the file that
defines it (import the package defining Action, i.e., the enums/action.dart
module) so that references like msg.action.isTerminal compile.

---

Duplicate comments:
In `@lib/services/deep_link_service.dart`:
- Around line 162-171: buildCandidates currently logs failed signature checks
but still returns events where e.pubkey == mostroPubkey, allowing
unverified/forged events to pass; change buildCandidates to immediately reject
events where !e.isVerified() (i.e., filter them out before comparing e.pubkey to
mostroPubkey) so only verified events are considered for the mostroPubkey match;
keep the existing logger.w call for telemetry but do not let unverified events
reach the e.pubkey == mostroPubkey check in buildCandidates.

---

Nitpick comments:
In `@lib/features/order/screens/take_order_screen.dart`:
- Around line 430-435: The catch blocks that only check mounted and setState to
clear _isSubmitting swallow exceptions; change both catch clauses to capture the
exception and stack trace (e.g., catch (e, stackTrace)) and log them before
calling setState—use an existing logger or Flutter's debugPrint/developer.log to
print a clear message including the exception and stackTrace; update both
occurrences around the setState({_isSubmitting = false}) in the TakeOrderScreen
so errors are recorded while preserving the current user-facing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 711c657b-b904-418a-805a-4edd89c2536c

📥 Commits

Reviewing files that changed from the base of the PR and between bb3aeb7 and b68cac0.

📒 Files selected for processing (3)
  • lib/data/models/enums/action.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/services/deep_link_service.dart

Comment thread lib/features/order/screens/take_order_screen.dart
Mostronator added 2 commits March 31, 2026 16:50
- Re-add import for action.dart (without alias) so Action.isTerminal
  resolves correctly; the previous commit removed it when dropping the
  unused 'as actions' prefix but isTerminal still requires the import
- Add logger import from logger_service.dart
- Capture exception and stack trace in both catch blocks around
  takeBuyOrder and takeSellOrder so errors are logged via logger.e
  before clearing _isSubmitting, aiding future debugging
The Action type is resolved transitively through MostroMessage, which
already imports action.dart. A direct import in take_order_screen.dart
is unnecessary and triggers an unused_import warning that fails CI.
@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 31, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
lib/services/deep_link_service.dart (1)

157-172: ⚠️ Potential issue | 🔴 Critical

Reject unverified events before trusting e.pubkey.

Line 171 still accepts an event after Line 165 reports that signature verification failed. That lets a relay spoof mostroPubkey with an invalid event and bypass the new node-isolation path.

🔒 Minimal hardening
       List<NostrEvent> buildCandidates(List<NostrEvent> raw) {
         if (mostroPubkey == null) return raw;
         return raw.where((e) {
-          if (!e.isVerified()) {
+          final isVerified = e.isVerified();
+          if (!isVerified) {
             logger.w(
-              'Event ${e.id} from pubkey ${e.pubkey} failed signature '
-              'verification (possible dart_nostr limitation).',
+              'Ignoring unverified event ${e.id} from pubkey ${e.pubkey}.',
             );
+            return false;
           }
           return e.pubkey == mostroPubkey;
         }).toList();
       }
For the `dart_nostr` version used in MostroP2P/mobile, what does `NostrEvent.isVerified()` validate, and is there any documented reason to accept an event after `isVerified()` returns false?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 157 - 172, The
buildCandidates function currently logs failed signature verification but still
accepts events (allowing a relay to spoof mostroPubkey); change the filter so
events are only accepted when e.isVerified() is true and e.pubkey ==
mostroPubkey (i.e., short-circuit reject unverified events), keep the existing
logger.w for failures, and ensure you reference the same symbols
(buildCandidates, mostroPubkey, NostrEvent.isVerified) when updating the
predicate so unverified events cannot bypass node-isolation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/data/models/enums/action.dart`:
- Around line 68-80: The isTerminal getter currently omits Action.release which
causes optimistic local emits to not be treated as terminal; update the const
set in the isTerminal getter to include Action.release, and also ensure the
status-mapping logic that consumes Action values treats both Action.release and
Action.released as Status.settledHoldInvoice (e.g., update the mapping function
that converts Action -> Status) so submit/loading state clears correctly for
both optimistic outgoing and confirmed events.

---

Duplicate comments:
In `@lib/services/deep_link_service.dart`:
- Around line 157-172: The buildCandidates function currently logs failed
signature verification but still accepts events (allowing a relay to spoof
mostroPubkey); change the filter so events are only accepted when e.isVerified()
is true and e.pubkey == mostroPubkey (i.e., short-circuit reject unverified
events), keep the existing logger.w for failures, and ensure you reference the
same symbols (buildCandidates, mostroPubkey, NostrEvent.isVerified) when
updating the predicate so unverified events cannot bypass node-isolation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a6022ff-d0c6-4c0a-b534-0947c1d2e057

📥 Commits

Reviewing files that changed from the base of the PR and between bb3aeb7 and b8ee090.

📒 Files selected for processing (3)
  • lib/data/models/enums/action.dart
  • lib/features/order/screens/take_order_screen.dart
  • lib/services/deep_link_service.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/features/order/screens/take_order_screen.dart

Comment thread lib/data/models/enums/action.dart Outdated
- Remove isTerminal getter from Action enum: it contained actions that
  cannot occur for an order in pending status (released, paymentFailed,
  holdInvoicePaymentCanceled, adminCanceled, cooperativeCancelAccepted,
  etc.), making it semantically incorrect and leaking UI logic into the
  domain enum
- Per the Mostro protocol, only cantDo and canceled can arrive in
  TakeOrderScreen — cooperative cancel, disputes and admin actions
  require the order to be active/in-progress, not pending
- Replace msg.action.isTerminal with an explicit two-value check
  (Action.cantDo || Action.canceled) that reflects the actual protocol
- Add direct import for action.dart since Action values are now
  referenced explicitly instead of via a getter on an inferred type
@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 31, 2026

@CodeRabbit review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

… Action

flutter/material.dart also exports an Action class (keyboard shortcuts).
Import mostro action.dart with 'as mostro_action' alias so the
compiler resolves Action.cantDo and Action.canceled unambiguously.
@Catrya
Copy link
Copy Markdown
Member

Catrya commented Mar 31, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@grunch
Copy link
Copy Markdown
Member

grunch commented Apr 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…fing

Events that fail isVerified() are now rejected (return false) instead
of being accepted when pubkey matches. This prevents an attacker from
publishing a fake event with the target Mostro pubkey and bypassing
the signature check.
@grunch
Copy link
Copy Markdown
Member

grunch commented Apr 1, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Member

@grunch grunch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
lib/services/deep_link_service.dart (1)

166-169: ⚠️ Potential issue | 🟡 Minor

Fix the escaped string interpolations in the warning log.

\${e.id} and \${e.pubkey} will be logged literally instead of showing the actual event ID and pubkey. Remove the backslashes.

🐛 Proposed fix
           logger.w(
-              'Event \${e.id} from pubkey \${e.pubkey} failed signature '
+              'Event ${e.id} from pubkey ${e.pubkey} failed signature '
               'verification — rejecting to prevent spoofing.',
           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 166 - 169, The warning log
in deep_link_service.dart is escaping string interpolation so it prints
"\${e.id}" and "\${e.pubkey}" literally; update the logger.w call (the statement
using logger.w) to remove the backslashes so it uses Dart interpolation with
${e.id} and ${e.pubkey} to log the actual event id and pubkey values. Ensure the
message string still spans the two concatenated parts and references e.id and
e.pubkey directly in the interpolations.
🧹 Nitpick comments (1)
lib/services/deep_link_service.dart (1)

159-161: Comment contradicts the actual code behavior.

The comment states events failing isVerified() are "not treated as hard rejections," but the code at lines 165-171 returns false, which is a rejection. Per the commit message, this rejection is the intended security fix. Update the comment to reflect the actual behavior.

📝 Suggested comment fix
-      // accepted. isVerified() failures are logged but not treated as hard
-      // rejections due to a known dart_nostr limitation (consistent with
-      // how mostro_nodes_notifier.dart handles kind-0 events).
+      // accepted. Events failing isVerified() are now rejected to prevent
+      // spoofing attacks where an attacker publishes a fake event with the
+      // target Mostro pubkey.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/services/deep_link_service.dart` around lines 159 - 161, Update the
misleading comment in lib/services/deep_link_service.dart near the isVerified()
check to state that verification failures are treated as hard rejections (the
code returns false), rather than being merely logged; reference the isVerified()
call and the surrounding rejection-return behavior so the comment accurately
documents the security fix implemented by that return false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@lib/services/deep_link_service.dart`:
- Around line 166-169: The warning log in deep_link_service.dart is escaping
string interpolation so it prints "\${e.id}" and "\${e.pubkey}" literally;
update the logger.w call (the statement using logger.w) to remove the
backslashes so it uses Dart interpolation with ${e.id} and ${e.pubkey} to log
the actual event id and pubkey values. Ensure the message string still spans the
two concatenated parts and references e.id and e.pubkey directly in the
interpolations.

---

Nitpick comments:
In `@lib/services/deep_link_service.dart`:
- Around line 159-161: Update the misleading comment in
lib/services/deep_link_service.dart near the isVerified() check to state that
verification failures are treated as hard rejections (the code returns false),
rather than being merely logged; reference the isVerified() call and the
surrounding rejection-return behavior so the comment accurately documents the
security fix implemented by that return false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 31bfebd6-c9c7-4b87-9d29-bab2380ecec7

📥 Commits

Reviewing files that changed from the base of the PR and between d259c32 and 831930b.

📒 Files selected for processing (1)
  • lib/services/deep_link_service.dart

Copy link
Copy Markdown
Member

@Catrya Catrya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tACK

Catrya

This comment was marked as duplicate.

@grunch grunch merged commit 2518c37 into main Apr 1, 2026
2 checks passed
@grunch grunch deleted the feat/deep-link-mostro-switch branch April 1, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: handle deep links from different Mostro instances

2 participants