Signers

Signers are responsible for cryptographic operations: signing events, encrypting and decrypting messages. NDK supports multiple signer types with a unified API.

Signer Types

Type Description Pending Requests
Bip340EventSigner Local signer with private key (pure Dart) No (instant)
WebEventSigner Fast local signer for web (JS crypto) No (instant)
NdkEventSigner Automatic platform selection (web/native) No (instant)
Nip46EventSigner Remote signer via NIP-46 bunker Yes
Nip07EventSigner Browser extension (NIP-07) Yes
Nip55EventSigner Android NIP-55 external signer app Yes

Pending Requests

External signers (NIP-46, NIP-07, NIP-55) require user approval for operations. The unified pending requests API allows your UI to:

  • Display pending operations to the user
  • Cancel pending requests
  • React to request completion

Listening to Pending Requests

You can access pending requests directly from an Account or through its signer:

final account = ndk.accounts.getLoggedAccount();

// Stream-based (reactive) - directly on account
account?.pendingRequestsStream.listen((requests) {
  print('${requests.length} pending request(s)');
  for (final request in requests) {
    print('- ${request.method.protocolString} (${request.id})');
  }
});

// Snapshot (synchronous)
final currentRequests = account?.pendingRequests ?? [];

PendingSignerRequest Properties

class PendingSignerRequest {
  final String id;                    // Unique request identifier
  final SignerMethod method;          // signEvent, nip44Encrypt, etc.
  final DateTime createdAt;           // When the request was created
  final String signerPubkey;          // Public key of the signer
  final Nip01Event? event;            // Event being signed (for signEvent)
  final String? plaintext;            // Text being encrypted
  final String? ciphertext;           // Text being decrypted
  final String? counterpartyPubkey;   // Other party's pubkey (encrypt/decrypt)

  String? get content;                // Returns plaintext or ciphertext
}

Cancelling Requests

// Cancel a specific request
final cancelled = account.cancelRequest(requestId);

if (cancelled) {
  print('Request cancelled');
} else {
  print('Request not found');
}

When cancelled, the caller receives a SignerRequestCancelledException:

try {
  await signer.sign(event);
} on SignerRequestCancelledException catch (e) {
  print('User cancelled locally: ${e.requestId}');
}

Handling Remote Rejections

When the user rejects a request on the remote signer (bunker, browser extension, NIP-55 signer app), the caller receives a SignerRequestRejectedException:

try {
  await signer.sign(event);
} on SignerRequestRejectedException catch (e) {
  print('User rejected on remote signer: ${e.requestId}');
  print('Original message: ${e.originalMessage}');
} on SignerRequestCancelledException catch (e) {
  print('User cancelled locally: ${e.requestId}');
}
Exception When thrown
SignerRequestCancelledException cancelRequest() called locally
SignerRequestRejectedException User rejected on remote signer

UI Example

StreamBuilder<List<PendingSignerRequest>>(
  stream: account.pendingRequestsStream,
  builder: (context, snapshot) {
    final requests = snapshot.data ?? [];

    if (requests.isEmpty) {
      return Text('No pending requests');
    }

    return ListView.builder(
      itemCount: requests.length,
      itemBuilder: (context, index) {
        final request = requests[index];
        return ListTile(
          title: Text(request.method.protocolString),
          subtitle: Text('Created ${request.createdAt}'),
          trailing: IconButton(
            icon: Icon(Icons.cancel),
            onPressed: () => account.cancelRequest(request.id),
          ),
        );
      },
    );
  },
)

Web Crypto

On web platforms, pure Dart elliptic curve operations are slow. WebEventSigner uses @noble/curves and the Web Crypto API via JS interop for fast BIP-340 signing, NIP-04 and NIP-44 encryption.

import 'package:ndk_flutter/signers/web_event_signer.dart';

final signer = WebEventSigner(
  privateKey: myPrivkey,
  publicKey: myPubkey,
);

WebEventSigner is only available on web. Use Bip340EventSigner for native platforms.

Platform Selection

NdkEventSigner automatically picks the best implementation for the current platform:

  • Web: uses WebEventSigner (fast JS crypto)
  • Native: uses Bip340EventSigner (pure Dart)
import 'package:ndk_flutter/ndk_flutter.dart';

final signer = NdkEventSigner(
  privateKey: myPrivkey,
  publicKey: myPubkey,
);

To wire it into NDK so it is used everywhere internally (accounts, bunkers, ephemeral signers, etc.), pass the matching factory to NdkConfig:

final ndk = Ndk(
  NdkConfig(
    eventVerifier: NdkEventVerifier(),
    cache: MemCacheManager(),
    eventSignerFactory: const NdkEventSignerFactory(),
  ),
);

This is the recommended setup for Flutter apps that target both mobile and web.

Disposing Accounts

Always dispose accounts when done to avoid memory leaks:

await account.dispose();

When to Use

Use the pending requests API when:

  • Building UI for external signers (bunkers, browser extensions, mobile apps)
  • Showing users what operations are waiting for approval
  • Allowing users to cancel stuck or unwanted requests
  • Implementing request timeouts in your application