mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
feat(relay): advertise the relay via /capabilities + NIP-05 relay hints
So clients can discover + connect to the relay (and self-adapt when it's off):
- /capabilities: features.nostr_relay + a messaging{ relay_url, write_policy,
inbound_pow_bits, supported_nips } object, with the same config-presence
downgrade as chains/nostr_identity (advertised only when enabled AND a URL is
configured — never advertise a relay clients can't reach). OpenAPI regenerated.
- nip05.rs: /.well-known/nostr.json relay hints are now config-driven — this
instance's own relay (the user's DM inbox) first when enabled, then the public
interop relays (damus/nos.lol) so cross-wallet delivery still works. Replaces
the hardcoded-only default.
This commit is contained in:
@@ -1976,6 +1976,17 @@
|
||||
"features": {
|
||||
"$ref": "#/components/schemas/FeatureCapabilities"
|
||||
},
|
||||
"messaging": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MessagingCapability",
|
||||
"description": "First-party Nostr relay details; present only when `features.nostr_relay`."
|
||||
}
|
||||
]
|
||||
},
|
||||
"registration": {
|
||||
"$ref": "#/components/schemas/RegistrationCapability",
|
||||
"description": "Registration gates for a new wallet on this instance."
|
||||
@@ -2253,6 +2264,7 @@
|
||||
"grin_relay",
|
||||
"prices",
|
||||
"nostr_identity",
|
||||
"nostr_relay",
|
||||
"tips"
|
||||
],
|
||||
"properties": {
|
||||
@@ -2264,6 +2276,10 @@
|
||||
"type": "boolean",
|
||||
"description": "Nostr-native identity (NIP-98 login/link, NIP-05 directory)."
|
||||
},
|
||||
"nostr_relay": {
|
||||
"type": "boolean",
|
||||
"description": "First-party Nostr relay (encrypted DM inbox). See `messaging` for details."
|
||||
},
|
||||
"prices": {
|
||||
"type": "boolean",
|
||||
"description": "Fiat price feed."
|
||||
@@ -2741,6 +2757,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"MessagingCapability": {
|
||||
"type": "object",
|
||||
"description": "First-party Nostr relay details (present only when `features.nostr_relay`).\nThe wallet connects here as its DM inbox (alongside the public interop\nrelays) and adapts its UI to the policy.",
|
||||
"required": [
|
||||
"relay_url",
|
||||
"write_policy",
|
||||
"inbound_pow_bits",
|
||||
"supported_nips"
|
||||
],
|
||||
"properties": {
|
||||
"inbound_pow_bits": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "NIP-13 PoW bits required on cross-ecosystem inbound (0 = off).",
|
||||
"minimum": 0
|
||||
},
|
||||
"relay_url": {
|
||||
"type": "string",
|
||||
"description": "The ws(s):// relay URL clients connect to."
|
||||
},
|
||||
"supported_nips": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"minimum": 0
|
||||
},
|
||||
"description": "NIPs the relay speaks (e.g. `[1, 17, 44, 59]`)."
|
||||
},
|
||||
"write_policy": {
|
||||
"type": "string",
|
||||
"description": "Write policy: `inbox-outbox` | `author-allowlist` | `open`."
|
||||
}
|
||||
}
|
||||
},
|
||||
"MyUsernameResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -45,10 +45,27 @@ pub struct FeatureCapabilities {
|
||||
pub prices: bool,
|
||||
/// Nostr-native identity (NIP-98 login/link, NIP-05 directory).
|
||||
pub nostr_identity: bool,
|
||||
/// First-party Nostr relay (encrypted DM inbox). See `messaging` for details.
|
||||
pub nostr_relay: bool,
|
||||
/// Tipping (parked).
|
||||
pub tips: bool,
|
||||
}
|
||||
|
||||
/// First-party Nostr relay details (present only when `features.nostr_relay`).
|
||||
/// The wallet connects here as its DM inbox (alongside the public interop
|
||||
/// relays) and adapts its UI to the policy.
|
||||
#[derive(Debug, Serialize, utoipa::ToSchema)]
|
||||
pub struct MessagingCapability {
|
||||
/// The ws(s):// relay URL clients connect to.
|
||||
pub relay_url: String,
|
||||
/// Write policy: `inbox-outbox` | `author-allowlist` | `open`.
|
||||
pub write_policy: String,
|
||||
/// NIP-13 PoW bits required on cross-ecosystem inbound (0 = off).
|
||||
pub inbound_pow_bits: u8,
|
||||
/// NIPs the relay speaks (e.g. `[1, 17, 44, 59]`).
|
||||
pub supported_nips: Vec<u16>,
|
||||
}
|
||||
|
||||
/// This instance's wallet-restore (import) policy. The wallet uses it to adapt
|
||||
/// its import UX — hide/grey the restore-height field under `create-only`, warn
|
||||
/// when a chosen date exceeds the bound. `max_depth_days` is present only for
|
||||
@@ -97,6 +114,9 @@ pub struct CapabilitiesResponse {
|
||||
pub restore: RestoreCapability,
|
||||
/// Registration gates for a new wallet on this instance.
|
||||
pub registration: RegistrationCapability,
|
||||
/// First-party Nostr relay details; present only when `features.nostr_relay`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub messaging: Option<MessagingCapability>,
|
||||
}
|
||||
|
||||
/// Whether an enabled chain can actually be served (its infra secret/URL is
|
||||
@@ -153,6 +173,9 @@ pub fn effective_capabilities(config: &Config) -> CapabilitiesResponse {
|
||||
// Nostr identity needs the canonical PUBLIC_API_URL.
|
||||
nostr_identity: config.features.nostr_identity
|
||||
&& config.identity.public_api_url.is_some(),
|
||||
// Relay advertised only when enabled AND a URL is configured (config
|
||||
// presence downgrade — never advertise a relay clients can't reach).
|
||||
nostr_relay: relay_advertised(config),
|
||||
tips: config.features.tips,
|
||||
},
|
||||
restore: RestoreCapability {
|
||||
@@ -180,9 +203,25 @@ pub fn effective_capabilities(config: &Config) -> CapabilitiesResponse {
|
||||
.require_payment
|
||||
.then(|| config.registration.payment.currency.clone()),
|
||||
},
|
||||
messaging: relay_advertised(config).then(|| {
|
||||
let r = &config.messaging.relay;
|
||||
MessagingCapability {
|
||||
relay_url: r.advertised_url.clone(),
|
||||
write_policy: r.write_policy.clone(),
|
||||
inbound_pow_bits: r.inbound_pow_bits,
|
||||
supported_nips: crate::infra::relay::SUPPORTED_NIPS.to_vec(),
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the relay is enabled AND reachable (a URL is configured). Mirrors the
|
||||
/// chain/nostr-identity secret-presence downgrade: never advertise a relay the
|
||||
/// client can't connect to.
|
||||
fn relay_advertised(config: &Config) -> bool {
|
||||
config.messaging.relay.enabled && !config.messaging.relay.advertised_url.trim().is_empty()
|
||||
}
|
||||
|
||||
/// Describe this instance's enabled chains and features.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
|
||||
+19
-4
@@ -108,10 +108,7 @@ pub async fn well_known_nostr(
|
||||
let name = raw.to_lowercase();
|
||||
if let Some(user) = state.db.get_user_by_username(&name).await? {
|
||||
if let Some(pubkey) = user.nostr_pubkey {
|
||||
relays.insert(
|
||||
pubkey.clone(),
|
||||
DEFAULT_RELAYS.iter().map(|r| r.to_string()).collect(),
|
||||
);
|
||||
relays.insert(pubkey.clone(), relay_hints(&state.config));
|
||||
names.insert(name, pubkey);
|
||||
}
|
||||
}
|
||||
@@ -120,6 +117,24 @@ pub async fn well_known_nostr(
|
||||
Ok(Json(WellKnownResponse { names, relays }))
|
||||
}
|
||||
|
||||
/// Relay hints advertised for a resolved pubkey: this instance's own relay (the
|
||||
/// user's DM inbox) FIRST when enabled, then the public interop relays so
|
||||
/// cross-wallet (e.g. Goblin) delivery still works before any per-user list.
|
||||
/// Per-user lists (NIP-65 kind 10002 / NIP-17 inbox relays) remain future work.
|
||||
fn relay_hints(config: &crate::config::Config) -> Vec<String> {
|
||||
let mut hints = Vec::new();
|
||||
let r = &config.messaging.relay;
|
||||
if r.enabled && !r.advertised_url.trim().is_empty() {
|
||||
hints.push(r.advertised_url.clone());
|
||||
}
|
||||
for d in DEFAULT_RELAYS {
|
||||
if !hints.iter().any(|h| h == d) {
|
||||
hints.push((*d).to_string());
|
||||
}
|
||||
}
|
||||
hints
|
||||
}
|
||||
|
||||
// ── router ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// NIP-05 route, mounted at the SERVER ROOT (NOT under `/api/v1`). NIP-05
|
||||
|
||||
@@ -71,6 +71,7 @@ use utoipa::OpenApi;
|
||||
crate::api::capabilities::FeatureCapabilities,
|
||||
crate::api::capabilities::RestoreCapability,
|
||||
crate::api::capabilities::RegistrationCapability,
|
||||
crate::api::capabilities::MessagingCapability,
|
||||
crate::api::prices::PricesResponse,
|
||||
crate::api::auth::AuthResponse,
|
||||
crate::api::auth::UserInfo,
|
||||
|
||||
Reference in New Issue
Block a user