Files
GoblinPay/crates/gp-nostr/src/relays.rs
T
2ro c32ddfa9ff M8: bundled relay — RelayMode::Bundled runs a co-located nostr-rs-relay
Make bundled mode actually self-contained: resolve() now leads the relay set with GP_BUNDLED_RELAY_URL (default ws://127.0.0.1:7777), which the checkout nprofile advertises, so a merchant needs no third-party relay. External mode uses only GP_RELAYS. Ship the relay as a vendored, unmodified nostr-rs-relay config (deploy/relay/nostr-rs-relay.toml); the compose service arrives with the deploy pipeline. Fix the stale "bundled is a later milestone" comment and reconcile the GP_NYM=off wording to a supported server-side-clearnet posture.
2026-07-03 03:22:29 -04:00

93 lines
3.5 KiB
Rust

//! Relay set resolution.
//!
//! GoblinPay runs in one of two relay modes (`GP_RELAY_MODE`, see
//! [`gp_core::config::RelayMode`]):
//!
//! - `bundled` (default): GoblinPay talks to its own co-located relay, the
//! nostr-rs-relay shipped as the `relay` service in
//! `deploy/docker-compose.yml`. Its URL is `GP_BUNDLED_RELAY_URL` (default
//! `ws://127.0.0.1:7777`). Because the resolved set is exactly what the
//! checkout `nprofile` advertises to payers, a merchant needs no third-party
//! relay: the payer's Goblin Wallet is told to deliver the gift-wrapped
//! slatepack to the merchant's own relay. Extra relays listed in `GP_RELAYS`
//! are appended for redundancy (and advertised alongside the bundled one).
//! - `external`: only the relays listed in `GP_RELAYS` are used (no bundled
//! relay); config validation requires at least one.
//!
//! The bundled relay is a vendored, unmodified nostr-rs-relay (config only, no
//! fork) rather than a relay written from scratch: it is a small, SQLite-backed
//! Rust relay that fits a single-merchant till, and reusing it keeps the money
//! path off any third-party infrastructure.
use gp_core::config::RelayMode;
/// Maximum relays published in the kind 10050 DM relay list (NIP-17
/// guidance) and read from a payer's list.
pub const MAX_DM_RELAYS: usize = 3;
/// The relay set to listen on, publish to, and advertise in the `nprofile`.
///
/// In `bundled` mode the co-located `bundled_url` comes first (so it heads the
/// advertised kind 10050 / `nprofile` hints), followed by any `configured`
/// redundancy relays, de-duplicated. In `external` mode only the `configured`
/// relays are used.
pub fn resolve(mode: RelayMode, bundled_url: &str, configured: &[String]) -> Vec<String> {
match mode {
RelayMode::Bundled => {
let mut relays = vec![bundled_url.to_string()];
for relay in configured {
if !relays.iter().any(|r| r == relay) {
relays.push(relay.clone());
}
}
relays
}
RelayMode::External => configured.to_vec(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bundled_leads_with_the_bundled_relay() {
// No extras: just the bundled relay, so the nprofile advertises it and
// nothing third-party is involved.
assert_eq!(
resolve(RelayMode::Bundled, "ws://127.0.0.1:7777", &[]),
vec!["ws://127.0.0.1:7777".to_string()]
);
// Extras are appended for redundancy; the bundled relay stays first.
let extras = vec!["wss://relay.damus.io".to_string()];
assert_eq!(
resolve(RelayMode::Bundled, "ws://127.0.0.1:7777", &extras),
vec![
"ws://127.0.0.1:7777".to_string(),
"wss://relay.damus.io".to_string(),
]
);
// A configured relay equal to the bundled one is not added twice.
let dup = vec![
"ws://127.0.0.1:7777".to_string(),
"wss://r.example".to_string(),
];
assert_eq!(
resolve(RelayMode::Bundled, "ws://127.0.0.1:7777", &dup),
vec![
"ws://127.0.0.1:7777".to_string(),
"wss://r.example".to_string(),
]
);
}
#[test]
fn external_uses_only_configured() {
let own = vec!["wss://relay.example".to_string()];
assert_eq!(
resolve(RelayMode::External, "ws://127.0.0.1:7777", &own),
own
);
}
}