# Migrating from Credo 0.5.x to 0.6.x

# Migrating from Credo 0.5.x to 0.6.x

This document describes everything you need to know for updating Credo 0.5.x to 0.6.x. If you're not aware of how updating in Credo works make sure to first read the guide on [Updating Credo](https://credo.js.org/guides/next/updating.md).

First of all, update your dependencies to the 0.6.x versions. This will also update the needed peer dependencies. **Extension packages are not updated with this command**. You need to update these manually, and make sure they're up to date with the latest version of Credo.

Credo 0.6.0 is a major release with a large number of breaking changes. The most notable ones are:

- **ESM-only**: Credo is now a pure ESM package. CommonJS `require()` is only supported in Node.JS 20.19 and above. But we recommend updating your project to ESM.
- **DIDComm extracted from core**: All DIDComm-related functionality has been moved to the new `@credo-ts/didcomm` package.
- **New KMS API**: The wallet API has been completely rewritten. `agent.wallet` is replaced by `agent.kms`.
- **Wallet config moved**: `walletConfig` has been removed from the main agent config and moved to the Askar module's `store` property.
- **Askar dependency renamed**: `@hyperledger/aries-askar-*` packages have been replaced by `@openwallet-foundation/askar-*`.
- **`label` and `connectionImageUrl` removed** from agent configuration.
- **`did:peer:4` is now the default** for DID Exchange requests.
- **Node 18 deprecated**: Node 20 and 22 are now the supported versions.

Follow the mentioned steps in this document carefully to make the upgrade as smooth as possible.

:::caution

