3.2 KiB
SigningAdapter guide
SigningAdapter is the lightweight interface bitvid uses to sign direct-message
payloads and message metadata without depending on a specific auth provider.
The canonical implementations live in js/auth/signingAdapter.js, and callers
such as NostrClient.sendDirectMessage() and the DM composer accept a
signingAdapter option so they can work with NIP-07 extensions, remote signers,
or test doubles.
Interface contract
A SigningAdapter is an object with the following async methods:
getPubkey()→ resolves the hex public key that should be used as the DM author.getDisplayName()→ optional display name string for UI metadata.signEvent(unsignedEvent)→ returns a fully signed Nostr event object.signMessage(message)→ returns a signature for an arbitrary string.
signEvent() is required for DM publishing; signMessage() is optional but is
used by the DM composer to attach a signature to the outgoing payload metadata.
Implementing a new adapter
- Normalize the pubkey: resolve and return a lowercased hex pubkey from
getPubkey(). The NIP-07 adapter does this by delegating towindow.nostr.getPublicKey()and normalizing the result. - Provide signing primitives:
signEvent()must return an event withidandsigpopulated.signMessage()should return a stable signature string for the message body when you need message-level signatures (for example, the DM composer includes this for UI payloads).
- Expose a type label: include a
typefield (for examplenip07,dev, ortest) to make debugging easier.
Reference implementations:
createNip07SigningAdapter({ extension })uses a browser extension signer.createEphemeralDevSigningAdapter()generates a local, throwaway keypair for development flows.createTestSigningAdapter()returns deterministic signatures for tests.
Testing a SigningAdapter
Unit tests
For Node-based tests, use createTestSigningAdapter() so signatures are stable
and deterministic. You can pass it directly to NostrClient.sendDirectMessage()
through the options.signingAdapter field to avoid relying on active signers:
import { createTestSigningAdapter } from "../js/auth/signingAdapter.js";
import { nostrClient } from "../js/nostrClientFacade.js";
const adapter = createTestSigningAdapter({
pubkey: "f".repeat(64),
});
const result = await nostrClient.sendDirectMessage(
"npub...",
"hello",
null,
{ signingAdapter: adapter }
);
Assertions can then verify that result.ok is true and that the signed event
was built with the adapter's pubkey and signatures.
Manual smoke checks
For browser-only checks, wire the adapter into the DM composer by passing it in
as the signingAdapter prop. The composer will call getPubkey(),
getDisplayName(), and signMessage() while building the outgoing payload
object so you can inspect the resulting metadata in your handler.
Usage reminders
- When the signer does not implement NIP-44 encryption, NIP-17 sends are blocked; keep the privacy toggle disabled until encryption is available.
- Always surface errors from
getPubkey()orsignMessage()to the caller so the UI can display a meaningful failure state.