QR scanner
The add-wallet flow accepts NWC connection URIs, Lightning and BIP353 addresses, BOLT12 offers, BIP321 URIs, and HTTPS Cashu mint URLs from one QR scanner.
ndk_flutter does not bundle a camera/scanner dependency. Instead you provide your own
scanner, so you stay in control of the camera plugin, runtime permissions, and the UI. When you
don't provide one, users can still paste a value manually. The shared wallet-input screen is
the sole add-wallet entry point; the previous intermediate Add Wallet screen no longer exists.
Your scanner remains responsible for camera-permission rationale and denial handling.
Provide a scanner
A scanner is a callback that opens your scanning UI and returns a scanned value, a launched
wallet connection, or null if the user cancels:
typedef WalletInputScanner = Future<WalletInputScanResult?> Function(
BuildContext context,
WalletInputScannerConfiguration configuration,
);
Pass it to the widget (or dialog) that needs it. For wallets, that's NWallets:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(context.l10n.tabWallets)),
Pass the scanner to the unified dialog directly when you do not use NWallets:
showAddWalletTypeDialog(
context,
ndkFlutter,
walletInputScanner: scanWalletInput,
);
Show configuration.supportedInputDescription in the scanner so users know which QR codes
work. Render configuration.connectionOptions beside camera and paste controls to expose the
standard installed-wallet chooser, Alby Go, custom providers, and web-wallet integrations
directly from the scanner. Listen to configuration.connectionState while an external flow is
active. Keep the scanner open through awaitingReturn, connecting, and failed; use
retryPendingConnection and cancelPendingConnection for retry and back actions. Return
WalletInputScanResult.connectionStarted() after the state reaches connected.
The widget classifies and validates scanned values. Return scanned values as
WalletInputScanResult.value(rawText). For pasted or
typed values, pass manuallyEntered: true so confirmation allows editing.
Legacy nwcUriScanner and bolt12InputScanner callbacks remain available on their
type-specific dialogs.
Wallet-assisted NWC connections
On Android and iOS, the unified flow includes the standard NWC wallet chooser and Alby Go.
Add installed or web wallet integrations with nwcConnectionOptions:
Alby Go uses the branded nostr+walletauth+alby:// NWC-08 flow. If direct app launch
fails because Alby Go is not installed, the same authorization request is shown as a QR
code. Web and desktop platforms show this QR directly for scanning with Alby Go on a phone.
The client keeps its generated secret locally, verifies any returned state tag,
discovers the wallet-service public key from its kind 13194 info event, and honors any
wallet-service relay tag.
Wallet-auth requests include state. For compatibility with deployed Alby implementations,
responses and info events may omit it; a present but mismatched state is always rejected.
Separate-phone QR requests omit return_to because no same-device callback is possible.
Coinos uses its fixed service public key and wss://relay.coinos.io. Because its info event
is not addressed to the generated client key, the client subscribes to info events from that
fixed service key and keeps validating later events until the approved connection works.
No manual confirmation is required.
NWallets(
ndkFlutter: ndkFlutter,
walletInputScanner: scanWalletInput,
nwcConnectionOptions: [
NwcConnectionOption(
label: 'My web wallet',
subtitle: 'Approve the connection in your browser',
connect: (context, ndkFlutter, coordinator) {
const callback = 'myapp://nwc';
return coordinator.connectWithUri(
context,
launchUri: Uri.parse(
'https://wallet.example/connect?callback=myapp%3A%2F%2Fnwc',
),
callback: callback,
walletName: 'My web wallet',
);
},
),
],
)
Client-key web wallets can receive configurable app metadata and a freshly generated public key. Default Alby Cloud, Alby Go, and Coinos connections keep a live discovery dialog open until a usable info event arrives or the user cancels:
NwcConnectionOption(
id: 'coinos',
label: 'Coinos',
connect: (context, ndkFlutter, coordinator) {
return coordinator.connectWebWalletAuth(
context,
authorizationEndpoint: Uri.parse('https://coinos.io/apps/new'),
appName: 'My app',
discoveryRelay: 'wss://relay.coinos.io',
callback: 'myapp://nwc',
walletName: 'Coinos',
walletServicePubkey:
'ba80990666ef0b6f4ba5059347beb13242921e54669e680064ca755256a1e3a6',
allowUntaggedInfoEvent: true,
);
},
)
Call NWalletsState.resumePendingWalletAuth() from mobile resumed lifecycle callbacks.
Legacy providers without client-tagged discovery events are revalidated every five
seconds while their connection screen remains open.
Use NWC-07 callback flow to launch nostrnwc://connect. Android resolves it using
normal system intent handling, including user defaults and multiple compatible apps:
return coordinator.connectInstalledWallet(
context,
config: const AlbyGoConnectConfig(
appName: 'My app',
appIconUrl: 'https://example.com/icon.png',
callback: 'myapp://nwc',
),
);
Forward callback URLs to NWalletsState.onProtocolUrlReceived. Wallet-auth callback results
return relay_url and wallet_pubkey; when state is present, it must match. Legacy providers
may return a nostr+walletconnect:// value in a callback query parameter.
Example: scanning with mobile_scanner
The sample app implements the callback with
mobile_scanner. Add it to your app (not to
ndk_flutter):
flutter pub add mobile_scanner
The callback just opens a dialog that wraps the camera view and pops the first decoded value:
/// Camera adapter only. Wallet input UI and navigation belong to ndk_flutter.
Widget buildWalletQrScanner(
BuildContext context,
ValueChanged<String> onScan,
ValueChanged<Object> onError,
) {
packages/sample-app/lib/nwc_qr_scanner.dart only adapts the host camera implementation.
Wallet choices, paste/manual input, errors, and layout live in ndk_flutter.