The migration guide only covers how to migrate from 0.5.x to 0.6.x while keeping the same behavior and dependencies. There are many new features in 0.6.0 (Drizzle ORM storage, batch credentials, DCQL, W3C VCDM 2.0, etc.) that are not covered here. Refer to the [release notes](https://github.com/openwallet-foundation/credo-ts/releases/tag/v0.6.0) and the individual package changelogs for more information on new features.

:::

:::caution

The automatic backup functionality has been removed from Credo 0.6.0. You are expected to create a backup yourself before performing any updates. For Askar you can export a store using the Askar API, or you can directly create a backup of your Postgres database.

:::

First install the updated dependencies. For all dependencies you have in `package.json`, update them to the following versions, for your platform. If you don't use a package, you don't have to add it here.

In the upgrade from 0.5 to 0.6, there is a new `@credo-ts/didcomm` package that must be added if your application uses DIDComm. Also, the **Askar native bindings have been moved from `@hyperledger` to `@openwallet-foundation`** scope, so `@hyperledger/aries-askar-*` should be replaced by `@openwallet-foundation/askar-*`.

**React Native**

- `"@credo-ts/core": "^0.6.0"`
- `"@credo-ts/didcomm": "^0.6.0"` _(new, required if using DIDComm)_
- `"@credo-ts/askar": "^0.6.0"`
- `"@credo-ts/anoncreds": "^0.6.0"`
- `"@credo-ts/openid4vc": "^0.6.0"`
- `"@credo-ts/react-native": "^0.6.0"`
- `"@credo-ts/tenants": "^0.6.0"`
- `"@credo-ts/cheqd": "^0.6.0"`
- `"@credo-ts/indy-vdr": "^0.6.0"`
- `"@openwallet-foundation/askar-react-native": "^0.4.0"` _(replaces `@hyperledger/aries-askar-react-native`)_
- `"@hyperledger/anoncreds-react-native": "^0.2.4"`
- `"@hyperledger/indy-vdr-react-native": "^0.2.2"`

**Node**

- `"@credo-ts/core": "^0.6.0"`
- `"@credo-ts/didcomm": "^0.6.0"` _(new, required if using DIDComm)_
- `"@credo-ts/askar": "^0.6.0"`
- `"@credo-ts/anoncreds": "^0.6.0"`
- `"@credo-ts/openid4vc": "^0.6.0"`
- `"@credo-ts/node": "^0.6.0"`
- `"@credo-ts/tenants": "^0.6.0"`
- `"@credo-ts/cheqd": "^0.6.0"`
- `"@credo-ts/indy-vdr": "^0.6.0"`
- `"@openwallet-foundation/askar-nodejs": "^0.4.0"` _(replaces `@hyperledger/aries-askar-nodejs`)_
- `"@hyperledger/anoncreds-nodejs": "^0.2.4"`
- `"@hyperledger/indy-vdr-nodejs": "^0.2.2"`

## Breaking Code Changes

This section will list all breaking changes made to the public API of Credo between version 0.5.x and 0.6.x.

:::info

If you have custom modules take into account there could be a lot more breaking changes that aren't documented here. We try to make sure that the biggest breaking changes to the internal API are also documented here, but it is possible some breaking changes are not documented here (feel free to open a pull request).

:::

### ESM-only package

Credo 0.6.0 is now a [pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). It cannot be `require()`'d from CommonJS. Since Node 20.19 you may be able to require() ESM modules. However, we strongly recommend moving to ESM instead.

If your project is a **Node.js** application, you will need to migrate it to ESM or use a Node.js version that supports requiring ESM modules (Node.js 22+). The recommended approach is to migrate your project to ESM:

1. Add `"type": "module"` to your `package.json`.
2. Replace all `require()` / `module.exports` with `import` / `export`.
3. Use only full relative file paths for imports: `import x from './index.js'` instead of `import x from '.'`.
4. Use the `node:` protocol for Node.js built-in imports (e.g. `import fs from 'node:fs'`).
5. If using TypeScript, add `"module": "node16"` and `"moduleResolution": "node16"` to your `tsconfig.json`. You must use a `.js` extension in relative imports even though you're importing `.ts` files.
6. Replace usages of `__dirname` and `__filename` with `import.meta.dirname` and `import.meta.filename` (Node.js 20.11+), or use `import.meta.url` with `fileURLToPath`.

If your project is a **React Native** application, this change should not impact you since React Native uses its own bundler (Metro) which handles ESM syntax.

:::tip

For a comprehensive guide on migrating a CommonJS project to ESM, see [Pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c) by Sindre Sorhus.

:::

### Node 18 deprecated

Starting from Credo 0.6, Node 18 has been deprecated. The supported versions are Node 20 and 22. We aim to keep support for all LTS releases that are either Current, Active, or in Maintenance.

See the [Release Schedule](https://nodejs.org/en/about/previous-releases#release-schedule) on the Node.js website for information on past and upcoming Node.js releases.

### DIDComm extracted from Core

One of the most significant changes in Credo 0.6.0 is that all DIDComm-related functionality has been extracted from `@credo-ts/core` into a new `@credo-ts/didcomm` package. This means:

- If your application **does not use DIDComm** at all, you simply need to remove DIDComm-related settings from your Agent configuration. No `@credo-ts/didcomm` dependency is needed.
- If your application **does use DIDComm**, you need to add `@credo-ts/didcomm` to your dependencies and update your imports and agent setup.

The following subsections cover all the DIDComm-related breaking changes.

#### Agent setup with DIDComm

Previously, DIDComm modules (connections, credentials, proofs, mediation, etc.) were automatically included in the agent. Now you must explicitly include them using the `getDefaultDidcommModules()` helper, and settings like `endpoints` are passed through DIDComm module configuration instead of the main agent config.

**0.5.x**

```ts
import { Agent } from '@credo-ts/core'
import { agentDependencies } from '@credo-ts/node'
import { HttpInboundTransport, HttpOutboundTransport } from '@credo-ts/node'

const agent = new Agent({
  config: {
    label: 'My Agent',
    endpoints: ['https://myendpoint.com'],
    walletConfig: {
      id: 'my-wallet',
      key: 'my-wallet-key',
    },
  },
  dependencies: agentDependencies,
  modules: {
    /* ... other modules */
  },
})

agent.registerInboundTransport(new HttpInboundTransport({ port: 3000 }))
agent.registerOutboundTransport(new HttpOutboundTransport())
```

**0.6.x**

```ts
import { Agent } from '@credo-ts/core'
import { agentDependencies } from '@credo-ts/node'
import {
  getDefaultDidcommModules,
  DidCommHttpInboundTransport,
  DidCommHttpOutboundTransport,
} from '@credo-ts/didcomm'

const agent = new Agent({
  config: {
    // label and endpoints have been removed from config
  },
  dependencies: agentDependencies,
  modules: {
    ...getDefaultDidcommModules({
      endpoints: ['https://myendpoint.com'],
    }),
    /* ... other modules */
  },
})

agent.didcomm.registerInboundTransport(new DidCommHttpInboundTransport({ port: 3000 }))
agent.didcomm.registerOutboundTransport(new DidCommHttpOutboundTransport())
```

#### DIDComm API access pattern

All DIDComm-related module APIs are now scoped under `agent.didcomm` instead of being accessed directly on `agent`.

**0.5.x**

```ts
const defaultMediator = await agent.mediationRecipient.findDefaultMediator()
const proofRecord = await agent.proofs.getById(proofRecordId)
const credentialRecord = await agent.credentials.getById(credentialRecordId)
const oobRecord = await agent.oob.createInvitation()
const connections = await agent.connections.getAll()
```

**0.6.x**

```ts
const defaultMediator = await agent.didcomm.mediationRecipient.findDefaultMediator()
const proofRecord = await agent.didcomm.proofs.getById(proofRecordId)
const credentialRecord = await agent.didcomm.credentials.getById(credentialRecordId)
const oobRecord = await agent.didcomm.oob.createInvitation()
const connections = await agent.didcomm.connections.getAll()
```

#### DIDComm class prefix changes

Most DIDComm-related classes, events and services now use the `DidComm` prefix in their naming. Imports must be updated from `@credo-ts/core` to `@credo-ts/didcomm`.

**0.5.x**

```ts
import {
  AgentMessage,
  ConnectionsModule,
  MediatorPickupStrategy,
  HttpInboundTransport,
  HttpOutboundTransport,
  WsInboundTransport,
  WsOutboundTransport,
  ConnectionEventTypes,
  CredentialEventTypes,
  ProofEventTypes,
} from '@credo-ts/core'
```

**0.6.x**

```ts
import {
  DidCommMessage,
  DidCommConnectionsModule,
  DidCommMediatorPickupStrategy,
  DidCommHttpInboundTransport,
  DidCommHttpOutboundTransport,
  DidCommWsInboundTransport,
  DidCommWsOutboundTransport,
  DidCommConnectionEventTypes,
  DidCommCredentialEventTypes,
  DidCommProofEventTypes,
} from '@credo-ts/didcomm'
```

DIDComm-related event type strings have also changed to distinguish them from events triggered by other protocols.

#### Credentials / Proofs API normalization

The DIDComm credentials and proofs APIs have been updated for consistency. In particular, `credentialExchangeRecord` is now used instead of `credentialRecord` and `proofExchangeRecord` instead of `proofRecord` in method parameters.

**0.5.x**

```ts
const requestedCredentials = await agent.proofs.selectCredentialsForRequest({
  proofRecordId: proofRecord.id,
})

await agent.proofs.acceptRequest({
  proofRecordId: proofRecord.id,
  proofFormats: { anoncreds: requestedCredentials.proofFormats.anoncreds },
})
```

**0.6.x**

```ts
const requestedCredentials = await agent.didcomm.proofs.selectCredentialsForRequest({
  proofExchangeRecordId: proofRecord.id,
})

await agent.didcomm.proofs.acceptRequest({
  proofExchangeRecordId: proofRecord.id,
  proofFormats: { anoncreds: requestedCredentials.proofFormats.anoncreds },
})
```

#### Dropped `label` and `connectionImageUrl` from agent configuration

The `label` and `connectionImageUrl` fields have been removed from the main Agent configuration. They are not present as a general DIDComm configuration parameter either. If you need to use them, you can specify them per-call in DIDComm connection establishment methods:

```ts
const outOfBandRecord = await agent.didcomm.oob.createInvitation({
  label: 'My Agent',
  imageUrl: 'https://myhost/myimage.png',
})

await agent.didcomm.oob.receiveInvitation(invitation, {
  label: 'My Agent',
  imageUrl: 'https://myhost/myimage.png',
})
```

:::tip

If you need to persist these values and your only persistent storage is the agent's wallet, you can use Generic Records to store arbitrary data.

:::

#### DID Exchange defaults changed to `did:peer:4`

Up to Credo 0.5.x, the default `did:peer` method used for DID Exchange was `did:peer:1`. In 0.6.0, the default is now `did:peer:4`. If you are interacting with older agents that don't support this method, you can override this setting in the DIDComm connections module configuration:

```ts
import { DidCommConnectionsModule, PeerDidNumAlgo } from '@credo-ts/didcomm'

const agent = new Agent({
  modules: {
    ...getDefaultDidcommModules({ endpoints: ['https://myendpoint.com'] }),
    connections: new DidCommConnectionsModule({
      peerNumAlgoForDidExchangeRequests: PeerDidNumAlgo.GenesisDoc,
    }),
  },
})
```

#### Inbound/Outbound transports configuration

Transports are now registered through the `agent.didcomm` API or as part of the DIDComm module configuration, instead of on the `agent` instance directly.

**0.5.x**

```ts
import { HttpInboundTransport, HttpOutboundTransport } from '@credo-ts/node'

agent.registerInboundTransport(new HttpInboundTransport({ port: 3000 }))
agent.registerOutboundTransport(new HttpOutboundTransport())
```

**0.6.x**

```ts
import {
  DidCommHttpInboundTransport,
  DidCommHttpOutboundTransport,
} from '@credo-ts/didcomm'

// Option 1: Register transports after agent creation
agent.didcomm.registerInboundTransport(new DidCommHttpInboundTransport({ port: 3000 }))
agent.didcomm.registerOutboundTransport(new DidCommHttpOutboundTransport())
```

Alternatively, transports can be specified directly in the `DidCommModule` configuration through the `transports` property, which is especially useful in React Native apps or when you want to keep all configuration in one place:

```ts
import {
  DidCommModule,
  DidCommHttpInboundTransport,
  DidCommHttpOutboundTransport,
} from '@credo-ts/didcomm'

const agent = new Agent({
  modules: {
    didcomm: new DidCommModule({
      transports: {
        inbound: [new DidCommHttpInboundTransport({ port: 3000 })],
        outbound: [new DidCommHttpOutboundTransport()],
      },
    }),
  },
})
```

#### MessagePickupRepository → QueueTransportRepository

If you are creating a DIDComm mediator or a related extension module, `MessagePickupRepository` has been refactored to `QueueTransportRepository` and now belongs to DIDComm module configuration. The `MessagePickupRepository` injection symbol has been dropped. To retrieve the current `QueueTransportRepository` instance, resolve `DidCommModuleConfig` and access `queueTransportRepository`.

All methods in `QueueTransportRepository` now include `AgentContext` as their first argument.

#### Deprecated DIDComm protocols

The following DIDComm protocols and formats are marked as deprecated in 0.6.0. The Connection, V1 Credential and V1 Proof protocols are still available in 0.7 and are removed in 0.8. The Legacy Indy formats still work with the V2 protocols in 0.8.

- **Connection Protocol V1** — Update to the DID Exchange protocol instead.
- **V1 Credential Protocol** — Update to the V2 Credential Protocol instead.
- **Legacy Indy Credential Format** — Update to the AnonCreds Credential Format.
- **V1 Proof Protocol** — Update to the V2 Proof Protocol instead.
- **Legacy Indy Proof Format** — Update to the AnonCreds Proof Format.

#### Express v5

If you're using the DIDComm HTTP transports or the OpenID4VC issuer/verifier packages, you should update to Express v5 (`express@^5.0.0`).

### Wallet configuration removed from agent config

The `walletConfig` property has been removed from the main `InitConfig`. Instead, each module defines its own storage and KMS configuration. For Askar, there is a new `store` property on the `AskarModule` config where you set the wallet id and key.

**0.5.x**

```ts
import { Agent } from '@credo-ts/core'
import { AskarModule } from '@credo-ts/askar'
import { ariesAskar } from '@hyperledger/aries-askar-nodejs'

const agent = new Agent({
  config: {
    label: 'My Agent',
    walletConfig: {
      id: 'my-wallet',
      key: 'my-wallet-key',
    },
  },
  modules: {
    askar: new AskarModule({ ariesAskar }),
  },
})
```

**0.6.x**

```ts
import { Agent } from '@credo-ts/core'
import { AskarModule } from '@credo-ts/askar'
import { askar } from '@openwallet-foundation/askar-nodejs'

const agent = new Agent({
  config: {
    // walletConfig removed from here
  },
  modules: {
    askar: new AskarModule({
      askar,
      store: {
        id: 'my-wallet',
        key: 'my-wallet-key',
      },
    }),
  },
})
```

### New Key Management System (KMS)

The wallet API has been completely rewritten. The previous `agent.wallet` API is replaced by the new `agent.kms` API. This new KMS supports multiple backends, symmetric keys, generic encryption/decryption, and identifies keys by key ID rather than by public key.

Key changes:

- **`Key` and `Jwk` classes removed** — replaced by the new `PublicJwk` class.
- **`JwaSignatureAlgorithm` renamed** — use `Kms.KnownJwaSignatureAlgorithms` instead.
- **`agent.wallet` removed** — use `agent.kms` for all key management operations.
- **`Wallet` injection symbol removed** — if you used this in custom modules, inject the KMS API using `agentContext.resolve(Kms.KeyManagementApi)` instead.

**0.5.x**

```ts
import { JwaSignatureAlgorithm, KeyType } from '@credo-ts/core'

const key = await agent.wallet.createKey({ keyType: KeyType.Ed25519 })
const publicKeyBase58 = key.publicKeyBase58
const algorithm = JwaSignatureAlgorithm.EdDSA
```

**0.6.x**

```ts
import { Kms } from '@credo-ts/core'

const { keyId, publicJwk } = await agent.kms.createKey({ type: 'ed25519' })
const algorithm = Kms.KnownJwaSignatureAlgorithms.EdDSA
```

### DID signing requires DidRecord

When signing with DIDs in Credo, it is now required that all DIDs have an associated `DidRecord` with the `created` role. The new KMS API needs to track key IDs for keys within a DID document, and these are stored on the DID record. You can import a DID using `agent.dids.import` and provide the `keys` array to define the mapping between verification method and key ID. If a verification method mapping is not provided, Credo assumes the legacy key ID format (base58 encoded public key).

### Credential record changes

Credential records have been updated to support batch credentials and KMS key tracking.

- **`record.credential`** is now **`record.firstCredential`**
- **`storeCredential()`** has been replaced by **`store()`** which expects a record parameter

**0.5.x**

```ts
await agent.w3cCredentials.storeCredential({ credential })

const credential = record.credential
```

**0.6.x**

```ts
const record = W3cCredentialRecord.fromCredential(credential)
await agent.w3cCredentials.store({ record })

const credential = record.firstCredential
```

Records now support multiple credential instances via the `credentialInstances` array and a `multiInstanceState` property to track usage.

### Askar dependency renamed

The Askar native bindings have been moved from `@hyperledger` to `@openwallet-foundation` scope.

**0.5.x**

```ts
import { ariesAskar } from '@hyperledger/aries-askar-nodejs'
// or
import { ariesAskar } from '@hyperledger/aries-askar-react-native'
```

**0.6.x**

```ts
import { askar } from '@openwallet-foundation/askar-nodejs'
// or
import { askar } from '@openwallet-foundation/askar-react-native'
```

### SD-JWT VC `typ` header default changed

The default `typ` header for SD-JWT VCs has been changed from `vc+sd-jwt` to `dc+sd-jwt`. You can still use `vc+sd-jwt` by providing the `headerType` value when signing an SD-JWT VC. For OID4VCI, the `typ` header is based on the OID4VCI credential format used.

### BBS+ signatures removed

The BBS+ module has been removed. The underlying implementation was outdated and unmaintained. A new version based on the BBS standard currently being developed may be added in the future. If you still need the legacy BBS+ functionality, you can extract the code from an older commit of the Credo repository and create your own custom module.

### Linked attachments removed

Support for DIDComm linked attachments has been removed. The functionality was not working correctly and only supported the deprecated V1 issuance protocol.

### Indy SDK migration package removed

The `@credo-ts/indy-sdk-to-askar-migration` package has been removed. The Indy SDK has been deprecated for a long time, and all wallets should have been migrated to Askar by now. If you haven't migrated yet, first update to 0.5.x and then to 0.6.x.

### Backup functionality removed

The automatic backup functionality has been removed from Credo. With the generalization of the KMS API and moving away from assuming Askar is used for storage, providing a generic backup API is not feasible. From now on, you are expected to create backups yourself before performing any updates.

### Other notable changes

- **TypeScript target updated to ES2020** — this generally should not impact supported environments (Node.js / React Native).
- **`Uint8Array` instead of `Buffer`** — internal key bytes representation now uses `Uint8Array` instead of `Buffer`.
- **Uncompressed keys by default** — EC keys (P-256, P-384, P-521, K-256) now default to uncompressed representation.
- **Zod 4** — Credo has been updated to Zod 4. Although the public API has not changed, error messages and some error structures may differ.
- **`DidCommV2Service` renamed** — `DidCommV2Service` is now `LegacyDidCommV2Service`, and `NewDidCommV2Service` became the default `DidCommV2Service`.
- **Modules no longer experimental** — `DcqlModule`, `DifPresentationExchangeModule`, `SdJwtVcModule`, `MdocModule`, `TenantsModule`, `CheqdModule`, `DrpcModule`, `OpenId4VcModule`, and `X509Module` are no longer experimental.
- **`createDeviceResponse` returns bytes** — `createDeviceResponse` now returns raw `Uint8Array` bytes instead of a base64-encoded string.
- **`DidDocumentService.protocolScheme()` removed** — this method has been removed as it's not possible to determine the protocol scheme from the base class.

### X509 Certificate API changes

The `X509Certificate` API has been updated to better support key management:

- The async `getData()` method has been split into a sync `.data` getter and an async `getThumbprint()` method.
- APIs that involve signing now require `X509Certificate` **instances** instead of encoded certificate strings. This allows you to attach a `keyId` to the certificate before passing it.

When signing with an X509 certificate, a `keyId` must always be set on the certificate instance. For certificates created before 0.6, use the legacy key id format. For certificates created after 0.6, you must manually store the key id and re-attach it after decoding.

```ts
// For certificates created before 0.6
const certificate = X509Certificate.fromEncodedCertificate(encodedCert)
certificate.keyId = certificate.publicJwk.legacyKeyId

// For certificates created after 0.6 (store the keyId yourself)
const certificate = X509Certificate.fromEncodedCertificate(encodedCert)
certificate.keyId = myStoredKeyId
```

Self-signed certificate creation is now done via `agent.x509.createCertificate` where the `subjectPublicKey` is not supplied or is equal to the `authorityKey`.

### OpenID4VC Module consolidation

The three separate OpenID4VC modules have been merged into a single `OpenId4VcModule`. The old `OpenId4VcHolderModule`, `OpenId4VcIssuerModule`, and `OpenId4VcVerifierModule` are replaced by `OpenId4VcModule`, which accepts optional `issuer` and `verifier` configuration. The holder is always available.

The `router` property on `OpenId4VcIssuerModule` and `OpenId4VcVerifierModule` has been replaced by a top-level `app` property on `OpenId4VcModule`. You pass your Express `app` instance directly, and Credo registers all routes (including `.well-known` paths) on it. If no `app` is provided and either issuer or verifier is configured, a new Express app instance is created automatically.

:::caution

Register your custom routes **after** calling `agent.initialize()` to ensure Credo's routes are registered first and your custom middleware does not clash with Credo's routes.

:::

**0.5.x**

```ts
import express from 'express'
import { OpenId4VcHolderModule, OpenId4VcIssuerModule, OpenId4VcVerifierModule } from '@credo-ts/openid4vc'

const issuerApp = express()
const verifierApp = express()

const agent = new Agent({
  modules: {
    openId4VcHolder: new OpenId4VcHolderModule(),
    openId4VcIssuer: new OpenId4VcIssuerModule({
      baseUrl: 'https://issuer.example.com',
      router: issuerRouter,
      credentialRequestToCredentialMapper: async ({ credentialRequest }) => { /* ... */ },
    }),
    openId4VcVerifier: new OpenId4VcVerifierModule({
      baseUrl: 'https://verifier.example.com',
      router: verifierRouter,
    }),
  },
})

// Register routers manually
issuerApp.use('/oid4vci', issuerRouter)
verifierApp.use('/oid4vp', verifierRouter)

// Access holder API
await agent.modules.openId4VcHolder.resolveCredentialOffer(credentialOffer)
// Access issuer API
await agent.modules.openId4VcIssuer.createIssuer()
// Access verifier API
await agent.modules.openId4VcVerifier.createVerifier()
```

**0.6.x**

```ts
import express from 'express'
import { OpenId4VcModule } from '@credo-ts/openid4vc'

const app = express()

const agent = new Agent({
  modules: {
    openid4vc: new OpenId4VcModule({
      app,
      issuer: {
        baseUrl: 'https://issuer.example.com',
        credentialRequestToCredentialMapper: async ({ credentialRequest }) => { /* ... */ },
      },
      verifier: {
        baseUrl: 'https://verifier.example.com',
      },
    }),
  },
})

// Holder is always available
await agent.openid4vc.holder.resolveCredentialOffer(credentialOffer)
// Issuer API (only available if issuer config was provided)
await agent.openid4vc.issuer.createIssuer()
// Verifier API (only available if verifier config was provided)
await agent.openid4vc.verifier.createVerifier()
```

### OpenID4VCI `requestCredentials` returns credential records

The `requestCredentials` method on the holder now returns objects with a `record` property (a `W3cCredentialRecord`, `SdJwtVcRecord`, or `MdocRecord`), pre-populated with KMS key IDs. Store each received credential using the `store({ record })` method on the appropriate credential API.

**0.5.x**

```ts
const credentialsResult = await agent.modules.openId4VcHolder.requestCredentials({
  resolvedCredentialOffer,
  ...accessTokenResponse,
  credentialBindingResolver: () => ({ method: 'did', didUrls: [holderDid] }),
})

for (const credential of credentialsResult.credentials) {
  await agent.w3cCredentials.storeCredential({ credential: credential.credential })
}
```

**0.6.x**

```ts
const credentialsResult = await agent.openid4vc.holder.requestCredentials({
  resolvedCredentialOffer,
  ...accessTokenResponse,
  credentialBindingResolver: () => ({ method: 'did', didUrls: [holderDid] }),
})

for (const credential of credentialsResult.credentials) {
  if (credential.record instanceof W3cCredentialRecord) {
    await agent.w3cCredentials.store({ record: credential.record })
  } else if (credential.record instanceof SdJwtVcRecord) {
    await agent.sdJwtVc.store({ record: credential.record })
  } else if (credential.record instanceof MdocRecord) {
    await agent.mdoc.store({ record: credential.record })
  }
}
```

### OpenID4VCI credential binding resolver called once

The `credentialBindingResolver` is now called once for the entire credential request (instead of once per credential). It can return multiple keys or a single key attestation to support batch issuance scenarios.

### OpenID4VCI `resolveIssuanceAuthorizationRequest` renamed

The method for resolving an authorization code flow request has been renamed:

**0.5.x**

```ts
await agent.modules.openId4VcHolder.resolveIssuanceAuthorizationRequest(
  resolvedCredentialOffer,
  authCodeFlowOptions
)
```

**0.6.x**

```ts
await agent.openid4vc.holder.resolveOpenId4VciAuthorizationRequest(
  resolvedCredentialOffer,
  authCodeFlowOptions
)
```

### OpenID4VCI draft version strings renamed

The version strings for credential offer versions have been updated. Draft 15 is **incompatible** with previous drafts, so if you are interacting with older issuers or holders you need to keep this in mind.

| 0.5.x | 0.6.x |
|-------|-------|
| `v1.draft11-13` | `v1.draft11-15` |
| `v1.draft13` | `v1.draft15` |

V1 support has also been added (`v1`) which is the default.

### SIOPv2 removed, renamed to OpenID4VP

Support for SIOPv2 has been removed. All classes, interfaces, and services that had `Siop` in their name have been renamed to use `OpenId4Vp`. For example, `OpenId4VcSiopHolderService` is now `OpenId4VpHolderService`. Update all imports accordingly.

### OpenID4VP `responseMode` default changed to `direct_post.jwt`

The default `responseMode` when creating an authorization request has changed from `direct_post` (unencrypted) to `direct_post.jwt` (encrypted). If you need to revert to unencrypted responses (e.g., for interoperability with older agents), you can set the `responseMode` explicitly:

```ts
await agent.openid4vc.verifier.createAuthorizationRequest({
  verifierId,
  requestSigner: { /* ... */ },
  presentationExchange: { /* ... */ },
  responseMode: 'direct_post', // opt out of encryption
})
```

### OpenID4VP DIF Presentation Exchange credential entry `type` → `claimFormat`

The field in returned credential entries from DIF Presentation Exchange results has been renamed from `type` to `claimFormat` to better align with the DC API terminology.

**0.5.x**

```ts
const verifiedResponse = await agent.modules.openId4VcVerifier.verifyAuthorizationResponse({ /* ... */ })

for (const submission of verifiedResponse.presentationExchange.presentations) {
  console.log(submission.type) // e.g. 'dc+sd-jwt'
}
```

**0.6.x**

```ts
const verifiedResponse = await agent.openid4vc.verifier.verifyAuthorizationResponse({ /* ... */ })

for (const submission of verifiedResponse.presentationExchange.presentations) {
  console.log(submission.claimFormat) // e.g. 'dc+sd-jwt'
}
```

### OpenID4VP DCQL multiple presentations per query

DCQL query results have changed: the selected credentials per query are now an **array** of credential IDs (at least one entry) instead of a single presentation. This allows the `multiple` option in DCQL queries to be properly supported.

### OpenID4VP mDOC: only one document per device response

Per ISO 18013-7, only **one** mDOC document per OpenID4VP device response is now supported. If a device response contains multiple documents, each is verified separately. When creating a device response for OpenID4VP presentation, ensure you only include one document.

### OpenID4VP `x509_san_uri` client id scheme removed on verifier side

Support for creating authorization requests using the `x509_san_uri` client id scheme has been removed from the verifier/issuer side (the holder still supports receiving such requests). This scheme is replaced by `x509_hash` in newer drafts, which is supported via the `x509_hash` client id prefix.

## Additional Resources

- [Credo 0.6.0 Release Notes](https://github.com/openwallet-foundation/credo-ts/releases/tag/v0.6.0)
- [Pure ESM package guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
- [Individual package changelogs](https://github.com/openwallet-foundation/credo-ts/tree/main/packages)
- [Hologram App migration PR (React Native)](https://github.com/2060-io/hologram-app/pull/368)
- [VS Agent migration PR (Node.js)](https://github.com/verana-labs/vs-agent/pull/317)
