Skip to content

NIP-46

Nostr Connect - remote signing over relays. Delegates signing to a remote signer (e.g. nsecBunker, Amber, LNbits) via NIP-04 encrypted kind 24133 events.

NostrConnect

Constructor

Accepts a nostrconnect:// or bunker:// URI string, or a Nip46ConnectionOptions object:

ts
import { NostrConnect } from 'nostr-core'

// From bunker:// URI (with multiple relays, secret, and app metadata)
const signer = new NostrConnect(
  'bunker://<remote-pubkey>?relay=wss://relay1.example.com&relay=wss://relay2.example.com&secret=mytoken&name=MyApp&url=https://myapp.com'
)

// From nostrconnect:// URI
const signer = new NostrConnect('nostrconnect://<remote-pubkey>?relay=wss://relay.example.com')

// From options
const signer = new NostrConnect({
  remotePubkey: '<hex-pubkey>',
  relayUrls: ['wss://relay1.example.com', 'wss://relay2.example.com'],
  secretKey: mySecretKey,  // optional - random key generated if omitted
  secret: 'mytoken',      // optional - required by Amber/LNbits signers
})

Connection

connect() tries each relay in order until one succeeds (fallback behavior):

ts
await signer.connect()     // Connect to relay + NIP-46 handshake
await signer.disconnect()  // Send disconnect RPC + close relay
signer.close()             // Immediate cleanup (no disconnect RPC)

Signer Methods

ts
const pubkey = await signer.getPublicKey()

const signed = await signer.signEvent({
  kind: 1,
  created_at: Math.floor(Date.now() / 1000),
  tags: [],
  content: 'Hello via remote signer!',
})

NIP-04 Encryption

ts
const encrypted = await signer.nip04.encrypt(recipientPubkey, 'secret message')
const decrypted = await signer.nip04.decrypt(senderPubkey, encrypted)

NIP-44 Encryption

ts
const encrypted = await signer.nip44.encrypt(recipientPubkey, 'secret message')
const decrypted = await signer.nip44.decrypt(senderPubkey, encrypted)

Other Methods

ts
const methods = await signer.describe()  // ['connect', 'sign_event', 'nip44_encrypt', ...]
const relays = await signer.getRelays()  // RelayMap

Configuration

ts
signer.timeout = 30000  // RPC timeout in ms (default: 60s)

parseConnectionURI

Parses a nostrconnect:// or bunker:// URI into its components. Supports multiple relay params, optional secret, and optional app metadata (name, url, image):

ts
import { parseConnectionURI } from 'nostr-core'

const opts = parseConnectionURI(
  'bunker://<pubkey>?relay=wss://r1.example.com&relay=wss://r2.example.com&secret=tok&name=MyApp'
)
// opts.remotePubkey  - hex pubkey
// opts.relayUrls     - ['wss://r1.example.com', 'wss://r2.example.com']
// opts.secret        - 'tok'
// opts.appMetadata   - { name: 'MyApp' }

Nip46ConnectionOptions

ts
type Nip46ConnectionOptions = {
  remotePubkey: string
  relayUrls: string[]
  secretKey?: Uint8Array
  secret?: string
}

Nip46AppMetadata

Extracted from URI query params when present:

ts
type Nip46AppMetadata = {
  name?: string
  url?: string
  image?: string
}

Nip46Method

ts
type Nip46Method =
  | 'connect'
  | 'disconnect'
  | 'describe'
  | 'get_public_key'
  | 'sign_event'
  | 'nip04_encrypt'
  | 'nip04_decrypt'
  | 'nip44_encrypt'
  | 'nip44_decrypt'
  | 'get_relays'

Errors

ClassCodeDescription
Nip46ErrorNIP46_ERRORBase error for NIP-46 operations
Nip46TimeoutErrorNIP46_TIMEOUTRPC request timed out
Nip46ConnectionErrorNIP46_CONNECTION_ERRORFailed to connect to relay or handshake failed
Nip46RemoteErrorNIP46_REMOTE_ERRORRemote signer returned an error

Released under the MIT License.