Skip to content

Nostr Connect ​

The NostrConnectSigner is a client side implementation of a NIP-46 remote signer.

Relay Communication ​

The NostrConnectSigner requires two methods for communicating with relays: a subscription method for receiving events and a publish method for sending events.

These methods can be set either through the constructor or globally on the class. At least one of these approaches must be used before creating a NostrConnectSigner instance.

typescript
import { Observable } from "rxjs";

function subscriptionMethod(relays, filters) {
  return new Observable((observer) => {
    // Create subscription to relays
    const cleanup = subscribeToRelays(relays, filters, (event) => {
      observer.next(event);
    });

    return () => cleanup();
  });
}

async function publishMethod(relays, event) {
  for (const relay of relays) {
    await publishToRelay(relay, event);
  }
}

// Set methods globally once at app initialization
NostrConnectSigner.subscriptionMethod = subscriptionMethod;
NostrConnectSigner.publishMethod = publishMethod;

// Or pass them as options when creating a signer
const signer = new NostrConnectSigner({
  relays: ["wss://relay.example.com"],
  subscriptionMethod,
  publishMethod,
  // ... other options
});

Using the relay pool ​

The simplest way to set these methods is to use the RelayPool from the applesauce-relay package.

typescript
import { RelayPool } from "applesauce-relay";

const pool = new RelayPool();

// Set the pool globally
NostrConnectSigner.pool = pool;

// Or pass the pool as an option when creating a signer
const signer = new NostrConnectSigner({
  relays: ["wss://relay.example.com"],
  pool,
  // ... other options
});

Connection secrets ​

NIP-46 has two distinct secrets that are both serialized as the secret= parameter of their respective URIs, so the signer exposes them under separate names:

  • connectSecret — used by the nostrconnect:// flow (client initiates). The signer generates it (or you provide one), publishes it in the URI, and treats the remote signer as connected once it echoes the value back. Always set; defaults to a random value.
  • bunkerSecret — used by the bunker:// flow (signer initiates). The remote signer puts it in its bunker:// URI and the client sends it in the connect request to authorize itself. Optional.
ts
signer.connectSecret; // echoed by the signer in the nostrconnect:// flow
signer.bunkerSecret; // sent to the signer in the bunker:// flow

The released secret field is a deprecated alias for connectSecret; use the named fields instead.

Connecting to a remote signer ​

The NostrConnectSigner can be created with a remote signer's public key. Call connect() once to establish a new session.

js
import { NostrConnectSigner } from "applesauce-signers";

const signer = new NostrConnectSigner({
  remote: "<remote signer pubkey>",
  // Optional: Users pubkey
  pubkey: "<user pubkey>",
  // Optional: Custom subscription method
  subscriptionMethod: customSubMethod,
  // Optional: Custom publish method
  publishMethod: customPubMethod,
  // Optional: Custom auth handler
  onAuth: async (url) => {
    // Handle auth requests
  },
  // Optional: Client metadata sent with the `connect` request
  metadata: { name: "My App", url: "https://example.com" },
});

// start the connection process
await signer.connect(/* optional bunkerSecret */, /* optional requested permissions */);
console.log("Connected!");

// get the users pubkey
const pubkey = await signer.getPublicKey();
console.log("Users pubkey is", pubkey);

Resuming sessions ​

Remote signers track sessions by the client pubkey, so an established session never needs another connect request. After close(), or when restoring a signer with a known remote, the next request reopens the relay subscription and continues the session.

js
await signer.close();
await signer.signEvent(draft); // Resubscribes and sends the request without reconnecting

Timeouts and relay switching ​

Requests reject if the remote signer doesn't respond within timeout milliseconds (default 60_000, 0 disables). An auth_url challenge restarts the timer. Set autoSwitchRelays to send a switch_relays request after connecting so the remote signer can move the session to its preferred relays.

js
const signer = new NostrConnectSigner({ relays, pool, timeout: 30_000, autoSwitchRelays: true });

Ending a session ​

logout() sends a logout request, waits for the remote signer to acknowledge it, then closes the connection. The acknowledgement is only a courtesy, so pass a timeout to close anyway if the remote signer doesn't respond in time.

