Skip to main content

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.

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​

ModelUsed inKey fields
PromptPartyIssuer/verifier identitydisplayName, logoUrl, website, privacyPolicyUrl, termsOfServiceUrl, trustLevel, trustReason, memberId
PromptTrustLevelTrust statustrusted, untrusted, unknown
PromptClaimClaims in previews and disclosure selectionpath, label, value, isRequired, selectedByDefault, isAvailable
PromptCredentialPreviewOffered credential (issuance)credentialType, displayName, claims, issuer, logoUrl, backgroundImageUrl
PromptCredentialOptionCandidate 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.
  • PromptTimeoutException is 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:

ConstantValue
credentialOfferAcceptancecredential_offer.acceptance
presentationSelectCredentialpresentation.select_credential
presentationSelectDisclosurespresentation.select_disclosures
presentationConfirmpresentation.confirm
presentationVerifierTrustpresentation.verifier_trust
presentationConfirmRejectionpresentation.confirm_rejection
recoverableErrorshared.recoverable_error