An API-shape release: NostrClient's ~70 methods move onto eight feature namespaces backed by capability protocols, every client feature runs through the NostrSigning abstraction so a remote signer drives all of them, and the relay layer consolidates into NostrCore with normalized, string-based targeting and an injectable WebSocket transport. Two NIPs join the supported set — NIP-98 HTTP Auth and NIP-29 relay-based groups — and NIP-44 gains strict padding validation and the official vector suite.
Upgrading from 0.6.0 requires source changes. The full list is in the changelog.
Added
- Client feature namespaces —
identity,relays,events,subscriptions,routing,messages,groups, andlists, each aSendablevalue conforming to a capability protocol (NostrMessaging,NostrEventPublishing, …), so a feature can depend on the slice it uses and be tested against a stub. - NIP-98 HTTP Auth — sign kind-27235
Authorization: Nostrheaders with any signer viaHTTPAuthandURLRequest.setNostrAuthorization(signer:), and run the full check chain server-side withHTTPAuth.validate(...). - NIP-29 Relay-based groups — join/leave, chat with timeline references, the eight moderation kinds, relay-signed state parsing,
naddrshare links with invite codes, and the NIP-51 kind-10009 simple group list. - Official NIP-44 vectors — the canonical paulmillr/nip44 suite runs in full, replacing the four hand-copied vectors it stood in for.
- Transport injection — the designated
RelayConnection(url:webSocketFactory:config:)is public and the sharedRelayConnectionTransporttakes awebSocketFactory:and aconfig:, so a single relay, a NIP-46 session, or a NIP-47 wallet connection can run on a platform-native socket or an in-memory fake. - Single import —
NostrClient,NostrWalletConnect, andNostrConnectre-exportNostrCore.
Fixed
- NIP-44 padding is validated strictly — a payload carrying a valid MAC over non-canonical padding no longer decrypts; the block must be the length prefix plus exactly
calc_padded_lenof the declared length. - Event deduplication is scoped per subscription — an event matching two subscriptions previously reached only whichever processed it first, and a repeated
fetchof the same event returned it once and then nothing.maxDeduplicationCacheSizeis now enforced as events are recorded rather than by a once-a-minute sweep. - One socket per relay, however it is spelled — the pool keys connections by normalized URL, and a relay listed for both reading and writing under different spellings no longer emits two conflicting NIP-65
rtags. - NIP-47 commands no longer race their own connection — a command issued while the wallet connection was still connecting could publish before the response subscription existed, losing the reply; both NIP-46 and NIP-47 sessions now share one setup task.
Changed
- Breaking: every pre-namespace method and property on
NostrClientis removed; most calls keep their name under a namespace (client.publishTextNote(…)→client.events.publishTextNote(…)). - Breaking: a remote signer drives every feature —
NostrError.localSignerRequiredis gone,GiftWrapand the direct-message builder/parser takeany NostrSigningin place of aKeyPair, and the direct-message sequences are throwing. - Breaking: relay targeting is string-based (
to: [String]?), pool keys are normalized, and zero-relay operations thrownoRelaysInPool/noMatchingRelaysinstead of silently succeeding. - Breaking:
RemoteSignerTransportandWalletConnectTransportcollapse intoNostrCore.RelayTransport;Contact.relayUrland everyrelayUrl:argument label becomerelayURL.