Signers
Signers are responsible for cryptographic operations: signing events, encrypting and decrypting messages. NDK supports multiple signer types with a unified API.
Signer Types
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}');
}
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