Credential Presentation
WalletSdk.present runs the complete OpenID4VP presentation flow: match stored credentials against the verifier's presentation definition, walk the user through credential and disclosure selection, build the SD-JWT verifiable presentation with holder binding, and submit it to the verifier.
Requires a promptHandler in WalletSdkOptions. Presentation is always available after WalletSdk.initialize.
Flow
Two ways to run the flow
Option A — Session API (recommended)
presentation.start(...) resolves the request (or reuses prepared state from a deep link) and returns a PresentationSession; session.present() drives the prompts and submission:
final presentation = sdk.presentation!;
final session = await presentation.start(
StartPresentationRequest(
walletExternalId: apiWalletId,
presentationOfferUrl: prepared.resolvedPresentationRequest,
verifier: verifierParty,
prepared: prepared, // from HandleLinkPresentation — skips re-resolve
did: holderDid,
originalPresentationUrl: linkResult.originalPresentationUrl,
verifierMemberId: linkResult.resolvedVerifierMemberId,
),
);
final result = await session.present();
Option B — Direct present()
Build a PresentRequest yourself, typically from a PreparedPresentation:
final result = await sdk.present(
PresentRequest(
walletExternalId: apiWalletId,
resolvedPresentationRequest: prepared.resolvedPresentationRequest,
presentationDefinition: prepared.presentationDefinition,
verifier: verifierParty,
did: holderDid,
purpose: prepared.purpose,
responseUri: prepared.responseUri.isNotEmpty ? prepared.responseUri : null,
verifierTrustCheckResult: prepared.verifierTrustCheckResult,
),
);
Input — PresentRequest
| Field | Type | Required | Description |
|---|---|---|---|
walletExternalId | String | Yes | Wallet whose credentials are matched |
resolvedPresentationRequest | String | Yes | Backend-normalized presentation request |
presentationDefinition | Map<String, dynamic> | Yes | Parsed presentation definition JSON |
verifier | PromptParty | Yes | Verifier identity for prompts (name, branding, trust level) |
did | String? | No | Holder DID for binding |
purpose | String? | No | Verifier-stated purpose shown to the user |
recipientDisplayName | String? | No | Display name recorded in the sharing log |
responseUri | String? | No | Verifier callback for OpenID4VP rejection (error=access_denied) |
verifierMemberId | String? | No | Trust-network member id for verifier trust gating |
verifierTrustCheckResult | VerifierTrustCheckResult? | No | Precomputed trust verdict — skips a re-check |
preselectedCredentialId | String? | No | Skips selection prompts and shares all requested fields (automated flows) |
Output — PresentResult
switch (result) {
case PresentSuccess(:final sharedFieldLabels, :final recipientDisplayName):
showSuccess('Shared ${sharedFieldLabels.length} fields '
'with $recipientDisplayName');
case PresentDeclined():
break; // user rejected the request (confirmed via ConfirmRejectionPrompt)
case PresentAddCredentialNeeded():
openIssuanceFlow(); // user chose "add credential" — nothing eligible
case PresentFailure(:final message):
showError(message);
}
On success the SDK also records a sharing log entry.
Prompts raised during presentation
Your prompt handler must cover these (see Prompts for full type documentation):
| Prompt | When | Responses |
|---|---|---|
SelectCredentialPrompt | Candidate credentials exist | CredentialChosen, AddCredentialRequested, RejectRequested |
SelectDisclosuresPrompt | SD-JWT flow — choose fields to reveal | DisclosuresSelected, DisclosureSelectionCancelled |
ConfirmPresentationPrompt | Final consent (non-selective path) | PresentationConfirmed, PresentationConfirmationCancelled |
VerifierTrustPrompt | Verifier untrusted in warn mode | ProceedDespiteUntrustedVerifier, AbortUntrustedVerifier |
ConfirmRejectionPrompt | User chose to reject | RejectionConfirmed, RejectionCancelled |
RecoverableErrorPrompt | Retryable failure | RetryRequested, FlowAborted |
Matching and inspection utilities
sdk.presentation exposes lower-level helpers for building custom pre-flight UI:
final presentation = sdk.presentation!;
// Credentials that satisfy the definition.
final matches = await presentation.matchCredentials(
walletExternalId: apiWalletId,
presentationDefinition: definition,
);
// Per-credential evaluation with reasons (includes non-matching ones).
final candidates = await presentation.evaluateCandidates(
walletExternalId: apiWalletId,
presentationDefinition: definition,
);
for (final candidate in candidates) {
print('${candidate.credential.id}: satisfies=${candidate.satisfiesRequest} '
'missing=${candidate.missingRequiredFieldPaths}');
}
// Requested fields, purpose, and credential types.
final fields = presentation.extractFields(definition); // List<PresentationField>
final purpose = presentation.extractPurpose(definition); // String?
final types = presentation.extractRawCredentialTypes(definition);
// Whether the request uses SD-JWT selective disclosure.
final isSd = presentation.isSelectiveDisclosure(
presentationDefinition: definition,
);
// Read a displayable value for a requested field from a credential.
final json = presentation.credentialJsonForPresentation(credential);
final value = presentation.readFieldValue(
fieldKey: fields.first.fieldKey,
fields: fields,
credentialJson: json,
);
Additional helpers:
| Method | Purpose |
|---|---|
prepare(PreparePresentationInput) | Resolve a presentation request into a PreparedPresentation without starting prompts |
extractFieldsForDescriptorId(definition, id) | Fields for one input descriptor |
detectFieldConflicts(...) | Detect requested fields whose values differ across stored credentials |
fetchJarRequestObject(requestUri) | Fetch and parse a JAR Request Object JWT (RFC 9101) |
PreparedPresentation
Produced by prepare/start and carried on HandleLinkPresentation:
| Field | Description |
|---|---|
resolvedPresentationRequest | Normalized request string for present |
presentationDefinition | Parsed definition JSON |
verifierDisplayName | Resolved verifier name |
purpose | Verifier-stated purpose, when present |
isSelectiveDisclosure | Whether the SD-JWT flow applies |
responseUri | Verifier response endpoint |
verifierTrustCheckResult | Trust verdict computed during preparation |
prepared.toPresentRequest(...) converts it into a ready-to-submit PresentRequest.
Verifier trust behavior
With a TrustNetworkMode of warn, an untrusted verifier triggers a VerifierTrustPrompt so the user decides; in strict mode the flow aborts without a prompt. Pass verifierTrustCheckResult from a PreparedPresentation to avoid duplicate checks.