js
await signer.logout({ timeout: 5_000 }); // Defaults to the signer's request timeout

Initiating connection from client ​

To start a connection from the client side, you can use the getNostrConnectURI and waitForSigner methods:

js
const signer = new NostrConnectSigner({
  subscriptionMethod: customSubMethod,
  publishMethod: customPubMethod,
  relays: ["wss://relay.example.com"],
  // ... other options
});

// get the nostrconnect:// URI with optional metadata
const uri = signer.getNostrConnectURI({
  name: "My App",
  url: "https://example.com",
  image: "https://example.com/icon.png",
  permissions: NostrConnectSigner.buildSigningPermissions([0, 1, 3, 10002]),
});
console.log(uri);

// Create an optional AbortSignal to cancel the waiting process if needed
const controller = new AbortController();
const signal = controller.signal;

try {
  // wait for the remote signer to connect, optionally passing an AbortSignal
  await signer.waitForSigner(signal);
  console.log("Connected!");

  const pubkey = await signer.getPublicKey();
  console.log("Users pubkey is", pubkey);
} catch (error) {
  console.error("Connection was aborted:", error);
}

// Later, if you need to abort the waiting process:
setTimeout(() => {
  controller.abort();
}, 10_000);

Handling bunker URIs ​

The NostrConnectSigner.fromBunkerURI method can be used to create a new signer from a bunker:// URI, it will return a promise that will resolve when the signer is connected.

js
const signer = await NostrConnectSigner.fromBunkerURI(
  "bunker://266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5?relay=wss://relay.nsec.app&secret=d9aa70",
  {
    permissions: NostrConnectSigner.buildSigningPermissions([0, 1, 3, 10002]),
    // ... other options
  },
);

You can also parse a bunker URI manually using NostrConnectSigner.parseBunkerURI:

js
const { remote, relays, bunkerSecret } = NostrConnectSigner.parseBunkerURI(uri);

Handling nbunksec sessions ​

The nbunksec format stores a complete NIP-46 client session, including the local client private key. Treat it like a secret credential. fromNbunksec resumes the stored session without sending a new connect request.

js
const signer = await NostrConnectSigner.fromNbunksec("nbunksec1...");

// Export the current session for later import
const encoded = signer.getNbunksec();

Permissions ​

The NostrConnectSigner uses a set of predefined permissions that can be requested from the remote signer:

typescript
enum Permission {
  GetPublicKey = "get_public_key",
  SignEvent = "sign_event",
  Nip04Encrypt = "nip04_encrypt",
  Nip04Decrypt = "nip04_decrypt",
  Nip44Encrypt = "nip44_encrypt",
  Nip44Decrypt = "nip44_decrypt",
}

Use the static NostrConnectSigner.buildSigningPermissions method to create an array of signing permissions for specific event kinds:

js
const permissions = NostrConnectSigner.buildSigningPermissions([0, 1, 3, 10002]);

These permissions can be passed when:

  • Connecting to a remote signer via connect(bunkerSecret, permissions)
  • Creating a nostr connect URI via getNostrConnectURI({ permissions })
  • Creating a signer from a bunker URI via fromBunkerURI(uri, { permissions })

App Metadata ​

When creating a nostr connect URI, you can provide metadata about your application:

typescript
type NostrConnectAppMetadata = {
  name?: string;
  image?: string;
  url?: string | URL;
  permissions?: string[];
};

This metadata is used to display information about your application to the user when they connect their signer. Pass it as the metadata option to also send it as the client metadata of connect requests in the bunker:// flow.

Encryption Methods ​

The NostrConnectSigner supports both NIP-04 and NIP-44 encryption through the nip04 and nip44 properties:

typescript
// NIP-04 encryption
const encrypted = await signer.nip04.encrypt(pubkey, plaintext);
const decrypted = await signer.nip04.decrypt(pubkey, ciphertext);

// NIP-44 encryption
const encrypted = await signer.nip44.encrypt(pubkey, plaintext);
const decrypted = await signer.nip44.decrypt(pubkey, ciphertext);