@the-hashgraph-group-ag/identity-client-sdk-js-holder
v0.6.0
Published
Holder Client SDK for OIDC4VCI credential issuance and management
Readme
Holder Client SDK
Client-side SDK for orchestrating credential issuance and presentation using OID4VCI, OID4VP, DID management, and self-issuance. You provide handlers for each domain; the SDK exposes a unified API via sdk.issuance, sdk.presentation, sdk.did, and sdk.selfIssuance.
Purpose
The Holder Client SDK is a client-side orchestrator for credential issuance (OID4VCI), presentation (OID4VP), decentralized identity (DID), and self-issued credentials. It is handler-based: you register one or more implementations of IOID4VCIHandler, IOID4VPHandler, IDIDHandler, and optionally ISelfIssuanceHandler. The first registered handler of each type becomes active; you can switch at runtime with useOID4VCIHandler, useOID4VPHandler, useDIDHandler, or useSelfIssuanceHandler.
Features
OID4VCI (Credential Issuance)
- Resolve credential offer URIs
- Get issuer and authorization server metadata
- Request access tokens and credentials
- Proof of possession (PoP)
- Deferred credential issuance and polling
OID4VP (Credential Presentation)
- Validate presentation requests and build verifiable presentations
- Generate signed VP tokens (DataIntegrityProof, Ed25519Signature2020)
- Match credentials to Presentation Definitions and create presentation submissions
- DCQL-based and Presentation Definition–based flows
DID Management
- Resolve, create, and deactivate DIDs via a pluggable handler
Self-Issuance
- Compose and validate self-issued credentials via
ISelfIssuanceHandler
DCQL
- Parse and validate DCQL requests; match queries against credentials (see
packages/holder/src/dcql/README.md).
Installation
pnpm add @the-hashgraph-group-ag/identity-client-sdk-js-holderGetting Started
You must register at least one handler per namespace you use (issuance, presentation, did, selfIssuance). The SDK does not ship default implementations; you wire your own (e.g. from @the-hashgraph-group-ag/identity-client-sdk-js-holder handlers or custom).
import { HolderSDK } from '@the-hashgraph-group-ag/identity-client-sdk-js-holder';
import { OID4VCIIssuanceHandler } from '@the-hashgraph-group-ag/identity-client-sdk-js-holder';
import { OID4VPHandler } from '@the-hashgraph-group-ag/identity-client-sdk-js-holder';
import { DIDHandler } from '@the-hashgraph-group-ag/identity-client-sdk-js-holder';
const sdk = new HolderSDK({
OID4VCIHandlers: [
new OID4VCIIssuanceHandler({
/* config */
}),
],
OID4VPHandlers: [
new OID4VPHandler({
/* config */
}),
],
DIDHandlers: [new DIDHandler({ didApiUrl: 'https://...' })],
});
// Issuance: resolve offer, then request credential
const resolved = await sdk.issuance.resolveCredentialOffer({ uri: credentialOfferUri });
const credential = await sdk.issuance.requestCredential({
accessToken: '...',
credentialIdentifier: resolved.credentialIdentifier,
credentialDataSupplierInput: { credentialSubject: { name: 'Jane' } },
});
// Presentation: build VP from request
const vp = await sdk.presentation.buildPresentation(request, getProof);
// DID: resolve or create
const doc = await sdk.did.resolve({ did: 'did:hedera:testnet:...' });
const { did } = await sdk.did.create({ ...params, sign });API Overview
HolderSDK
Constructor: new HolderSDK(options?: Partial<HolderSDKOptions>)
options.OID4VCIHandlers– optional array ofIOID4VCIHandleroptions.OID4VPHandlers– optional array ofIOID4VPHandleroptions.DIDHandlers– optional array ofIDIDHandleroptions.selfIssuanceHandlers– optional array ofISelfIssuanceHandler
Getters (throw HandlerNotSetError if no handler is set):
sdk.issuance– currentIOID4VCIHandlersdk.presentation– currentIOID4VPHandlersdk.did– currentIDIDHandlersdk.selfIssuance– currentISelfIssuanceHandler
Registration: registerHandler(handler), registerOID4VCIHandler(handler), registerOID4VPHandler(handler), registerDIDHandler(handler), registerSelfIssuanceHandler(handler).
Switch active handler: useOID4VCIHandler(handler), useOID4VPHandler(handler), useDIDHandler(handler), useSelfIssuanceHandler(handler).
List handlers: getOID4VCIHandlers(), getOID4VPHandlers(), getDIDHandlers(), getSelfIssuanceHandlers().
Handler interfaces
- IOID4VCIHandler –
getAuthorizationServerMetadata,getCredentialIssuerMetadata,resolveCredentialOffer,requestCredential,requestDeferredCredential,pollDeferredCredential,createProofOfPossession,requestAccessToken,requestCNonce. - IOID4VPHandler –
validateRequest,buildPresentation,getAuthorizationServerMetadata,generateVPToken,formatAsVPToken,formatMultipleVPTokens,matchPresentationDefinition,createPresentationSubmission,createSubmissionFromMatchResult,formatVPTokenForPresentationDefinition,verifyPresentationSubmission. - IDIDHandler –
resolve,create,deactivate. - ISelfIssuanceHandler –
compose,validate.
OID4VCI flow (example)
// Construct the SDK without a fixed issuer URL when you want to be host-agnostic.
const sdk = new HolderSDK({
OID4VCIHandlers: [new OID4VCIHandler({ id: 'default' })],
// ...other handlers...
});
// `resolveCredentialOffer` discovers the issuer host from the offer and
// exposes it as `issuerUrl` on the returned object.
const resolved = await sdk.issuance.resolveCredentialOffer({ uri: credentialOfferUri });
// `requestAccessToken` is optional on `IOID4VCIHandler`; the bundled
// `OID4VCIHandler` always implements it, so we assert it here.
const tokenResponse = await sdk.issuance.requestAccessToken!({
preAuthorizedCode: resolved.preAuthorizedCode,
grantType: 'urn:ietf:params:oauth:grant-type:pre-authorized_code',
issuerUrl: resolved.issuerUrl,
});
const credential = await sdk.issuance.requestCredential({
accessToken: tokenResponse.access_token,
credentialIdentifier: resolved.credentialIdentifier,
credentialDataSupplierInput: { credentialSubject: { name: 'Jane', degree: 'BSc' } },
issuerUrl: resolved.issuerUrl,
});You can also pin a default issuer host at construction time (new OID4VCIHandler({ id, url }))
and skip issuerUrl per call — the constructor URL becomes the fallback. If neither is
provided, calls throw ValidationError.
OID4VP flow (agnostic verifier host)
// Construct the SDK without a fixed verifier URL when you want to be host-agnostic.
const sdk = new HolderSDK({
OID4VPHandlers: [new OID4VPHandler({ id: 'default' })],
// ...other handlers...
});
// `parseRequestUri` extracts the verifier host once, at QR-parse time.
const { request_uri, request_uri_method, verifierUrl } = sdk.presentation.parseRequestUri(qrUri);
const jwt = await sdk.presentation.getVpRequestJwt(request_uri, request_uri_method);
const request = sdk.presentation.decodeJwtPayload(jwt);
sdk.presentation.validateRequest(request);
// ...build VP token (unchanged flow)...
const result = await sdk.presentation.verifyPresentationSubmission({
state: request.state,
vp_token: vpTokenString,
presentation_submission: submissionString,
verifierUrl,
});You can also pin a default verifier host at construction time
(new OID4VPHandler({ id, url })) and skip verifierUrl per call — the
constructor URL becomes the fallback. If neither is provided, calls throw
ValidationError.
OID4VP: generate VP token
const vp = await sdk.presentation.generateVPToken({
holderDid: 'did:hedera:testnet:...',
privateKey: privateKeyHexOrPemOrUint8Array,
credentials: [credential],
proofPurpose: 'authentication',
challenge: 'nonce-from-verifier',
verificationMethodKey: 'did-root-key',
});Supported private key formats: hex (96 or 64 chars), PEM PKCS#8, Uint8Array (32- or 64-byte).
DCQL
See DCQL README for parsing, validation, and matching of DCQL requests.
Error handling
Errors are thrown from the core package (@the-hashgraph-group-ag/identity-client-sdk-js-core):
- HandlerNotSetError – thrown when you access
sdk.issuance,sdk.presentation,sdk.did, orsdk.selfIssuanceand no handler has been registered for that namespace. Register at least one handler (e.g. in the constructor or viaregisterOID4VCIHandleretc.) before using the getter. - HttpError – thrown by handlers when an HTTP request returns 4xx/5xx (e.g.
responseInterceptors: [throwOnHttpError]). Hasstatus,data,headers. - ValidationError – thrown when input validation fails (e.g. invalid DID, missing required field). Has
messageand optionalfield.
Use instanceof to handle them: if (err instanceof HandlerNotSetError) { ... }.
Development
Testing
pnpm test:all
pnpm test:utils
pnpm test:sdkBuilding
pnpm build