feat: add Nostr-based exchange rates spec for v2#36
feat: add Nostr-based exchange rates spec for v2#36mostronatorcoder[bot] wants to merge 5 commits intomainfrom
Conversation
New spec: NOSTR_EXCHANGE_RATES.md **Overview:** Censorship-resistant exchange rate fetching via NIP-33 addressable events, with Yadio HTTP API fallback. **Key Features:** - Primary source: Nostr (kind 30078, d:rates) - Fallback: Yadio HTTP API - Security: MUST verify event signed by connected Mostro instance pubkey - Publisher: Mostro daemon (fetches from Yadio, publishes to Nostr) **Event Structure:** - Kind 30078 (NIP-33 addressable) - Content: JSON with all currency rates - Published every 5 minutes by Mostro daemon - Signature verification prevents price manipulation attacks **Implementation Plan:** - Phase 1: Mostro daemon publishing (src/exchange_rates.rs) - Phase 2: Mobile client integration (Rust core + Flutter UI) - Phase 3: Gradual rollout with metrics monitoring **Benefits:** - Censorship-resistant (works in Venezuela, Cuba, etc.) - Zero scaling cost for Yadio - Better reliability (multiple relays) - Aligns with Nostr/Bitcoin philosophy **Cross-references:** - v1-reference/EXCHANGE_SERVICE.md (HTTP-based implementation) - Issue #684 (original proposal) - NIP-33 (Parameterized Replaceable Events) Also created .specify/README.md for v2 spec directory overview.
- Removed Phase 1 (daemon) section - Removed mostro.toml config examples - Removed src/exchange_rates.rs references - Focused spec exclusively on client-side implementation - Updated cross-references to point to Issue #684 for daemon implementation - Kept only client architecture, security, testing, and UI concerns
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughTwo specification documents are added to establish the Mostro v2 framework. The Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~5 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
.specify/README.md (2)
13-15: Clarify that referenced specs are planned for future implementation.Lines 13-15 reference
ARCHITECTURE.md,DESIGN_SYSTEM.md, andPROTOCOL.md, which are not included in this PR. To avoid confusion, consider adding a note that these are planned specifications.📝 Suggested clarification
### Core Architecture +_(The following specs are planned for future implementation)_ + - [ARCHITECTURE.md](./ARCHITECTURE.md) — System architecture and component design - [DESIGN_SYSTEM.md](./DESIGN_SYSTEM.md) — UI/UX design system and guidelines - [PROTOCOL.md](./PROTOCOL.md) — Mostro protocol specification🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.specify/README.md around lines 13 - 15, Add a short clarifying note in .specify/README.md near the list that ARCHITECTURE.md, DESIGN_SYSTEM.md, and PROTOCOL.md are planned/specs-to-be-added rather than included in this PR; update the list area to append or precede a single-line remark like "These specification documents are planned for future implementation and are not included in this PR" so readers understand the referenced files are pending.
43-49: Clarify SpecKit is planned/future tooling.The section references "SpecKit — the automated app generation tool" as if it exists, but there's no evidence of this tool in the repository. Consider clarifying this is planned infrastructure to avoid confusion.
📝 Suggested clarification
### For SpecKit Generation -These specs are written to be consumed by **SpecKit** — the automated app generation tool for Mostro v2. When a spec is ready for implementation: +These specs are written to be consumed by **SpecKit** — a planned automated app generation tool for Mostro v2. Once SpecKit is available, the workflow will be: 1. Tag the spec with implementation status (e.g., `status: ready`) 2. Run SpecKit generator: `speckit generate --spec FEATURE_NAME.md`🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.specify/README.md around lines 43 - 49, The README currently refers to "SpecKit — the automated app generation tool" as if it exists; update the wording to clarify SpecKit is planned or future tooling (e.g., change "These specs are written to be consumed by SpecKit — the automated app generation tool" to "These specs are intended for a planned tool ('SpecKit') — an automated app generator to be added in future"), and add a short note under the bulleted steps that SpecKit is not yet available in this repository (and that developers should tag specs with status: ready and run speckit generate once the tool is published).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.specify/NOSTR_EXCHANGE_RATES.md:
- Around line 259-269: Update the spec to use a consistent event update
frequency across the document: pick either a specific interval (e.g., "every 5
minutes") or explicitly state the allowed range (e.g., "every 5–10 minutes
depending on market volatility"), and apply that choice to all mentions
including the "v1 (HTTP)"/ "v2 (Nostr)" bandwidth comparison block, the "5-10
minute TTL" reference, and the PR objectives section so "1 event pushed to all
subscribed clients every 5-10 minutes" and the stated PR objective ("updates
every 5 minutes") are aligned.
- Around line 166-170: The spec adds a persistent SQLite cache for
"exchange_rates" but omits the schema, invalidation policy, and how to map
persisted data into AsyncValue states; add a concrete table definition (columns:
id, currency_code, btc_rate, fetched_at, source, mostro_pubkey) and indexes for
currency_code and fetched_at, implement startup purge of rows older than 24
hours, treat entries older than 15 minutes as "stale" when used as a fallback,
and document that when loading fallback data you should return
AsyncValue.data(rates, staleness: duration) (not AsyncValue.error) while surface
an explicit staleness warning; reference the persistent cache handling around
the "exchange_rates" cache key and the AsyncValue state mapping logic so
reviewers can locate where to persist/read and apply the purge/staleness checks.
- Around line 160-164: The event handling flow that parses event.content needs
explicit error handling: when verifying pubkey (event.pubkey) and signature
(event.verify()) succeeds but JSON.parse(event.content) throws, catch the parse
error, log it with context (including event id/pubkey), do not update the
in-memory/persistent cache or notify Riverpod UI state, and instead trigger the
HTTP API fallback path to fetch/update rates; ensure the parse-failure branch is
clearly documented in the update flow and returns/flags the event as handled to
avoid crashes or duplicate processing.
- Around line 237-238: The UI currently logs an error message ("Could not fetch
exchange rates. Using cached data.") while the fallback path
self.load_from_cache() returns Ok(rates), causing a mismatch between
persistent-cache semantics and AsyncValue mapping; decide which behavior you
want and implement it consistently: either (Option A) treat cached fallback as a
successful AsyncValue.data(rates) plus a staleness flag (add a staleness
boolean/metadata on the rates and render a warning in ErrorView or replace
ErrorView with a StaleDataView), or (Option B) surface the network failure as
AsyncValue.error(err, cachedData) so the error handler (the error: (err, stack)
=> ErrorView(...) branch) can render both the error and the attached cached
rates; update the code paths around self.load_from_cache(), the AsyncValue
construction (AsyncValue.data vs AsyncValue.error), and the ErrorView usage to
reflect the chosen option so the UI message matches the actual returned
AsyncValue.
- Around line 353-374: The migration text is ambiguous about backwards
compatibility—explicitly state that the abstract ExchangeService interface
(methods getExchangeRate() and getCurrencyCodes()) must remain unchanged, add a
new NostrExchangeRateService implementation that internally attempts Nostr first
then falls back to YadioExchangeRateService and persistent cache, and confirm
Riverpod provider names/signatures (e.g. ExchangeRateNotifier,
StateNotifierProvider.family exchangeRateProvider and a
Provider<ExchangeService> exchangeServiceProvider) remain the same so the new
service is a drop‑in replacement for existing UI code.
- Line 122: The fenced code block for the architecture diagram is missing a
language identifier; update the opening fence to include one (e.g., change the
opening "```" to "```text") so the diagram lines like "ExchangeRateService (Rust
core)" and "NostrExchangeRateProvider (primary)" are rendered and recognized
correctly by tooling.
In @.specify/README.md:
- Around line 92-108: The fenced code block in .specify/README.md is missing a
language identifier which can break syntax highlighting; update the opening
fence for the directory-structure block to include a language (for example
"text") so the block becomes ```text and leave the rest unchanged — locate the
directory tree block in README.md (the triple-backtick section showing
".specify/ ├── README.md ...") and add the language identifier to the opening
fence.
---
Nitpick comments:
In @.specify/README.md:
- Around line 13-15: Add a short clarifying note in .specify/README.md near the
list that ARCHITECTURE.md, DESIGN_SYSTEM.md, and PROTOCOL.md are
planned/specs-to-be-added rather than included in this PR; update the list area
to append or precede a single-line remark like "These specification documents
are planned for future implementation and are not included in this PR" so
readers understand the referenced files are pending.
- Around line 43-49: The README currently refers to "SpecKit — the automated app
generation tool" as if it exists; update the wording to clarify SpecKit is
planned or future tooling (e.g., change "These specs are written to be consumed
by SpecKit — the automated app generation tool" to "These specs are intended for
a planned tool ('SpecKit') — an automated app generator to be added in future"),
and add a short note under the bulleted steps that SpecKit is not yet available
in this repository (and that developers should tag specs with status: ready and
run speckit generate once the tool is published).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d0b18955-7635-42eb-9f26-3a3b45ff548d
📒 Files selected for processing (2)
.specify/NOSTR_EXCHANGE_RATES.md.specify/README.md
|
|
||
| ### Architecture | ||
|
|
||
| ``` |
There was a problem hiding this comment.
Add language identifier to fenced code block.
The architecture diagram should have a language identifier for proper rendering and tooling support.
📝 Proposed fix
-```
+```text
ExchangeRateService (Rust core)
├─ NostrExchangeRateProvider (primary)🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 122-122: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specify/NOSTR_EXCHANGE_RATES.md at line 122, The fenced code block for the
architecture diagram is missing a language identifier; update the opening fence
to include one (e.g., change the opening "```" to "```text") so the diagram
lines like "ExchangeRateService (Rust core)" and "NostrExchangeRateProvider
(primary)" are rendered and recognized correctly by tooling.
| 2. Verify `event.pubkey == mostroPubkey` | ||
| 3. Verify signature (`event.verify()`) | ||
| 4. Parse `event.content` as JSON | ||
| 5. Update local cache (in-memory + persistent storage) | ||
| 6. Notify UI (Riverpod state update) |
There was a problem hiding this comment.
Add error handling for malformed JSON content.
The update handling flow (lines 160-164) doesn't specify what happens if event.content contains invalid JSON. This should explicitly fall back to HTTP API to prevent app crashes.
🛡️ Recommended addition to the spec
Add after line 164:
5. **If JSON parsing fails:** Log error, fall back to HTTP API🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specify/NOSTR_EXCHANGE_RATES.md around lines 160 - 164, The event handling
flow that parses event.content needs explicit error handling: when verifying
pubkey (event.pubkey) and signature (event.verify()) succeeds but
JSON.parse(event.content) throws, catch the parse error, log it with context
(including event id/pubkey), do not update the in-memory/persistent cache or
notify Riverpod UI state, and instead trigger the HTTP API fallback path to
fetch/update rates; ensure the parse-failure branch is clearly documented in the
update flow and returns/flags the event as handled to avoid crashes or duplicate
processing.
| **Cache strategy:** | ||
|
|
||
| - **In-memory cache:** 5-10 minute TTL | ||
| - **Persistent cache (SQLite):** Store last successful fetch as fallback if both Nostr and HTTP fail | ||
| - **Cache key:** `"exchange_rates"` |
There was a problem hiding this comment.
Document SQLite schema and cache invalidation strategy.
The spec introduces persistent cache (SQLite) as a significant architectural change from v1's memory-only approach, but critical details are missing:
- SQLite schema — No table definition provided for the persistent cache
- Cache invalidation — No strategy for expiring stale persistent data (does it ever age out?)
- State integration — How does stale persistent cache data integrate with AsyncValue state management? (error state vs. data state with staleness warning)
As per context snippet 3 from v1-reference/EXCHANGE_SERVICE.md, v1 explicitly documented "No caching implemented in v1" and recommended "Store in memory (not persistent across app restarts)". This persistent cache is a major departure that needs complete specification.
📋 Recommended additions to the spec
Add a subsection after line 170:
#### SQLite Schema
CREATE TABLE exchange_rates_cache (
id INTEGER PRIMARY KEY,
currency_code TEXT NOT NULL,
btc_rate REAL NOT NULL,
fetched_at INTEGER NOT NULL, -- Unix timestamp
source TEXT NOT NULL, -- 'nostr' or 'http'
mostro_pubkey TEXT NOT NULL
);
CREATE INDEX idx_currency ON exchange_rates_cache(currency_code);
CREATE INDEX idx_fetched_at ON exchange_rates_cache(fetched_at);Cache Invalidation Policy:
- Persistent cache entries older than 24 hours are automatically purged on app startup
- When loading from persistent cache as last resort, display staleness warning if data is older than 15 minutes
- UI state:
AsyncValue.data(rates, staleness: Duration)to preserve error information while showing cached data
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @.specify/NOSTR_EXCHANGE_RATES.md around lines 166 - 170, The spec adds a
persistent SQLite cache for "exchange_rates" but omits the schema, invalidation
policy, and how to map persisted data into AsyncValue states; add a concrete
table definition (columns: id, currency_code, btc_rate, fetched_at, source,
mostro_pubkey) and indexes for currency_code and fetched_at, implement startup
purge of rows older than 24 hours, treat entries older than 15 minutes as
"stale" when used as a fallback, and document that when loading fallback data
you should return AsyncValue.data(rates, staleness: duration) (not
AsyncValue.error) while surface an explicit staleness warning; reference the
persistent cache handling around the "exchange_rates" cache key and the
AsyncValue state mapping logic so reviewers can locate where to persist/read and
apply the purge/staleness checks.
</details>
<!-- fingerprinting:phantom:triton:puma:f147a7a5-c61a-4047-8fb5-efac2b6cbce2 -->
<!-- This is an auto-generated comment by CodeRabbit -->
| error: (err, stack) => ErrorView( | ||
| message: "Could not fetch exchange rates. Using cached data.", |
There was a problem hiding this comment.
Clarify persistent cache → UI state mapping.
The error state message "Could not fetch exchange rates. Using cached data." implies cached data is shown in an error state. However, the fallback logic at line 193 shows self.load_from_cache() returns Ok(rates), which would produce a data state, not an error state.
Recommendation: Specify whether persistent cache fallback should:
- Option A: Return
Okwith a staleness indicator →AsyncValue.data(rates)+ staleness warning UI - Option B: Return
Errwith cached data attached →AsyncValue.error(err, cachedData)→ requires custom AsyncValue handling
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specify/NOSTR_EXCHANGE_RATES.md around lines 237 - 238, The UI currently
logs an error message ("Could not fetch exchange rates. Using cached data.")
while the fallback path self.load_from_cache() returns Ok(rates), causing a
mismatch between persistent-cache semantics and AsyncValue mapping; decide which
behavior you want and implement it consistently: either (Option A) treat cached
fallback as a successful AsyncValue.data(rates) plus a staleness flag (add a
staleness boolean/metadata on the rates and render a warning in ErrorView or
replace ErrorView with a StaleDataView), or (Option B) surface the network
failure as AsyncValue.error(err, cachedData) so the error handler (the error:
(err, stack) => ErrorView(...) branch) can render both the error and the
attached cached rates; update the code paths around self.load_from_cache(), the
AsyncValue construction (AsyncValue.data vs AsyncValue.error), and the ErrorView
usage to reflect the chosen option so the UI message matches the actual returned
AsyncValue.
|
|
||
| **v1 (HTTP):** | ||
| - 1 request every 5-10 minutes per active user | ||
| - ~5-10 KB per request (all currencies) | ||
|
|
||
| **v2 (Nostr):** | ||
| - 1 event pushed to all subscribed clients every 5-10 minutes | ||
| - ~5-10 KB per event (same payload) | ||
| - No per-user request overhead | ||
|
|
||
| **Conclusion:** Bandwidth roughly equivalent, but Nostr scales better (relays handle distribution). |
There was a problem hiding this comment.
Standardize event update frequency.
The spec mentions "1 event pushed to all subscribed clients every 5-10 minutes" (line 265), and line 168 mentions "5-10 minute TTL", but the PR objectives state "updates every 5 minutes". This should be consistent throughout the spec.
Recommendation: Use a specific value (e.g., "every 5 minutes") or document the variability (e.g., "every 5-10 minutes depending on market volatility").
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specify/NOSTR_EXCHANGE_RATES.md around lines 259 - 269, Update the spec to
use a consistent event update frequency across the document: pick either a
specific interval (e.g., "every 5 minutes") or explicitly state the allowed
range (e.g., "every 5–10 minutes depending on market volatility"), and apply
that choice to all mentions including the "v1 (HTTP)"/ "v2 (Nostr)" bandwidth
comparison block, the "5-10 minute TTL" reference, and the PR objectives section
so "1 event pushed to all subscribed clients every 5-10 minutes" and the stated
PR objective ("updates every 5 minutes") are aligned.
| ## Migration from v1 → v2 | ||
|
|
||
| ### Step 1: Add Nostr as Primary Source | ||
|
|
||
| - Implement `NostrExchangeRateProvider` in Rust core | ||
| - Keep `YadioExchangeRateProvider` as-is (for fallback) | ||
| - Update `ExchangeService` to try Nostr first, then HTTP | ||
|
|
||
| ### Step 2: Test in Regtest/Testnet | ||
|
|
||
| - Connect to test Mostro instance with rate publishing enabled | ||
| - Verify mobile app receives and validates events | ||
| - Test fallback when Nostr unavailable | ||
|
|
||
| ### Step 3: Gradual Rollout | ||
|
|
||
| - **Beta users:** Nostr-first (with fallback) | ||
| - Monitor metrics: | ||
| - % of rate fetches from Nostr vs HTTP | ||
| - Latency (Nostr vs HTTP) | ||
| - Failure rate | ||
| - **Stable release:** Once Nostr proves reliable (>95% success rate) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Clarify backward compatibility and provider implementation pattern.
The migration step "Update ExchangeService to try Nostr first, then HTTP" (line 359) is vague about the implementation approach. Based on v1-reference/EXCHANGE_SERVICE.md context:
- v1 has an abstract
ExchangeServicebase class withgetExchangeRate()andgetCurrencyCodes()methods - Current implementation is
YadioExchangeService - Riverpod providers expose
StateNotifierProvider.family<ExchangeRateNotifier, AsyncValue<double>, String>
The spec should clarify:
- Does the
ExchangeServiceabstract interface stay unchanged (ensuring existing UI code works without modification)? - Is a new
NostrExchangeRateServiceimplementation created that wraps both Nostr and HTTP providers internally? - Do the Riverpod provider names and signatures remain the same?
This is critical for ensuring backward compatibility. As per context snippet 1, the v1 spec emphasized maintaining the public API surface while changing internal implementation.
📋 Recommended addition to migration section
Add after line 359:
**Backward Compatibility:**
- Keep `ExchangeService` abstract interface unchanged (same method signatures)
- Create new `NostrExchangeRateService` implementation that internally:
1. Tries Nostr subscription first
2. Falls back to HTTP API (reuses existing `YadioExchangeRateService` logic)
3. Uses persistent cache as last resort
- Riverpod provider setup remains the same:
```dart
final exchangeServiceProvider = Provider<ExchangeService>((ref) {
return NostrExchangeRateService(); // Drop-in replacement
});- Existing UI code requires no changes (same
ref.watch(exchangeRateProvider('USD'))pattern)
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @.specify/NOSTR_EXCHANGE_RATES.md around lines 353 - 374, The migration text
is ambiguous about backwards compatibility—explicitly state that the abstract
ExchangeService interface (methods getExchangeRate() and getCurrencyCodes())
must remain unchanged, add a new NostrExchangeRateService implementation that
internally attempts Nostr first then falls back to YadioExchangeRateService and
persistent cache, and confirm Riverpod provider names/signatures (e.g.
ExchangeRateNotifier, StateNotifierProvider.family exchangeRateProvider and a
Provider exchangeServiceProvider) remain the same so the new
service is a drop‑in replacement for existing UI code.
</details>
<!-- fingerprinting:phantom:triton:puma:f147a7a5-c61a-4047-8fb5-efac2b6cbce2 -->
<!-- This is an auto-generated comment by CodeRabbit -->
| ``` | ||
| .specify/ | ||
| ├── README.md # This file | ||
| ├── ARCHITECTURE.md # Overall system design | ||
| ├── DESIGN_SYSTEM.md # UI/UX guidelines | ||
| ├── PROTOCOL.md # Mostro protocol spec | ||
| ├── NOSTR_EXCHANGE_RATES.md # v2 feature: Nostr-based rates | ||
| ├── v1-reference/ # Flutter v1 implementation docs | ||
| │ ├── README.md | ||
| │ ├── EXCHANGE_SERVICE.md | ||
| │ ├── MOSTRO_SERVICE.md | ||
| │ └── ... | ||
| ├── memory/ # Spec evolution notes | ||
| │ └── constitution.md | ||
| ├── scripts/ # Tooling for spec generation | ||
| └── templates/ # Spec templates | ||
| ``` |
There was a problem hiding this comment.
Add language identifier to fenced code block.
The directory structure code block should have a language identifier for proper rendering.
📝 Proposed fix
-```
+```text
.specify/
├── README.md # This file📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ``` | |
| .specify/ | |
| ├── README.md # This file | |
| ├── ARCHITECTURE.md # Overall system design | |
| ├── DESIGN_SYSTEM.md # UI/UX guidelines | |
| ├── PROTOCOL.md # Mostro protocol spec | |
| ├── NOSTR_EXCHANGE_RATES.md # v2 feature: Nostr-based rates | |
| ├── v1-reference/ # Flutter v1 implementation docs | |
| │ ├── README.md | |
| │ ├── EXCHANGE_SERVICE.md | |
| │ ├── MOSTRO_SERVICE.md | |
| │ └── ... | |
| ├── memory/ # Spec evolution notes | |
| │ └── constitution.md | |
| ├── scripts/ # Tooling for spec generation | |
| └── templates/ # Spec templates | |
| ``` |
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 92-92: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.specify/README.md around lines 92 - 108, The fenced code block in
.specify/README.md is missing a language identifier which can break syntax
highlighting; update the opening fence for the directory-structure block to
include a language (for example "text") so the block becomes ```text and leave
the rest unchanged — locate the directory tree block in README.md (the
triple-backtick section showing ".specify/ ├── README.md ...") and add the
language identifier to the opening fence.
Updated subscription filter and verification logic to use 'mostro-rates' instead of 'rates' to match daemon implementation.
Updated spec to match daemon fix: rates are published as
{"USD": 50000.0} (price of 1 BTC), not {"USD": {"BTC": 0.000024}}.
Matches daemon implementation and clarifies semantics: published_at = daemon publish timestamp (not source timestamp).
Comprehensive specification for censorship-resistant exchange rate fetching via Nostr (NIP-33).
New Files
NOSTR_EXCHANGE_RATES.md
Complete v2 feature spec for Nostr-based exchange rates.
Protocol:
30078, d tag"rates"){"USD": {"BTC": 0.000024}, ...})Security (Critical):
event.pubkeymatches connected Mostro instanceArchitecture:
Implementation Plan:
Benefits:
Relays:
wss://relay.mostro.network(primary)wss://nos.lol(fast public fallback)wss://relay.nostr.band(archival)Testing Strategy:
Cross-References:
.specify/README.md
New directory-level README for v2 specs.
Contents:
Related
Next Steps
Summary by CodeRabbit