Prompts — User Decisions
The SDK owns flow orchestration; your app owns all UI. Whenever a flow reaches a user decision — consent to store a credential, pick a credential to present, choose fields to disclose — the SDK emits a typed WalletPrompt and awaits a typed WalletPromptResponse from your WalletPromptHandler.
This contract keeps the SDK out of your widget tree while guaranteeing every decision point is explicit and type-checked.
The contract
abstract interface class WalletPromptHandler {
/// Render UI for [prompt] and return the user's typed decision.
/// Throw PromptCancelledException when the user dismisses without deciding.
Future<R> requestPrompt<R extends WalletPromptResponse>(
WalletPrompt<R> prompt,
);
}
Each prompt type is generically bound to exactly one response family, so requestPrompt returns a statically-typed future. Every prompt carries a stable id (from WalletPromptIds) for logging and analytics.
Option A — WalletPromptRegistry (recommended)
Register one typed callback per prompt type; the registry dispatches incoming prompts without manual casts:
final registry = WalletPromptRegistry()
..register<CredentialAcceptancePrompt, CredentialAcceptanceResult>((p) async {
final accepted = await showConsentSheet(p.credential);
return accepted
? const CredentialAcceptanceAccepted()
: const CredentialAcceptanceDeclined();
})
..register<SelectCredentialPrompt, SelectCredentialResult>((p) async {
final id = await showCredentialPicker(p.options);
return id != null ? CredentialChosen(id) : const RejectRequested();
});
WalletSdkOptions(promptHandler: registry, /* ... */);
An unregistered prompt type throws a StateError at dispatch time — register a callback for every prompt your enabled flows can raise.
Option B — implement the interface
Useful when routing through a navigator with pattern matching:
class AppPromptHandler implements WalletPromptHandler {
AppPromptHandler(this.navigatorKey);
final GlobalKey<NavigatorState> navigatorKey;
Future<R> requestPrompt<R extends WalletPromptResponse>(
WalletPrompt<R> prompt,
) async {
final context = navigatorKey.currentContext!;
return switch (prompt) {
CredentialAcceptancePrompt p => showDialog<R>(
context: context,
barrierDismissible: false,
builder: (_) => CredentialAcceptanceDialog(prompt: p),
).then((v) => v ?? const CredentialAcceptanceDeclined() as R),
SelectCredentialPrompt p => showModalBottomSheet<R>(
context: context,
builder: (_) => SelectCredentialSheet(prompt: p),
).then((v) => v ?? const RejectRequested() as R),
// ... one arm per prompt type
_ => throw UnsupportedError('Unknown prompt: ${prompt.id}'),
};
}
}
Prompt catalog
Issuance
CredentialAcceptancePrompt → CredentialAcceptanceResult
Consent to store an offered credential. Carries the credential preview (claims, issuer with trust status), the full issued payload, resolution metadata, and isDuplicate / isIssuerTrusted flags.
Responses: CredentialAcceptanceAccepted, CredentialAcceptanceDeclined.
Presentation
SelectCredentialPrompt → SelectCredentialResult
Choose which stored credential to present. Each PromptCredentialOption carries precomputed eligibility — render ineligible options disabled without recomputing matching:
for (final option in prompt.options) {
ListTile(
enabled: option.isSelectable, // satisfiesRequest && acceptableByVerifier
title: Text(option.displayName),
subtitle: Text(!option.satisfiesRequest
? 'Missing: ${option.missingRequiredClaimPaths.join(', ')}'
: (!option.acceptableByVerifier
? (option.verifierAcceptanceReason ?? 'Not accepted by verifier')
: 'Eligible')),
onTap: option.isSelectable
? () => complete(CredentialChosen(option.credentialId))
: null,
);
}
Responses: CredentialChosen(credentialId), AddCredentialRequested, RejectRequested.
SelectDisclosuresPrompt → SelectDisclosuresResult
SD-JWT selective disclosure — choose which optional claims to reveal. Required claims (PromptClaim.isRequired) must not be deselectable; optional claims start at selectedByDefault. The confirm action doubles as the share confirmation in the SD-JWT flow.
Responses: DisclosuresSelected(disclosedClaimPaths), DisclosureSelectionCancelled (returns to credential selection).
ConfirmPresentationPrompt → ConfirmPresentationResult
Final consent listing the exact claims to be shared — primarily for the non-selective (JWT-VC) path.
Responses: PresentationConfirmed, PresentationConfirmationCancelled.
VerifierTrustPrompt → VerifierTrustResult
The verifier failed the trust check in warn mode. prompt.verifier.trustLevel is untrusted or unknown, with a diagnostic trustReason.
Responses: ProceedDespiteUntrustedVerifier, AbortUntrustedVerifier.
ConfirmRejectionPrompt → ConfirmRejectionResult
Confirm the user's intent to reject the presentation request.
Responses: RejectionConfirmed, RejectionCancelled.
Shared
RecoverableErrorPrompt → RecoverableErrorResult
A recoverable failure occurred (offer resolve failure, submit failure, network error). Carries the flow (stage), a machine-oriented code to map to localized copy, an optional diagnostic message, and retryable. When retryable is false, offer only an exit action.
Responses: RetryRequested, FlowAborted.
Shared display models
| Model | Used in | Key fields |
|---|---|---|
PromptParty | Issuer/verifier identity | displayName, logoUrl, website, privacyPolicyUrl, termsOfServiceUrl, trustLevel, trustReason, memberId |
PromptTrustLevel | Trust status | trusted, untrusted, unknown |
PromptClaim | Claims in previews and disclosure selection | path, label, value, isRequired, selectedByDefault, isAvailable |
PromptCredentialPreview | Offered credential (issuance) | credentialType, displayName, claims, issuer, logoUrl, backgroundImageUrl |
PromptCredentialOption | Candidate credential (presentation) | credentialId, displayName, issuer, satisfiesRequest, missingRequiredClaimPaths, acceptableByVerifier, claims, isSelectable |
The SDK resolves display values (names, logos, trust) from the trust network and credential metadata; it never localizes copy — labels and reasons are identifiers or provider-supplied values your app may replace.
Cancellation and timeouts
- Throw
PromptCancelledException(promptId, reason)when the user dismisses a prompt without deciding (back button, sheet swiped away). The SDK unwinds the flow cleanly. This is distinct from an explicit decline response, which is a completed decision. PromptTimeoutExceptionis surfaced when a prompt dispatcher timeout elapses without a response — treated like a cancellation for flow control, but distinguishable in diagnostics.
Prompt ids
Stable identifiers for logging, exposed on WalletPromptIds:
| Constant | Value |
|---|---|
credentialOfferAcceptance | credential_offer.acceptance |
presentationSelectCredential | presentation.select_credential |
presentationSelectDisclosures | presentation.select_disclosures |
presentationConfirm | presentation.confirm |
presentationVerifierTrust | presentation.verifier_trust |
presentationConfirmRejection | presentation.confirm_rejection |
recoverableError | shared.recoverable_error |