https://github.com/yysskk/swift-nostr-client
A Swift library for the Nostr protocol, built with Swift 6 concurrency support.
https://github.com/yysskk/swift-nostr-client
bitcoin ios macos nostr tvos watchos
Last synced: 4 months ago
JSON representation
A Swift library for the Nostr protocol, built with Swift 6 concurrency support.
- Host: GitHub
- URL: https://github.com/yysskk/swift-nostr-client
- Owner: yysskk
- License: mit
- Created: 2026-01-19T07:25:26.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-01-31T07:00:26.000Z (5 months ago)
- Last Synced: 2026-02-18T10:43:11.662Z (4 months ago)
- Topics: bitcoin, ios, macos, nostr, tvos, watchos
- Language: Swift
- Homepage:
- Size: 73.2 KB
- Stars: 6
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-nostr - swift-nostr-client - nostr-client.svg?style=social) - A modern Swift library for the Nostr protocol with Swift 6 concurrency support (Libraries / Client reviews and/or comparisons)
README
# NostrClient
A modern Swift library for the Nostr protocol, built with Swift 6 concurrency support.
## Features
- **Full NIP-01 Support**: Events, subscriptions, and relay communication
- **NIP-02 Contact List**: Follow/unfollow users and manage contact lists
- **NIP-03 OpenTimestamps**: Attach OTS attestations to events
- **NIP-05 Verification**: DNS-based identifier verification
- **NIP-06 Key Derivation**: Generate keys from BIP-39 mnemonic seed phrases
- **NIP-17 Private DMs**: End-to-end encrypted direct messages with sender anonymity
- **Cryptographic Operations**: Schnorr signatures with secp256k1
- **Bech32 Encoding**: npub/nsec key encoding (NIP-19)
- **Async/Await**: Modern Swift concurrency with actors
- **Multi-Relay Support**: Connect to multiple relays with RelayPool
- **Type-Safe**: Full Sendable compliance for thread safety
## Requirements
- Swift 6.2+
- iOS 17.0+ / macOS 14.0+ / tvOS 17.0+ / watchOS 10.0+ / visionOS 1.0+
## Installation
### Swift Package Manager
Add to your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/yysskk/swift-nostr-client", from: "1.0.0")
]
```
Or add via Xcode: File → Add Package Dependencies → Enter repository URL.
## Quick Start
### Generate Keys
```swift
import NostrClient
// Generate a new random keypair
let keyPair = try KeyPair()
print("Public Key: \(keyPair.publicKeyHex)")
print("npub: \(keyPair.npub)")
print("nsec: \(keyPair.nsec)")
// Import from nsec
let imported = try KeyPair(nsec: "nsec1...")
// Generate from mnemonic (NIP-06)
let (mnemonic, keyPairFromMnemonic) = try KeyPair.generate(wordCount: 12)
print("Mnemonic: \(mnemonic.phrase)")
print("Public Key: \(keyPairFromMnemonic.npub)")
// Restore from existing mnemonic
let restored = try KeyPair(mnemonicPhrase: "leader monkey parrot ring guide accident before fence cannon height naive bean")
```
### Connect to Relays
```swift
let client = NostrClient()
// Add relays
try await client.addRelays([
"wss://relay.damus.io",
"wss://nos.lol",
"wss://relay.nostr.band"
])
// Connect
try await client.connect()
```
### Publish Events
```swift
// Set your private key
try await client.setNsec("nsec1...")
// Publish a text note
let event = try await client.publishTextNote(content: "Hello, Nostr!")
// Publish with tags
let tagged = try await client.publishTextNote(
content: "Check out #nostr",
tags: [["t", "nostr"]]
)
// React to an event
try await client.publishReaction(to: event, content: "🤙")
```
### Subscribe to Events
```swift
// Subscribe to a user's notes
let subscriptionId = try await client.subscribeToUserTimeline(pubkey: "...") { event in
print("New note: \(event.content)")
}
// Subscribe to the global feed
try await client.subscribeToGlobalFeed(limit: 50) { event in
print("Global: \(event.content)")
}
// Custom filter subscription
let filter = Filter(
kinds: [1],
authors: ["pubkey1", "pubkey2"],
limit: 100
)
try await client.subscribe(filters: [filter]) { event in
print("Received: \(event.id)")
}
// Unsubscribe
try await client.unsubscribe(subscriptionId: subscriptionId)
```
### Fetch Events
```swift
// Fetch specific event
let event = try await client.fetchEvent(id: "eventid...")
// Fetch user metadata
let metadata = try await client.fetchMetadata(pubkey: "...")
print("Name: \(metadata?.name ?? "Unknown")")
```
## Models
### Event
```swift
public struct Event: Codable, Identifiable, Hashable, Sendable {
public let id: String
public let pubkey: String
public let createdAt: Int64
public let kind: Int
public let tags: [[String]]
public let content: String
public let sig: String
}
```
### Filter
```swift
public struct Filter: Codable, Sendable, Hashable {
public var ids: [String]?
public var authors: [String]?
public var kinds: [Int]?
public var eventReferences: [String]? // #e
public var pubkeyReferences: [String]? // #p
public var since: Int64?
public var until: Int64?
public var limit: Int?
}
```
### Event Kinds
```swift
Event.Kind.setMetadata // 0
Event.Kind.textNote // 1
Event.Kind.recommendRelay // 2
Event.Kind.contacts // 3
Event.Kind.eventDeletion // 5
Event.Kind.repost // 6
Event.Kind.reaction // 7
Event.Kind.seal // 13 (NIP-59)
Event.Kind.privateDirectMessage // 14 (NIP-17)
Event.Kind.giftWrap // 1059 (NIP-59)
Event.Kind.zapRequest // 9734
Event.Kind.zap // 9735
// ... and more
```
## Low-Level API
### Direct Relay Connection
```swift
let connection = try RelayConnection(urlString: "wss://relay.damus.io")
await connection.connect()
// Subscribe
try await connection.subscribe(
subscriptionId: "sub1",
filters: [Filter(kinds: [1], limit: 10)]
)
// Listen for messages
for await message in await connection.messages() {
switch message {
case .event(let subId, let event):
print("Event: \(event.content)")
case .endOfStoredEvents(let subId):
print("EOSE for \(subId)")
case .notice(let msg):
print("Notice: \(msg)")
default:
break
}
}
```
### Manual Event Signing
```swift
let keyPair = try KeyPair()
let signer = EventSigner(keyPair: keyPair)
let unsigned = UnsignedEvent(
pubkey: keyPair.publicKeyHex,
kind: .textNote,
tags: [["t", "test"]],
content: "Manual signing example"
)
let signed = try signer.sign(unsigned)
// Verify signature
let isValid = try signed.verify()
```
## Supported NIPs
- [x] NIP-01: Basic protocol
- [x] NIP-02: Contact list and petnames
- [x] NIP-03: OpenTimestamps attestations
- [x] NIP-05: DNS-based identifiers
- [x] NIP-06: Basic key derivation from mnemonic seed phrase
- [x] NIP-17: Private direct messages
- [x] NIP-19: bech32-encoded entities (npub, nsec)
- [x] NIP-20: Command Results (OK)
- [x] NIP-42: Authentication (AUTH message parsing)
- [x] NIP-44: Versioned encryption
- [x] NIP-59: Gift wrap
## License
MIT License