diff --git a/bitchat/Models/BitchatMessage.swift b/bitchat/Models/BitchatMessage.swift index 24b16692f..7088d8d46 100644 --- a/bitchat/Models/BitchatMessage.swift +++ b/bitchat/Models/BitchatMessage.swift @@ -21,7 +21,7 @@ final class BitchatMessage: Codable { let originalSender: String? let isPrivate: Bool let recipientNickname: String? - let senderPeerID: PeerID? + let senderPeerID: PeerID let mentions: [String]? // Array of mentioned nicknames var deliveryStatus: DeliveryStatus? // Delivery tracking @@ -51,7 +51,7 @@ final class BitchatMessage: Codable { originalSender: String? = nil, isPrivate: Bool = false, recipientNickname: String? = nil, - senderPeerID: PeerID? = nil, + senderPeerID: PeerID = .empty, mentions: [String]? = nil, deliveryStatus: DeliveryStatus? = nil ) { @@ -67,6 +67,52 @@ final class BitchatMessage: Codable { self.mentions = mentions self.deliveryStatus = deliveryStatus ?? (isPrivate ? .sending : nil) } + + required convenience init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let id = try container.decodeIfPresent(String.self, forKey: .id) + let sender = try container.decode(String.self, forKey: .sender) + let content = try container.decode(String.self, forKey: .content) + let timestamp = try container.decode(Date.self, forKey: .timestamp) + let isRelay = try container.decode(Bool.self, forKey: .isRelay) + let originalSender = try container.decodeIfPresent(String.self, forKey: .originalSender) + let isPrivate = try container.decode(Bool.self, forKey: .isPrivate) + let recipientNickname = try container.decodeIfPresent(String.self, forKey: .recipientNickname) + let senderPeerID = try container.decodeIfPresent(PeerID.self, forKey: .senderPeerID) ?? .empty + let mentions = try container.decodeIfPresent([String].self, forKey: .mentions) + let deliveryStatus = try container.decodeIfPresent(DeliveryStatus.self, forKey: .deliveryStatus) + + self.init( + id: id, + sender: sender, + content: content, + timestamp: timestamp, + isRelay: isRelay, + originalSender: originalSender, + isPrivate: isPrivate, + recipientNickname: recipientNickname, + senderPeerID: senderPeerID, + mentions: mentions, + deliveryStatus: deliveryStatus + ) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(sender, forKey: .sender) + try container.encode(content, forKey: .content) + try container.encode(timestamp, forKey: .timestamp) + try container.encode(isRelay, forKey: .isRelay) + try container.encodeIfPresent(originalSender, forKey: .originalSender) + try container.encode(isPrivate, forKey: .isPrivate) + try container.encodeIfPresent(recipientNickname, forKey: .recipientNickname) + if !senderPeerID.isEmpty { + try container.encode(senderPeerID, forKey: .senderPeerID) + } + try container.encodeIfPresent(mentions, forKey: .mentions) + try container.encodeIfPresent(deliveryStatus, forKey: .deliveryStatus) + } } // MARK: - Equatable Conformance @@ -113,7 +159,7 @@ extension BitchatMessage { if isPrivate { flags |= 0x02 } if originalSender != nil { flags |= 0x04 } if recipientNickname != nil { flags |= 0x08 } - if senderPeerID != nil { flags |= 0x10 } + if !senderPeerID.isEmpty { flags |= 0x10 } if mentions != nil && !mentions!.isEmpty { flags |= 0x20 } data.append(flags) @@ -163,7 +209,7 @@ extension BitchatMessage { data.append(recipData.prefix(255)) } - if let peerData = senderPeerID?.id.data(using: .utf8) { + if !senderPeerID.isEmpty, let peerData = senderPeerID.id.data(using: .utf8) { data.append(UInt8(min(peerData.count, 255))) data.append(peerData.prefix(255)) } @@ -276,11 +322,11 @@ extension BitchatMessage { } } - var senderPeerID: PeerID? + var senderPeerID: PeerID = .empty if hasSenderPeerID && offset < dataCopy.count { let length = Int(dataCopy[offset]); offset += 1 if offset + length <= dataCopy.count { - senderPeerID = PeerID(data: dataCopy[offset.. String { - if let spid = message.senderPeerID { + if message.hasSenderPeerID { + let spid = message.senderPeerID if spid.isGeoChat || spid.isGeoDM { let full = (nostrKeyMapping[spid] ?? spid.bare).lowercased() return "nostr:" + full @@ -1998,8 +1999,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let gh = currentGeohash { if var arr = geoTimelines[gh] { arr.removeAll { msg in - if let spid = msg.senderPeerID, spid.isGeoDM || spid.isGeoChat { - if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex } + if msg.hasSenderPeerID { + let spid = msg.senderPeerID + if spid.isGeoDM || spid.isGeoChat { + if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex } + } } return false } @@ -2009,8 +2013,11 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { switch activeChannel { case .location: messages.removeAll { msg in - if let spid = msg.senderPeerID , spid.isGeoDM || spid.isGeoChat { - if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex } + if msg.hasSenderPeerID { + let spid = msg.senderPeerID + if spid.isGeoDM || spid.isGeoChat { + if let full = nostrKeyMapping[spid]?.lowercased() { return full == hex } + } } return false } @@ -3058,9 +3065,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Store the Nostr pubkey if provided (for messages from unknown senders) if let nostrPubkey = notification.userInfo?["nostrPubkey"] as? String, - let senderPeerID = message.senderPeerID { + message.hasSenderPeerID { // Store mapping for read receipts - nostrKeyMapping[senderPeerID] = nostrPubkey + nostrKeyMapping[message.senderPeerID] = nostrPubkey } // Process the Nostr message through the same flow as Bluetooth messages @@ -3199,7 +3206,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { originalSender: nil, isPrivate: false, recipientNickname: nil, - senderPeerID: nil, + senderPeerID: .empty, mentions: nil ) @@ -3437,7 +3444,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { for message in messagesToAck { // Only send read ACKs for messages from the peer (not our own) // Check both the ephemeral peer ID and stable Noise key as sender - if (message.senderPeerID == peerID || message.senderPeerID == noiseKeyHex) && !message.isRelay { + let matchesNoiseKey = noiseKeyHex.map { message.senderPeerID == $0 } ?? false + if (message.senderPeerID == peerID || matchesNoiseKey) && !message.isRelay { // Skip if we already sent an ACK for this message if !sentReadReceipts.contains(message.id) { // Use stable Noise key hex if available; else fall back to peerID @@ -3709,7 +3717,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { func formatMessageAsText(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString { // Determine if this message was sent by self (mesh, geo, or DM) let isSelf: Bool = { - if let spid = message.senderPeerID { + if message.hasSenderPeerID { + let spid = message.senderPeerID // In geohash channels, compare against our per-geohash nostr short ID if case .location(let ch) = activeChannel, spid.isGeoChat { let myGeo: NostrIdentity? = { @@ -3755,7 +3764,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let fontWeight: Font.Weight = isSelf ? .bold : .medium senderStyle.font = .bitchatSystem(size: 14, weight: fontWeight, design: .monospaced) // Make sender clickable: encode senderPeerID into a custom URL - if let spid = message.senderPeerID, let url = URL(string: "bitchat://user/\(spid.toPercentEncoded())") { + if message.hasSenderPeerID, + let url = URL(string: "bitchat://user/\(message.senderPeerID.toPercentEncoded())") { senderStyle.link = url } @@ -4053,7 +4063,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor func formatMessageHeader(_ message: BitchatMessage, colorScheme: ColorScheme) -> AttributedString { let isSelf: Bool = { - if let spid = message.senderPeerID { + if message.hasSenderPeerID { + let spid = message.senderPeerID if case .location(let ch) = activeChannel, spid.id.hasPrefix("nostr:") { if let myGeo = try? idBridge.deriveIdentity(forGeohash: ch.geohash) { return spid == "nostr:\(myGeo.publicKeyHex.prefix(TransportConfig.nostrShortKeyDisplayLength))" @@ -4081,8 +4092,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { var senderStyle = AttributeContainer() senderStyle.foregroundColor = baseColor senderStyle.font = .bitchatSystem(size: 14, weight: isSelf ? .bold : .medium, design: .monospaced) - if let spid = message.senderPeerID, - let url = URL(string: "bitchat://user/\(spid.id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? spid.id)") { + if message.hasSenderPeerID, + let url = URL(string: "bitchat://user/\(message.senderPeerID.id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? message.senderPeerID.id)") { senderStyle.link = url } @@ -4333,7 +4344,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor private func peerColor(for message: BitchatMessage, isDark: Bool) -> Color { - if let spid = message.senderPeerID { + if message.hasSenderPeerID { + let spid = message.senderPeerID if spid.isGeoChat || spid.isGeoDM { let full = nostrKeyMapping[spid]?.lowercased() ?? spid.bare.lowercased() return getNostrPaletteColor(for: full, isDark: isDark) @@ -5977,16 +5989,20 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { /// Check if a message should be blocked based on sender @MainActor private func isMessageBlocked(_ message: BitchatMessage) -> Bool { - if let peerID = message.senderPeerID ?? getPeerIDForNickname(message.sender) { - // Check mesh/known peers first - if isPeerBlocked(peerID) { return true } - // Check geohash (Nostr) blocks using mapping to full pubkey - if peerID.isGeoChat || peerID.isGeoDM { - if let full = nostrKeyMapping[peerID]?.lowercased() { - if identityManager.isNostrBlocked(pubkeyHexLowercased: full) { return true } - } + var peerID = message.senderPeerID + if peerID.isEmpty, let resolved = getPeerIDForNickname(message.sender) { + peerID = resolved + } + + guard !peerID.isEmpty else { return false } + + // Check mesh/known peers first + if isPeerBlocked(peerID) { return true } + // Check geohash (Nostr) blocks using mapping to full pubkey + if peerID.isGeoChat || peerID.isGeoDM { + if let full = nostrKeyMapping[peerID]?.lowercased() { + if identityManager.isNostrBlocked(pubkeyHexLowercased: full) { return true } } - return false } return false } @@ -6125,12 +6141,17 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor private func handlePrivateMessage(_ message: BitchatMessage) { SecureLogger.debug("📥 handlePrivateMessage called for message from \(message.sender)", category: .session) - let senderPeerID = message.senderPeerID ?? getPeerIDForNickname(message.sender) - - guard let peerID = senderPeerID else { + var resolvedSender = message.senderPeerID + if resolvedSender.isEmpty, let fallback = getPeerIDForNickname(message.sender) { + resolvedSender = fallback + } + + guard !resolvedSender.isEmpty else { SecureLogger.warning("⚠️ Could not get peer ID for sender \(message.sender)", category: .session) - return + return } + + let peerID = resolvedSender // Check if this is a favorite/unfavorite notification if message.content.hasPrefix("[FAVORITED]") || message.content.hasPrefix("[UNFAVORITED]") { @@ -6235,7 +6256,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { readerNickname: nickname ) - let recipientID = message.senderPeerID ?? peerID + let recipientID = message.hasSenderPeerID ? message.senderPeerID : peerID Task { @MainActor in var originalTransport: String? = nil @@ -6267,7 +6288,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if isMessageBlocked(finalMessage) { return } // Classify origin: geochat if senderPeerID starts with 'nostr:', else mesh (or system) - let isGeo = finalMessage.senderPeerID?.isGeoChat == true + let isGeo = finalMessage.senderPeerID.isGeoChat // Apply per-sender and per-content rate limits (drop if exceeded) if finalMessage.sender != "system" { diff --git a/bitchat/_PreviewHelpers/BitchatMessage+Preview.swift b/bitchat/_PreviewHelpers/BitchatMessage+Preview.swift index 25a530cdf..e33478222 100644 --- a/bitchat/_PreviewHelpers/BitchatMessage+Preview.swift +++ b/bitchat/_PreviewHelpers/BitchatMessage+Preview.swift @@ -19,7 +19,7 @@ extension BitchatMessage { originalSender: nil, isPrivate: false, recipientNickname: "Jane Doe", - senderPeerID: nil, + senderPeerID: .empty, mentions: nil, deliveryStatus: .sent ) diff --git a/bitchatTests/EndToEnd/PublicChatE2ETests.swift b/bitchatTests/EndToEnd/PublicChatE2ETests.swift index 8a7f678ec..a8c65af18 100644 --- a/bitchatTests/EndToEnd/PublicChatE2ETests.swift +++ b/bitchatTests/EndToEnd/PublicChatE2ETests.swift @@ -388,7 +388,7 @@ struct PublicChatE2ETests { if let message = BitchatMessage(packet.payload) { // Don't relay own messages - guard message.senderPeerID?.id != node.peerID else { return } + guard message.senderPeerID != node.peerID else { return } // Create relay message let relayMessage = BitchatMessage( diff --git a/bitchatTests/Mocks/MockBLEService.swift b/bitchatTests/Mocks/MockBLEService.swift index c9002ff61..88b5a5235 100644 --- a/bitchatTests/Mocks/MockBLEService.swift +++ b/bitchatTests/Mocks/MockBLEService.swift @@ -301,7 +301,7 @@ final class MockBLEService: NSObject { let nextTTL = packet.ttl > 0 ? packet.ttl - 1 : 0 for neighbor in neighbors() { // Avoid immediate echo loopback to sender if known - if let sender = message.senderPeerID, sender == neighbor.peerID { continue } + if message.hasSenderPeerID && message.senderPeerID == neighbor.peerID { continue } var relay = packet relay.ttl = nextTTL neighbor.simulateIncomingPacket(relay) diff --git a/relays/online_relays_gps.csv b/relays/online_relays_gps.csv index eb5e8c277..c6f201bb6 100644 --- a/relays/online_relays_gps.csv +++ b/relays/online_relays_gps.csv @@ -1,269 +1,271 @@ Relay URL,Latitude,Longitude -relay-admin.thaliyal.com,40.8218,-74.45 -nostr.notribe.net,40.8302,-74.1299 -strfry.bonsai.com,37.8715,-122.273 -nostr-relay.online,40.7357,-74.1724 -shu05.shugur.net,48.8566,2.35222 -dev-nostr.bityacht.io,25.0797,121.234 -relay.nostrhub.tech,49.0291,8.35696 -relay.davidebtc.me,50.1109,8.68213 -relay.moinsen.com,50.4754,12.3683 -relay.olas.app,50.4754,12.3683 -mhp258zrpiiwn.clorecloud.net,43.6532,-79.3832 -orangepiller.org,60.1699,24.9384 -relayrs.notoshi.win,43.6532,-79.3832 +slick.mjex.me,39.048,-77.4817 relay.guggero.org,47.3769,8.54169 -nostr.blankfors.se,60.1699,24.9384 -wot.sovbit.host,64.1466,-21.9426 -nostr.huszonegy.world,47.4979,19.0402 -wot.sebastix.social,51.8933,4.42083 -articles.layer3.news,37.3387,-121.885 -nostr.spicyz.io,40.7357,-74.1724 -nostr.jerrynya.fun,31.2304,121.474 -nostr.oxtr.dev,50.4754,12.3683 -relay.mwaters.net,50.9871,2.12554 -vitor.nostr1.com,40.7128,-74.006 -relay.lumina.rocks,49.0291,8.35695 -nostr-relay-1.trustlessenterprise.com,40.7357,-74.1724 -nostr.bilthon.dev,25.8128,-80.2377 -nostr.now,36.55,139.733 -nostr.girino.org,40.7357,-74.1724 -nostr-01.yakihonne.com,1.32123,103.695 -ribo.af.nostria.app,-26.2041,28.0473 -nostrelay.memory-art.xyz,43.6532,-79.3832 -relay.evanverma.com,40.8302,-74.1299 -a.nos.lol,50.4754,12.3683 -purpura.cloud,40.7357,-74.1724 -relay.snort.social,43.6532,-79.3832 -relay.holzeis.me,43.6532,-79.3832 -nostr.spaceshell.xyz,40.7128,-74.006 -nostr.liberty.fans,36.9104,-89.5875 -relay.fundstr.me,42.3601,-71.0589 -wot.basspistol.org,49.4521,11.0767 -ribo.eu.nostria.app,52.3676,4.90414 +nostr-relay.online,40.7357,-74.1724 +relay.bullishbounty.com,40.7357,-74.1724 +nostr.kalf.org,52.3676,4.90414 +relay.hook.cafe,40.7357,-74.1724 +nostrcheck.me,43.6532,-79.3832 +relay.vrtmrz.net,40.7357,-74.1724 +nostr.notribe.net,40.8302,-74.1299 +nos.lol,50.4754,12.3683 relay.notoshi.win,13.4166,101.335 -relay.stream.labs.h3.se,59.4016,17.9455 -nostr.stakey.net,52.3676,4.90414 -relay.satlantis.io,32.8769,-80.0114 -nostr-2.21crypto.ch,47.4988,8.72369 -nostr.satstralia.com,64.1476,-21.9392 -slick.mjex.me,39.048,-77.4817 -nostr-relay.nextblockvending.com,47.2343,-119.853 -relay.origin.land,35.6673,139.751 -nostr.fbxl.net,48.382,-89.2502 -relay.nostr.place,32.7767,-96.797 -nr.yay.so,46.2126,6.1154 -nostream.breadslice.com,40.7357,-74.1724 -wot.tealeaf.dev,33.7488,-84.3877 -relay.primal.net,43.6532,-79.3832 -relay.chorus.community,50.1109,8.68213 -wot.dergigi.com,64.1476,-21.9392 -nostr-relay.amethyst.name,39.0438,-77.4874 -nostr.mehdibekhtaoui.com,49.4939,-1.54813 -relay.mess.ch,46.948,7.44745 -relay.sigit.io,50.4754,12.3683 -relay-rpi.edufeed.org,49.4543,11.0746 +nostrelites.org,41.8781,-87.6298 +nostr-02.czas.top,53.471,9.88208 +relay.2nix.de,60.1699,24.9384 nostr.faultables.net,43.6532,-79.3832 -relay.getsafebox.app,43.6532,-79.3832 -cyberspace.nostr1.com,40.7128,-74.006 -relay.endfiat.money,43.6532,-79.3832 -soloco.nl,43.6532,-79.3832 -nostr.kungfu-g.rip,33.7946,-84.4488 -nostrelay.circum.space,51.2217,6.77616 -relay.trustroots.org,43.6532,-79.3832 -relay.wellorder.net,45.5201,-122.99 -relay.coinos.io,40.7357,-74.1724 +nostr.noones.com,50.1109,8.68213 +relay.nostromo.social,49.4543,11.0746 +keys.nostr1.com,40.7057,-74.0136 +relay.toastr.net,40.8054,-74.0241 +nostr.spicyz.io,40.7357,-74.1724 +shu04.shugur.net,25.2604,55.2989 relay-testnet.k8s.layer3.news,37.3387,-121.885 -relay.nostriot.com,41.5695,-83.9786 -relay.bitcoinartclock.com,50.4754,12.3683 -nostr.einundzwanzig.space,50.1109,8.68213 -nostr.casa21.space,43.6532,-79.3832 -premium.primal.net,40.7357,-74.1724 -relay.tagayasu.xyz,43.6715,-79.38 +relay.freeplace.nl,52.3676,4.90414 +relay.wolfcoil.com,35.6092,139.73 +relay2.ngengine.org,43.6532,-79.3832 +nostr.davidebtc.me,50.1109,8.68213 +inbox.azzamo.net,52.2633,21.0283 +relay.arx-ccn.com,50.4754,12.3683 +nostr2.girino.org,43.6532,-79.3832 +nostr.camalolo.com,24.1469,120.684 nostr.mom,50.4754,12.3683 -nostr.zenon.network,43.5009,-70.4428 -nostr-pub.wellorder.net,45.5201,-122.99 -relay.g1sms.fr,43.9432,2.07537 -nostr-rs-relay-ishosta.phamthanh.me,40.7357,-74.1724 -relay.illuminodes.com,47.6061,-122.333 -dizzyspells.nostr1.com,40.7057,-74.0136 -relay.mostro.network,40.8302,-74.1299 -relay.nostr.wirednet.jp,34.706,135.493 -relay.barine.co,43.6532,-79.3832 -relay.damus.io,43.6532,-79.3832 -relay.0xchat.com,1.35208,103.82 -relay.mattybs.lol,40.7357,-74.1724 -no.str.cr,9.92857,-84.0528 -relay.utxo.farm,35.6916,139.768 -nostr.pleb.one,38.6327,-90.1961 -relay-dev.satlantis.io,40.8302,-74.1299 -relay.nostrdice.com,-33.8688,151.209 -relay.nostraddress.com,40.7357,-74.1724 -satsage.xyz,37.3986,-121.964 -offchain.pub,36.1809,-115.241 -noxir.kpherox.dev,34.8587,135.509 -nostr-relay.psfoundation.info,39.0438,-77.4874 -khatru.nostrver.se,51.8933,4.42083 -purplerelay.com,50.1109,8.68213 -relay.tapestry.ninja,40.8054,-74.0241 +relayone.soundhsa.com,33.1384,-95.6011 +relay.zone667.com,60.1699,24.9384 +nr.yay.so,46.2126,6.1154 +r.lostr.net,52.3676,4.90414 +zap.watch,45.5029,-73.5723 +relay.evanverma.com,40.8302,-74.1299 +nostr.sagaciousd.com,49.2827,-123.121 +dev-nostr.bityacht.io,25.0797,121.234 +relay.dwadziesciajeden.pl,52.2297,21.0122 +nostr.mehdibekhtaoui.com,49.4939,-1.54813 nostr.night7.space,50.4754,12.3683 -nostr.rikmeijer.nl,50.4754,12.3683 -relay1.nostrchat.io,60.1699,24.9384 -nostr.21crypto.ch,47.4988,8.72369 +wot.basspistol.org,49.4521,11.0767 +nostr.hekster.org,37.3986,-121.964 +relay.lumina.rocks,49.0291,8.35695 +offchain.pub,36.1809,-115.241 +satsage.xyz,37.3986,-121.964 +nostr.openhoofd.nl,51.9229,4.40833 +nostrelay.memory-art.xyz,43.6532,-79.3832 +nostr-02.yakihonne.com,1.32123,103.695 +yabu.me,35.6092,139.73 +relay.magiccity.live,25.8128,-80.2377 wot.soundhsa.com,33.1384,-95.6011 -relay.orangepill.ovh,49.1689,-0.358841 -talon.quest,43.6532,-79.3832 nostr-rs-relay.dev.fedibtc.com,39.0438,-77.4874 -wot.codingarena.top,50.4754,12.3683 -fanfares.nostr1.com,40.7128,-74.006 -gnostr.com,42.6978,23.3246 -nostrcheck.tnsor.network,40.7357,-74.1724 -nostrelites.org,41.8781,-87.6298 -relay.bitcoindistrict.org,43.6532,-79.3832 +relay.upleb.uk,52.2297,21.0122 +soloco.nl,40.7357,-74.1724 +nostr.plantroon.com,50.1013,8.62643 +relay.angor.io,48.1046,11.6002 +relay.sigit.io,50.4754,12.3683 +khatru.nostrver.se,51.8933,4.42083 +nostrelay.circum.space,51.2217,6.77616 +nproxy.kristapsk.lv,60.1699,24.9384 +nostr.chaima.info,51.223,6.78245 +nostr.bilthon.dev,25.8128,-80.2377 +nostr.azzamo.net,52.2633,21.0283 relay.fr13nd5.com,52.5233,13.3426 +nostr.agentcampfire.com,50.8933,6.05805 +relay.uid.ovh,43.6532,-79.3832 +nostr-rs-relay-ishosta.phamthanh.me,40.7357,-74.1724 +wot.sovbit.host,64.1466,-21.9426 +nostr-relay.cbrx.io,43.6532,-79.3832 +relay.tagayasu.xyz,43.6715,-79.38 +relay.nostr.wirednet.jp,34.706,135.493 wot.nostr.place,30.2672,-97.7431 -ithurtswhenip.ee,51.223,6.78245 -relay.dwadziesciajeden.pl,52.2297,21.0122 -relay2.ngengine.org,40.7357,-74.1724 -relay.nostr.net,50.4754,12.3683 -nostr-relay.cbrx.io,40.7357,-74.1724 +relay.libernet.app,43.6532,-79.3832 +purpura.cloud,43.6532,-79.3832 +adre.su,59.9311,30.3609 +strfry.felixzieger.de,50.1013,8.62643 +nostr-verified.wellorder.net,45.5201,-122.99 +relay.islandbitcoin.com,12.8498,77.6545 +gnostr.com,42.6978,23.3246 +relay.davidebtc.me,50.1109,8.68213 +freelay.sovbit.host,64.1476,-21.9392 +nostr.spaceshell.xyz,43.6532,-79.3832 +nostr.red5d.dev,43.6532,-79.3832 +relay.nostriches.club,43.6532,-79.3832 +relay.basspistol.org,46.2044,6.14316 +relay.ditto.pub,43.6532,-79.3832 +nostr.rtvslawenia.com,49.4543,11.0746 +relay.stream.labs.h3.se,59.4016,17.9455 +a.nos.lol,50.4754,12.3683 +relay.nostraddress.com,40.7357,-74.1724 dev-relay.lnfi.network,39.0997,-94.5786 -relay.jeffg.fyi,43.6532,-79.3832 -relay.ngengine.org,40.7357,-74.1724 -nos.xmark.cc,50.6924,3.20113 -relay.21e6.cz,50.1682,14.0546 -relay.degmods.com,50.4754,12.3683 -nostr.coincrowd.fund,39.0438,-77.4874 +relay.cosmicbolt.net,37.3986,-121.964 +relay03.lnfi.network,39.0997,-94.5786 +relay04.lnfi.network,39.0997,-94.5786 +nostr.snowbla.de,60.1699,24.9384 +santo.iguanatech.net,40.8302,-74.1299 nostr.myshosholoza.co.za,52.3676,4.90414 -relay.digitalezukunft.cyou,45.5019,-73.5674 -r.lostr.net,52.3676,4.90414 -relay.etch.social,41.2619,-95.8608 -nostr.tac.lol,47.4748,-122.273 -nostr.azzamo.net,52.2633,21.0283 -nostr.4rs.nl,49.0291,8.35696 -nostr-03.dorafactory.org,1.35208,103.82 -relay.copylaradio.com,51.223,6.78245 -nostr.camalolo.com,24.1469,120.684 +nostr-01.yakihonne.com,1.32123,103.695 +relay.usefusion.ai,38.7134,-78.1591 +relay.nostr.place,32.7767,-96.797 +relay.bitcoinartclock.com,50.4754,12.3683 +nostr.coincards.com,53.5501,-113.469 +relay.coinos.io,40.7357,-74.1724 +relay01.lnfi.network,39.0997,-94.5786 +no.str.cr,9.92857,-84.0528 +relay.holzeis.me,43.6532,-79.3832 +relay.agora.social,50.7383,15.0648 +theoutpost.life,64.1476,-21.9392 +articles.layer3.news,37.3387,-121.885 +relay.primal.net,40.7357,-74.1724 +nostr-2.21crypto.ch,47.4988,8.72369 +relay.puresignal.news,40.7357,-74.1724 +pyramid.fiatjaf.com,51.5072,-0.127586 +relay.wavlake.com,41.2619,-95.8608 +nostr.0x7e.xyz,47.4988,8.72369 +relay.21e6.cz,50.1682,14.0546 +nostr-relay.zimage.com,34.282,-118.439 +relay.fundstr.me,42.3601,-71.0589 +temp.iris.to,43.6532,-79.3832 nostr-dev.wellorder.net,45.5201,-122.99 -relay.nostx.io,43.6532,-79.3832 +relay.agorist.space,52.3734,4.89406 +relay.mwaters.net,50.9871,2.12554 +wot.utxo.one,40.7128,-74.006 +relay.utxo.farm,35.6916,139.768 +relayrs.notoshi.win,43.6532,-79.3832 r.bitcoinhold.net,43.6532,-79.3832 -nproxy.kristapsk.lv,60.1699,24.9384 -adre.su,59.9311,30.3609 -relay.hasenpfeffr.com,39.0438,-77.4874 -nos.lol,50.4754,12.3683 -relay.nostr.band,60.1699,24.9384 -nostr-02.czas.top,53.471,9.88208 +nostr.huszonegy.world,47.4979,19.0402 relay.nosto.re,51.8933,4.42083 -nostr.plantroon.com,50.1013,8.62643 -nostr.rblb.it,43.4633,11.8796 +relay.0xchat.com,1.35208,103.82 +relay.nostrhub.tech,49.0291,8.35696 +purplerelay.com,50.1109,8.68213 +nostr.namek.link,40.7357,-74.1724 nostr.thebiglake.org,32.71,-96.6745 -nostr.luisschwab.net,40.7357,-74.1724 -relay.electriclifestyle.com,26.2897,-80.1293 -librerelay.aaroniumii.com,43.6532,-79.3832 -nostr.88mph.life,40.7357,-74.1724 +nostr-relay.psfoundation.info,39.0438,-77.4874 +wot.dergigi.com,64.1476,-21.9392 +nostr.carroarmato0.be,50.9928,3.26317 +nostr.21crypto.ch,47.4988,8.72369 +relay.nostr.net,50.4754,12.3683 +cyberspace.nostr1.com,40.7128,-74.006 +srtrelay.c-stellar.net,43.6532,-79.3832 +relay.nostrdice.com,-33.8688,151.209 +wot.sebastix.social,51.8933,4.42083 +nostr.satstralia.com,64.1476,-21.9392 +relay.illuminodes.com,47.6061,-122.333 +relay.letsfo.com,51.098,17.0321 +relay-dev.satlantis.io,40.8302,-74.1299 +mhp258zrpiiwn.clorecloud.net,43.6532,-79.3832 +relay.nostrhub.fr,48.1046,11.6002 +nostr.simplex.icu,50.8198,-1.08798 +wot.nostr.party,36.1627,-86.7816 shu02.shugur.net,21.4902,39.2246 -relay.hook.cafe,40.7357,-74.1724 -strfry.elswa-dev.online,48.8566,2.35222 -wot.sudocarlos.com,51.5072,-0.127586 -relay.islandbitcoin.com,12.8498,77.6545 -nostr.tadryanom.me,40.7357,-74.1724 -relay.zone667.com,60.1699,24.9384 -nostr.agentcampfire.com,50.8933,6.05805 -relay.ditto.pub,40.7357,-74.1724 -relay03.lnfi.network,39.0997,-94.5786 -relay2.angor.io,48.1046,11.6002 -srtrelay.c-stellar.net,40.7357,-74.1724 -relayone.soundhsa.com,33.1384,-95.6011 +nostr.casa21.space,40.7357,-74.1724 +schnorr.me,43.6532,-79.3832 +nostr.sathoarder.com,48.5734,7.75211 +nostr.lostr.space,43.6532,-79.3832 +relay.barine.co,40.7357,-74.1724 +nostr-relay.xbytez.io,50.6924,3.20113 relay.javi.space,43.4633,11.8796 -nostr.carroarmato0.be,50.9928,3.26317 -nostr.hekster.org,37.3986,-121.964 -strfry.shock.network,41.8959,-88.2169 -nostr.2b9t.xyz,34.0549,-118.243 -relay.toastr.net,40.8054,-74.0241 -relay.bitcoinveneto.org,64.1466,-21.9426 -relay.wavlake.com,41.2619,-95.8608 -relay.arx-ccn.com,50.4754,12.3683 -relay.cosmicbolt.net,37.3986,-121.964 -relay.mccormick.cx,52.3563,4.95714 -temp.iris.to,40.7357,-74.1724 -relay.vrtmrz.net,40.7357,-74.1724 -nostr-relay.zimage.com,34.282,-118.439 -nostr.data.haus,50.4754,12.3683 -ribo.us.nostria.app,41.5868,-93.625 +nostr.smartflowsocial.com,40.7357,-74.1724 +bcast.seutoba.com.br,40.7357,-74.1724 +nostr.oxtr.dev,50.4754,12.3683 +nostr.n7ekb.net,47.4941,-122.294 +fanfares.nostr1.com,40.7057,-74.0136 +bcast.girino.org,43.6532,-79.3832 nostr.vulpem.com,49.4543,11.0746 -relay.agora.social,50.7383,15.0648 -nostr.ovia.to,43.6532,-79.3832 -nostr.red5d.dev,40.7357,-74.1724 -orangesync.tech,50.1109,8.68213 -relay.fountain.fm,39.0997,-94.5786 -relay.aloftus.io,34.0881,-118.379 -nostr.hifish.org,47.4043,8.57398 -relay.siamdev.cc,13.9178,100.424 -fenrir-s.notoshi.win,43.6532,-79.3832 -nostr.overmind.lol,43.6532,-79.3832 -wheat.happytavern.co,40.7357,-74.1724 -nostr.rtvslawenia.com,49.4543,11.0746 -relay.nostrhub.fr,48.1046,11.6002 -strfry.openhoofd.nl,51.9229,4.40833 -relay.usefusion.ai,38.7134,-78.1591 -relay.credenso.cafe,43.3601,-80.3127 -nostr.lostr.space,40.7357,-74.1724 -relay.jmoose.rocks,60.1699,24.9384 -relay.nostromo.social,49.4543,11.0746 -nostr.jfischer.org,49.0291,8.35696 -relay.wolfcoil.com,35.6092,139.73 -nostr.thaliyal.com,40.8218,-74.45 -relay.magiccity.live,25.8128,-80.2377 -relay.puresignal.news,40.7357,-74.1724 -prl.plus,55.7623,37.6381 -wot.brightbolt.net,47.6735,-116.781 -relay.varke.eu,52.6921,6.19372 -alienos.libretechsystems.xyz,55.4724,9.87335 -relay.goodmorningbitcoin.com,43.6532,-79.3832 -pyramid.fiatjaf.com,51.5072,-0.127586 +shu05.shugur.net,48.8566,2.35222 +nostr-03.dorafactory.org,1.35208,103.82 +relay.artx.market,43.652,-79.3633 +relay.chorus.community,50.1109,8.68213 +nostream.breadslice.com,43.6532,-79.3832 +nostr-relay.nextblockvending.com,47.2343,-119.853 +relay.olas.app,50.4754,12.3683 +strfry.elswa-dev.online,48.8566,2.35222 +nostr-relay.amethyst.name,39.0438,-77.4874 +relay.nostr.vet,52.6467,4.7395 relay02.lnfi.network,39.0997,-94.5786 -nostr.davidebtc.me,50.1109,8.68213 -nostr-verified.wellorder.net,45.5201,-122.99 +nos.xmark.cc,50.6924,3.20113 +nostr-01.uid.ovh,40.7357,-74.1724 +nostr.liberty.fans,36.9104,-89.5875 +nostr.4rs.nl,49.0291,8.35696 +wheat.happytavern.co,40.7357,-74.1724 +fenrir-s.notoshi.win,40.7357,-74.1724 +relay.btcforplebs.com,43.6532,-79.3832 +nostr.now,36.55,139.733 +orangepiller.org,60.1699,24.9384 +relay.satlantis.io,32.8769,-80.0114 relay.cypherflow.ai,48.8566,2.35222 -nostr.snowbla.de,60.1699,24.9384 -inbox.azzamo.net,52.2633,21.0283 -shu01.shugur.net,21.4902,39.2246 -nostr.middling.mydns.jp,35.8099,140.12 -nostr.kalf.org,52.3676,4.90414 -relay.laantungir.net,-19.4692,-42.5315 -relay.angor.io,48.1046,11.6002 -nostr2.girino.org,40.7357,-74.1724 -relay01.lnfi.network,39.0997,-94.5786 -nostr.chaima.info,51.223,6.78245 -x.kojira.io,43.6532,-79.3832 -shu04.shugur.net,25.2604,55.2989 -santo.iguanatech.net,40.8302,-74.1299 -relay.artx.market,43.652,-79.3633 -alien.macneilmediagroup.com,40.7357,-74.1724 -nostr.sathoarder.com,48.5734,7.75211 -zap.watch,45.5029,-73.5723 -relay.basspistol.org,46.2044,6.14316 -relay.13room.space,43.6532,-79.3832 -relay.bullishbounty.com,40.7357,-74.1724 -theoutpost.life,64.1476,-21.9392 -nostr.coincards.com,53.5501,-113.469 -black.nostrcity.club,41.8781,-87.6298 -relay.npubhaus.com,40.7357,-74.1724 -relay.freeplace.nl,52.3676,4.90414 -relay.seq1.net,43.6532,-79.3832 +relay.orangepill.ovh,49.1689,-0.358841 +wot.brightbolt.net,47.6735,-116.781 +wot.tealeaf.dev,33.7488,-84.3877 +relay.wellorder.net,45.5201,-122.99 ynostr.yael.at,60.1699,24.9384 -relay.nostr.vet,52.6467,4.7395 +ribo.af.nostria.app,-26.2041,28.0473 +nostr.tadryanom.me,43.6532,-79.3832 +relay.jeffg.fyi,43.6532,-79.3832 +relay.bitcoinveneto.org,64.1466,-21.9426 +nostr.data.haus,50.4754,12.3683 +relay.varke.eu,52.6921,6.19372 +relay.artiostr.ch,40.7357,-74.1724 +relay.moinsen.com,50.4754,12.3683 +nostr.makibisskey.work,40.7357,-74.1724 +relay.snort.social,40.7357,-74.1724 +premium.primal.net,43.6532,-79.3832 +relay.getsafebox.app,43.6532,-79.3832 +nostr-relay-1.trustlessenterprise.com,43.6532,-79.3832 relay.lifpay.me,1.35208,103.82 -relay.chakany.systems,43.6532,-79.3832 +strfry.openhoofd.nl,51.9229,4.40833 +nostrcheck.tnsor.network,43.6532,-79.3832 +noxir.kpherox.dev,34.8587,135.509 +relay5.bitransfer.org,43.6532,-79.3832 +ribo.us.nostria.app,41.5868,-93.625 +nostr-pub.wellorder.net,45.5201,-122.99 +relay.mostro.network,40.8302,-74.1299 +wot.nostr.net,40.7357,-74.1724 +relay.nostriot.com,41.5695,-83.9786 +nostr.mikoshi.de,50.1109,8.68213 +relay.mattybs.lol,40.7357,-74.1724 +relay.nostr.band,60.1699,24.9384 +strfry.shock.network,41.8959,-88.2169 +x.kojira.io,43.6532,-79.3832 +relay.etch.social,41.2619,-95.8608 relay.lightning.pub,41.8959,-88.2169 +relay.damus.io,43.6532,-79.3832 +relay.degmods.com,50.4754,12.3683 +relay.nostrcheck.me,43.6532,-79.3832 +relay.credenso.cafe,43.3601,-80.3127 +shu01.shugur.net,21.4902,39.2246 +relay.ru.ac.th,13.7584,100.622 +relay.goodmorningbitcoin.com,40.7357,-74.1724 +relay.bitcoindistrict.org,43.6532,-79.3832 +black.nostrcity.club,41.8781,-87.6298 +nostr.jerrynya.fun,31.2304,121.474 +orangesync.tech,50.1109,8.68213 +nostr.blankfors.se,60.1699,24.9384 +relay.chatbett.de,28.0199,-82.5248 +relay.siamdev.cc,13.8434,100.363 +relay.seq1.net,40.7128,-74.006 +dizzyspells.nostr1.com,40.7057,-74.0136 +strfry.bonsai.com,37.8715,-122.273 +nostr.stakey.net,52.3676,4.90414 +ithurtswhenip.ee,51.223,6.78245 +relay.mccormick.cx,52.3563,4.95714 +alien.macneilmediagroup.com,43.6532,-79.3832 +relay-rpi.edufeed.org,49.4543,11.0746 +relay.aloftus.io,34.0881,-118.379 +nostr.girino.org,40.7357,-74.1724 +nostr.einundzwanzig.space,50.1109,8.68213 +nostr.rikmeijer.nl,50.4754,12.3683 +relay2.angor.io,48.1046,11.6002 +nostr.zenon.network,43.5009,-70.4428 +relay.internationalright-wing.org,-22.5022,-48.7114 +relayb.uid.ovh,40.7357,-74.1724 +prl.plus,38.9072,-77.0369 +relay.ngengine.org,40.7357,-74.1724 +nostr.tac.lol,47.4748,-122.273 +nostr.88mph.life,43.6532,-79.3832 +relay.13room.space,40.7357,-74.1724 +relay.hasenpfeffr.com,39.0438,-77.4874 +nostr.zoracle.org,45.6018,-121.185 +nostr.luisschwab.net,40.7357,-74.1724 +relay.electriclifestyle.com,26.2897,-80.1293 +vitor.nostr1.com,40.7128,-74.006 +relay.origin.land,35.6673,139.751 +alienos.libretechsystems.xyz,55.4724,9.87335 +relay.fountain.fm,39.0997,-94.5786 wot.dtonon.com,43.6532,-79.3832 -yabu.me,35.6092,139.73 -wot.nostr.net,43.6532,-79.3832 -relay.libernet.app,40.7357,-74.1724 -relay04.lnfi.network,39.0997,-94.5786 -nostr.0x7e.xyz,47.4988,8.72369 -nostr.mikoshi.de,50.1109,8.68213 -wot.nostr.party,36.1627,-86.7816 -relay.letsfo.com,51.098,17.0321 -nostr.makibisskey.work,43.6532,-79.3832 -nostr.simplex.icu,50.8198,-1.08798 +nostr.hifish.org,47.4043,8.57398 +ribo.eu.nostria.app,52.3676,4.90414