Introducing swift-nostr: Nostr for the Swift Ecosystem

For the past several months I’ve been building swift-nostr, an open-source Swift library for the Nostr protocol. With today’s 0.6.0 release the feature set is broad enough — 33 NIPs, four libraries, and a full documentation site — that it deserves a proper introduction.

What is Nostr?

Nostr (“Notes and Other Stuff Transmitted by Relays”) is a minimal, open protocol for decentralized social networking. Your identity is a keypair, not an account: every post, profile update, and message is a JSON event signed with your key, and clients publish those events to simple WebSocket servers called relays. There is no central server to ban you or lose your data — switch relays and your identity comes with you. On top of that simple core, the community layers optional extensions called NIPs (Nostr Implementation Possibilities): encrypted direct messages, Lightning payments, long-form articles, and much more.

Why another Nostr library?

When I started exploring Nostr from Swift, I wanted a library that felt like it was designed for the language rather than ported to it. That became the design brief for swift-nostr:

Four libraries, one package

The package vends four products:

A taste of the API

Generate an identity, connect to a couple of relays, and publish a note:

import NostrClient
import NostrCore

let keyPair = try KeyPair()
print(try keyPair.npub)

let client = NostrClient()
try await client.connect(to: ["wss://relay.damus.io", "wss://nos.lol"])
try await client.setNsec(try keyPair.nsec)

let note = try await client.publishTextNote(content: "Hello, Nostr!")
print("Accepted by \(note.result.acceptedRelays.count) relay(s)")

Reading is just as direct — subscriptions are async sequences:

let timeline = try await client.subscribeToUserTimeline(pubkey: somePubkey)
for await event in timeline.events {
    print(event.content)
}

End-to-end encrypted direct messages (NIP-17) arrive already decrypted and parsed:

for await message in try await client.directMessages() {
    print("\(message.senderPubkey): \(message.content)")
}
try await client.sendDirectMessage("Hello privately!", to: recipientPubkey)

And paying a Lightning zap through a remote wallet (NIP-47) is one call:

import NostrWalletConnect

let connection = WalletConnection(uri: try WalletConnectURI(string: "nostr+walletconnect://…"))
let payment = try await connection.payInvoice("lnbc…")
print(payment.preimage)

What’s new in 0.6.0

The 0.6.0 release is the largest so far:

Learn by example

Alongside the library there’s a companion repo, swift-nostr-examples: nearly forty small, self-contained, unit-tested “case studies” covering everything from generating a keypair to reconstructing threads, building zap requests, and connecting to a bunker. It also ships a SwiftUI showcase app for iOS and macOS that lets you browse every example and run several of them live against real relays. If you learn best by reading working code, start there.

Getting started

swift-nostr requires Swift 6.3+ and runs on iOS 17, macOS 14, tvOS 17, watchOS 10, and visionOS 1. Add it with Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/yysskk/swift-nostr", from: "0.6.0")
]

The API documentation covers all four libraries, with Getting Started and Advanced Usage guides for each.

The library is MIT-licensed and contributions are welcome. If you build something with it — or run into something that could be better — I’d love to hear about it on GitHub. I’m also writing a hands-on implementation guide that walks through building a Nostr client with swift-nostr from scratch — that post is up next.

← Back to blog