Wallet Workflow
This guide walks you through a basic wallet workflow using the Procivis One SDK, covering the essential operations: creating an identifier, receiving credentials, and presenting proofs.
Prerequisites
The SDK is installed and initialized.
Setup: Create an organization
Before your wallet can interact with issuers and verifiers, create an organization. The organization is the fundamental unit in Procivis One Core — all entities and actions belong to one organization. A typical wallet needs only one organization.
const organisationId = 'your-unique-org-id';
await core.createOrganisation({ id: organisationId });
Workflow A: Receive a credential
1. Handle the credential offer
When an issuer offers a credential (typically via QR code or deep link), handle the invitation to begin the issuance process.
const invitationUrl = 'openid-credential-offer://...'; // From QR code or deep link
const invitation = await core.handleInvitation({
url: invitationUrl,
organisationId: organisationId,
});
The response indicates the type of flow:
- If
authorizationCodeFlowUrlis present: User must authenticate (see Authorization Code Flow) - If
txCodeis present: User must provide a transaction code - Otherwise: Proceed directly to accept
2. Accept the credential
Accept the credential offer. The system automatically generates a dedicated key pair for this credential and stores it in your wallet.
const { interactionId } = invitation;
const credentialId = await core.holderAcceptCredential({
interactionId: interactionId,
// txCode: 'code' // Only if required by the issuer
});
Alternatively you can specify an identifier to use for the issuance. See Manual key and identifier management for guidance.
3. Retrieve the credential
Get the credential details so the user can review what they've received.
const credential = await core.getCredential(credentialId);
// Access credential information
console.log('Credential schema:', credential.schema.name);
console.log('Issuer:', credential.issuer?.name);
console.log('Claims:', credential.claims);
Workflow B: Present a proof
Now that your wallet holds a credential, it can respond to verification requests.
1. Handle the proof request
When a verifier requests proof (typically via QR code or deep link), handle the invitation to begin the presentation process.
const invitationUrl = 'openid4vp://...'; // From QR code or deep link
const invitation = await core.handleInvitation({
url: invitationUrl,
organisationId: organisationId,
});
const { interactionId, proofId } = invitation;
2. Get presentation definition V2
Retrieve the presentation definition to see what the verifier is requesting and which of your credentials match. This uses the V2 endpoint for OpenID4VP 1.0 with DCQL.
When to use V1 endpoints?
Use the V1 of "presentation definition" and "submit presentation" when responding to requests made with:
- Draft versions of OpenID4VP
- ISO mDL verification protocol
const presentationDef = await core.getPresentationDefinitionV2(proofId);
The response contains:
credentialQueries: Object mapping query IDs to credential requirementscredentialSets: Valid combinations of credentials that satisfy the request
3. Check credential revocation status
Before submitting, verify that applicable credentials haven't been revoked.
// Collect all applicable credential IDs
const credentialIds = new Set<string>();
Object.values(presentationDef.credentialQueries).forEach((query) => {
if ('applicableCredentials' in query) {
query.applicableCredentials.forEach((cred) => {
credentialIds.add(cred.id);
});
}
});
// Check revocation status
await core.checkRevocation(Array.from(credentialIds), true);
4. Build the submission
Create a submission object that maps query IDs to the credentials and claims you want to share.
const submission: Record<string, PresentationSubmitV2CredentialRequest[]> = {};
// For each query in the presentation definition
Object.entries(presentationDef.credentialQueries).forEach(([queryId, query]) => {
if ('applicableCredentials' in query) {
// Select the first applicable credential (or implement your own selection logic)
const credential = query.applicableCredentials[0];
if (credential) {
// Determine which optional claims the user wants to share
const userSelections: string[] = [];
credential.claims.forEach((claim) => {
// If the claim has userSelection: true, the user can choose to share it
if (claim.userSelection && !claim.required) {
// Add to userSelections if user chooses to share
// userSelections.push(claim.path);
}
});
submission[queryId] = [{
credentialId: credential.id,
userSelections: userSelections,
}];
}
}
});
5. Submit the proof
Submit the presentation to the verifier.
await core.holderSubmitProofV2(interactionId, submission);
Alternative: Reject the request
If the user decides not to share any credentials, reject the request.
await core.holderRejectProof(interactionId);
Understanding user selections
When building a submission, pay attention to the userSelection and
required flags on claims:
required: true: The claim must be included in the submissionrequired: false: The claim can optionally be included (if the credential format supports selective disclosure)userSelection: true: Show a toggle to let the user choose whether to share this optional claim
Sharing an optional claim may automatically include related claims due to the nested structure. See Understanding claim flags for details.
Advanced: Manual Key and Identifier Management
By default, the system automatically generates a dedicated key pair for each credential issuance. However, you can also manually create and manage keys and identifiers if your use case requires it.
When you specify an identifierId and keyId, the system uses those
instead of auto-generating new ones. This means multiple credentials can
share the same identifier, which carries privacy risk through linkability.
Create a key and identifier
1. Generate a key
Generate a key pair for cryptographic operations:
const keyId = await core.generateKey({
organisationId: organisationId,
keyType: 'ECDSA',
name: 'wallet-key',
storageType: 'SECURE_ELEMENT',
keyParams: {},
storageParams: {},
});
2. Create an identifier
Create either a key-identifier or a DID-identifier. For example, choose a DID method and assign the key for all verification methods:
const identifierId = await core.createIdentifier({
organisationId: organisationId,
name: 'wallet-identifier',
did: {
name: 'wallet-did',
method: 'KEY',
keys: {
authentication: [keyId],
assertionMethod: [keyId],
keyAgreement: [keyId],
capabilityInvocation: [keyId],
capabilityDelegation: [keyId],
},
params: {},
},
});
Use with credential issuance
When accepting a credential, specify your pre-created identifier:
const credentialId = await core.holderAcceptCredential({
interactionId: interactionId,
identifierId: identifierId,
keyId: keyId, // Only used for DIDs with multiple authentication keys
});
Next steps
This basic workflow covers the fundamental operations. For more advanced scenarios, see:
- Receiving a credential - Complete guide including Authorization Code Flow and transaction codes
- Submitting a presentation - Detailed explanation of presentation definition structures and claim selection
- Configuration reference - Configure protocols, key types, and storage options