@snort/shared
Common utilities shared across the Snort ecosystem.
Installation
bun add @snort/sharedKey Management
getPublicKey(privKey: string | Uint8Array): string
Derive a public key from a private key.
import { getPublicKey } from '@snort/shared'
const pubkey = getPublicKey('hex-private-key')sha256(data: string | Uint8Array): string
Compute SHA-256 hash.
import { sha256 } from '@snort/shared'
const hash = sha256('hello world')Bech32 Encoding
hexToBech32(hrp: string, id?: string): string
Encode hex data to bech32.
import { hexToBech32 } from '@snort/shared'
const npub = hexToBech32('npub', 'hex-pubkey')
const note = hexToBech32('note', 'hex-event-id')bech32ToHex(str: string): string
Decode bech32 to hex.
const hex = bech32ToHex('npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws')bech32ToText(str: string): string
Decode bech32 to UTF-8 text.
TLV Encoding
encodeTLV(prefix: string, id: Uint8Array, relays?: string[], kind?: number, author?: string): string
Encode TLV data into a bech32 string (used for nprofile, nevent, naddr).
decodeTLV(str: string): TLVEntry[]
Decode TLV entries from a bech32 string.
TLVEntryType
enum TLVEntryType {
Special = 0, // The entity ID
Relay = 1, // Relay hint
Author = 2, // Author pubkey
Kind = 3, // Event kind
}NostrPrefix
enum NostrPrefix {
PublicKey = "npub",
PrivateKey = "nsec",
Note = "note",
Profile = "nprofile",
Event = "nevent",
Relay = "nrelay",
Address = "naddr",
}NIP-05
fetchNip05Pubkey(name: string, domain: string, timeout?: number): Promise<string | undefined>
Resolve a NIP-05 identifier to a hex pubkey.
import { fetchNip05Pubkey } from '@snort/shared'
const pubkey = await fetchNip05Pubkey('kieran', 'snort.social')fetchNostrAddress(name: string, domain: string, timeout?: number): Promise<NostrJson | undefined>
Fetch full NIP-05 JSON document.
fetchNip05PubkeyWithThrow(name: string, domain: string, timeout?: number): Promise<string>
Same as fetchNip05Pubkey but throws on error.
LNURL
LNURL
LNURL-pay client implementation.
import { LNURL } from '@snort/shared'
// From lightning address
const svc = new LNURL('[email protected]')
// From lnurl bech32
const svc = new LNURL('lnurl1...')
// Load service info
await svc.load()
// Check capabilities
svc.canZap // boolean
svc.min // min millisats
svc.max // max millisats
svc.maxCommentLength
svc.zapperPubkey
// Get invoice
const invoice = await svc.getInvoice(100, 'Zap comment', zapRequestEvent)
console.log(invoice.pr) // bolt11 invoiceLNURLService
interface LNURLService {
tag: string
nostrPubkey?: string
minSendable?: number
maxSendable?: number
metadata: string
callback: string
commentAllowed?: number
}Invoice Decoding
decodeInvoice(pr: string): InvoiceDetails | undefined
Decode a bolt11 Lightning invoice.
import { decodeInvoice } from '@snort/shared'
const details = decodeInvoice('lnbc1000...')
console.log(details?.amount) // millisats
console.log(details?.description) // payment description
console.log(details?.expired) // boolean
console.log(details?.paymentHash) // hex
console.log(details?.timestamp) // unix timestampInvoiceDetails
interface InvoiceDetails {
amount?: number
expire?: number
timestamp?: number
description?: string
descriptionHash?: string
paymentHash?: string
expired: boolean
pr: string
}Utility Functions
unixNow(): number
Current Unix timestamp in seconds.
unixNowMs(): number
Current Unix timestamp in milliseconds.
unwrap<T>(v: T | undefined | null): T
Throw if value is null/undefined, otherwise return it.
sanitizeRelayUrl(url: string): string | undefined
Normalize a relay URL.
dedupe<T>(v: T[]): T[]
Remove duplicates from array.
appendDedupe<T>(a?: T[], b?: T[]): T[]
Concat two arrays and deduplicate.
dedupeBy<T>(v: T[], mapper: (x: T) => string): T[]
Deduplicate by a key function.
removeUndefined<T>(v: (T | undefined)[]): T[]
Filter out undefined values.
deepClone<T>(obj: T): T
Deep clone an object.
deepEqual(x: any, y: any): boolean
Deep equality check.
isHex(s?: string): boolean
Check if a string is valid hex.
isOffline(): boolean
Check if the browser is offline.
normalizeReaction(content: string): Reaction
Normalize reaction content to + or -.
import { normalizeReaction, Reaction } from '@snort/shared'
normalizeReaction('👍') // Reaction.Positive
normalizeReaction('👎') // Reaction.Negative
normalizeReaction('+') // Reaction.PositiveFeedCache
Base cache class with optional persistent storage. See Caching for full documentation.
CacheStore
Interface for persistent storage backends.
interface CacheStore<T> {
get(key: string): Promise<T | undefined>
bulkGet(keys: string[]): Promise<T[]>
put(obj: T): Promise<void>
bulkPut(obj: T[]): Promise<void>
delete(key: string): Promise<void>
bulkDelete(keys: string[]): Promise<void>
clear(): Promise<void>
keys(): Promise<string[]>
}ExternalStore
React-like external store for subscribing to state changes.
WorkQueue
Simple work queue for serializing async operations.
WorkQueueItem
interface WorkQueueItem {
next: () => Promise<void>
resolve: (value: any) => void
reject: (reason?: any) => void
}processWorkQueue(queue?: WorkQueueItem[], queueDelay?: number): void
Start processing a work queue. Default delay is 200ms.
barrierQueue<T>(queue: WorkQueueItem[], fn: () => Promise<T>): Promise<T>
Execute a function exclusively (one at a time per queue).
SortedMap
A map that maintains sorted order by key.
ImgProxy
URL builder for ImgProxy image resizing service.
ImgProxySettings
interface ImgProxySettings {
url: string
key: string
salt: string
}DefaultImgProxy
Default ImgProxy settings object.
proxyImg(url: string, settings?: ImgProxySettings, resize?: number, sha256?: string): string
Generate a proxied/resized image URL.
import { proxyImg, DefaultImgProxy } from '@snort/shared'
// Using default settings
const url = proxyImg('https://example.com/image.jpg', DefaultImgProxy, 200)
// Without settings, returns the original URL
const original = proxyImg('https://example.com/image.jpg')