iOS Holder SDK v6.0.0 Migration Guide
A comprehensive guide to migrating to iOS Holder SDK v6.0.0, covering breaking changes, new features, and step-by-step migration instructions.
This is a draft migration guide, shared ahead of general availability to help you gauge migration effort and plan ahead of the release. We will update this page to a final copy when the SDK is generally available.
Overview
This guide provides a comprehensive overview of the changes introduced in the iOS Holder SDK v6.0.0, including breaking changes, new features, and migration steps.
This release focuses on strengthening trust at issuance, improving predictability in credential handling, and increasing consistency across platforms. It introduces Wallet Attestation support, enabling issuers to verify wallet integrity before issuing credentials, while making holder-side integrations more reliable and easier to reason about in production environments.
Key Features
- Wallet Attestation support: The iOS Holder SDK now supports Wallet Attestation, allowing your holder application to prove to issuers that it is a trusted wallet before credentials are issued. To enable this, the SDK is now tethered to a MATTR VII tenant, which issues wallet attestation tokens when required by an issuer.
- Stronger application identity during issuance: Credential issuance flows now consistently use the application's
client_id, ensuring the holder is accurately represented when interacting with issuers. This improves compatibility with issuers applying stricter controls. - More predictable credential retrieval results: Credential retrieval responses are now more structured and deterministic, explicitly identifying success or failure with guaranteed fields per result type.
- Cross-platform alignment: Naming and response structures have been aligned with the Android Holder SDK, minimizing divergence for teams maintaining cross-platform applications.
- Improved biometric and storage lifecycle handling: Edge cases in biometric authentication, storage lifecycle, and app lifecycle events (such as prewarming or reinstall scenarios) have been addressed.
- General stability and performance improvements: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability.
Breaking Changes
This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation:
| # | Change | Impact |
|---|---|---|
| 1 | SDK initialization now requires platform/tenant configuration | You must create a holder application on your MATTR VII tenant and update all initialization call sites to provide tenant configuration. |
| 2 | Pre-authorized code flow now uses the application's client_id instead of a default identifier | Ensure your application has a valid configured client_id and issuers recognize it. |
| 3 | Credential retrieval result shape updated to explicitly identify success or failure with guaranteed fields | Update result parsing logic to use success/failure branching. |
| 4 | doctype renamed to docType in credential retrieval | Rename all usages of doctype to docType. |
| 5 | MobileCredentialAuthenticationOption renamed to DeviceAuthenticationOption | Update all imports and type references. |
| 6 | matchedCredentials is now non-optional for online presentation | Remove optional handling; treat as an array and handle empty-array cases. |
| 7 | VerificationResult renamed to MobileCredentialVerificationResult and aligned with Android | Update all type references and downstream mapping logic. |
| 8 | Wallet Attestation introduces new error scenarios | Update error handling, logging, analytics, and support diagnostics to account for new wallet attestation error cases. |
| 9 | challenge in requestMobileCredentials is now optional to align with Android | Remove assumptions that challenge must always be supplied. |
| 10 | VerifierAuthenticationResult aligned with Android structure | Update verifier authentication result handling and exhaustive switches. |
Migration Steps
Create a holder application on your MATTR VII tenant
The iOS Holder SDK is now tethered to a MATTR VII tenant. The SDK will not function without a holder application configuration on your tenant. This is required regardless of whether wallet attestation is used in your credential flows.
To register your iOS application, make a request to create a holder application:
POST /v1/holder/applications{
"name": "My iOS Holder Application",
"clientId": "your-wallet-client-id",
"type": "ios",
"bundleId": "com.yourcompany.holderapp",
"teamId": "YOUR_APPLE_TEAM_ID"
}name: A unique name to identify your holder application.clientId: The OAuth 2.0client_idthat identifies your wallet application. This value is included in attestation JWTs and must match theclient_idconfigured on the issuer's Authorization Server.type: Must beiosfor an iOS application.bundleId: The Bundle ID of your iOS app (must match your Xcode project configuration).teamId: Your Apple Developer Team ID.
Once created, your holder application will be able to use the SDK and interact with the MATTR VII platform (for example, to obtain attestation tokens).
The response will include a unique id for your application, which must be used when initializing the SDK so that it can correctly identify and authenticate your application.
The clientId you configure here is the same value you must:
- Pass as the
clientIdparameter when callingretrieveCredentials. - Register with each issuer you intend to interact with, so the issuer can identify and trust requests coming from your wallet application.
Update SDK initialization
SDK initialization now requires platform configuration. Update initialization calls to provide the required configuration:
- try await MobileCredentialHolder.shared.initialize(instanceID: instanceID)
+ let platformConfig = PlatformConfiguration(
+ tenantUrl: "https://your-tenant.vii.mattr.global",
+ appIdentifier: "1ef1f867-20b4-48ea-aec1-bea7aff4964c"
+ )
+ try await MobileCredentialHolder.shared.initialize(
+ instanceID: instanceID,
+ platformConfiguration: platformConfig
+ )tenantUrl: The URL of your MATTR VII tenant. This must be the tenant where your holder application is configured.appIdentifier: Theidof your configured MATTR VII holder application.
Handle new Wallet Attestation error modes
Wallet Attestation introduces new error scenarios that can occur during credential issuance. These errors may arise when:
- The SDK requests an attestation token from your MATTR VII tenant.
- The SDK presents wallet attestation credentials to an issuer's Authorization Server at its
/tokenendpoint. - The SDK requests credentials from an issuer's
/credentialendpoint with DPoP-bound access tokens.
The following methods are affected:
retrieveCredentials— may throw new error cases and return new per-credential failure types.
New error cases will be added to MobileCredentialHolderError (a breaking change requiring updates to exhaustive switch statements), CredentialIssuanceError, and RetrieveCredentialError.
Update your error handling, logging, analytics, and support diagnostics to account for new wallet attestation error cases. An exhaustive list of new errors will be provided with the final migration guide.
Update client_id configuration
Previously, the client_id passed to
retrieveCredentials
was not shared with the issuer during the pre-authorized code flow, so any value would work. This is no longer the case — the SDK now presents the client_id to the issuer as part of wallet attestation, and the issuer validates it against its list of trusted wallet providers.
To prepare for this change:
- Coordinate with the issuer to register your wallet application as a trusted wallet provider. The issuer will provide you with a
client_idthat identifies your application. - Pass the issuer-provided
client_idwhen callingretrieveCredentials.
let results = try await holder.retrieveCredentials(
credentialOffer: offer,
- clientId: "any-value",
+ clientId: "issuer-provided-client-id"
)Issuance flows that previously worked with an arbitrary client_id will fail if the issuer requires a trusted wallet provider. Ensure you have coordinated with each issuer and obtained the correct client_id before upgrading. Test direct issuance flows to confirm credentials are issued successfully.
Update credential retrieval result handling
RetrieveCredentialResult has been converted from a struct with optional fields to an enum with explicit .success and .failure cases. Each case carries guaranteed associated values, removing ambiguity from result handling.
The retrieveCredentials function returns [RetrieveCredentialResult] — an array with one result per offered credential. Update your iteration logic to use pattern matching:
let results = try await holder.retrieveCredentials(options)
for result in results {
- if let credentialId = result.credentialId {
- // Use result.docType and credentialId
- } else if let error = result.error {
- // Use result.docType and error
- }
+ switch result {
+ case .success(let docType, let credentialId):
+ // docType and credentialId are guaranteed
+ case .failure(let docType, let error):
+ // docType and error are guaranteed
+ }
}Update tests and any downstream logic that checked for nil fields.
Rename doctype to docType
The credential retrieval field has been renamed from doctype to docType. Update all usages across your codebase:
- let documentType = credential.doctype
+ let documentType = credential.docTypeUpdate models, serializers, mocks, tests, and any cross-platform abstraction layers.
Rename MobileCredentialAuthenticationOption to DeviceAuthenticationOption
Update all imports, type references, and configuration objects:
- let authOption: MobileCredentialAuthenticationOption = .biometryCurrentSet
+ let authOption: DeviceAuthenticationOption = .biometryCurrentSetUpdate matchedCredentials handling for online presentation
The matchedCredentials property is now non-optional for online presentation. Remove optional handling and treat it as an array:
- if let matched = session.matchedCredentials {
- // Handle matched credentials
- } else {
- // Handle nil case
- }
+ // matchedCredentials is now always an array (may be empty)
+ if session.matchedCredentials.isEmpty {
+ // Handle no matched credentials
+ } else {
+ // Handle matched credentials
+ }Update VerificationResult to MobileCredentialVerificationResult
The VerificationResult type has been renamed to MobileCredentialVerificationResult and aligned structurally with Android:
- let result: VerificationResult = ...
+ let result: MobileCredentialVerificationResult = ...You will need to validate cross-platform result handling and update any downstream mapping logic.
Handle optional challenge in requestMobileCredentials
The challenge parameter is now optional in requestMobileCredentials. Review call sites and validation logic, and remove any app-side assumptions that challenge must always be supplied:
- // challenge was previously required
- let request = try holder.requestMobileCredentials(challenge: challenge, ...)
+ // challenge is now optional
+ let request = try holder.requestMobileCredentials(challenge: challenge, ...) // still works
+ let request = try holder.requestMobileCredentials(...) // also valid without challengeUpdate Verifier Authentication Result handling
VerifierAuthenticationResult is no longer optional. Previously, if the verifier didn't provide authentication proof, the result was nil. Now, the SDK always returns a concrete value — if no authentication proof is provided, the result is .unsigned(origin: nil) rather than absent. This means you can always inspect the verifier authentication result without optional unwrapping.
For online presentation, where the value was previously always nil, it is now always .unsigned(origin:).
Update your code to remove optional handling and account for the new default:
- public internal(set) var verifierAuthenticationResult: VerifierAuthenticationResult?
+ public internal(set) var verifierAuthenticationResult: VerifierAuthenticationResult init(docType: MobileCredentialDataTypes.DocType,
namespaces: [MobileCredentialDataTypes.NameSpace: [MobileCredentialDataTypes.ElementID: MobileCredentialDataTypes.IntentToRetain]],
- verifierAuthenticationResult: VerifierAuthenticationResult? = nil) {
+ verifierAuthenticationResult: VerifierAuthenticationResult = .unsigned(origin: nil)) {How would you rate this page?
Last updated on