Vault
Affinidi TDK - Vault for Dart provides the libraries and tools to implement digital identity wallet into your Flutter/Dart application for creating Decentralised Identity solution to securely manage Decentralised Identifiers (DIDs), Verifiable Credentials, and other data that represents the user’s identity based on different context, for example, you can create an identity for shopping, personal finance, and work.
Installation
Package: affinidi_tdk_vault
dart pub add affinidi_tdk_vaultCheck the latest version on pub.dev or view the source on GitHub.
Most of the examples on this page also require affinidi_tdk_vault_data_manager for cloud profile storage.
Create and Initialise Vault
Initialise vault with wallet, storage, and profile interface. To create a wallet, you must generate a seed to create the key pairs. The profile is created using a cloud or local storage option supported by the vault.
Refer to the published SSI Dart package to know more about different types of wallet supported.
The sample code uses InMemoryVaultStore as the storage option for demonstration purposes only and must not be used in production; instead, use a secure storage option like FlutterSecureVaultStore from affinidi_tdk_vault_flutter_utils package.
Import
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';Parameters
vaultStore
VaultStore
RequiredprofileRepositories
Map<String, ProfileRepository>
RequiredVfsProfileRepository.restorableRepositories
Map<String, Restorable>
OptionalprofileRepositories. Only needed for repositories that participate in Vault backups; see Back Up and Restore the Vault.namedRestorables
Map<String, Restorable>
OptionaldefaultProfileRepositoryId
String
OptionalprofileRepositories.Example
// Initialise InMemory storage
final vaultStore = InMemoryVaultStore();
await vaultStore.setAccountIndex(32);
// Generate seed from the storage layer
final seed = vaultStore.getRandomSeed();
await vaultStore.setSeed(seed);
// Initialise profile interface
const vfsRepositoryId = 'vfs';
final profileRepositories = <String, ProfileRepository>{
vfsRepositoryId: VfsProfileRepository(vfsRepositoryId),
};
// In this example, we are using Bip32 type wallet from SSI package
final vault = await Vault.fromVaultStore(
vaultStore,
profileRepositories: profileRepositories,
defaultProfileRepositoryId: vfsRepositoryId,
);
// Ensure vault is initialised before being able to access any of the repositories
await vault.ensureInitialized();Most Vault, profile, and shared-storage operations also accept an optional cancelToken (VaultCancelToken) to cancel an in-flight request. Call cancelToken.cancel() to cancel; the pending operation completes with a TdkException whose code is request_cancelled.
Manage Vault Profiles
Create and manage profiles associated with the vault.
Create Vault Profile
Create a profile to store credentials and documents. A profile represents a user’s identity.
These examples use a cloud profile backed by Affinidi Vault (
VfsProfileRepository). The vault also supports local profiles through the edge provider packages, which participate in backup and restore.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
name
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Create Profile
try {
final profileRepository = vault.defaultProfileRepository;
// alternatively can be accessed via profile repository identifier
// final ProfileRepository profileRepository = vault.profileRepositories[vfsRepositoryId];
await profileRepository.createProfile(name: 'Work Profile');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}List Vault Profiles
Retrieve the list of profiles associated with the vault, across all registered profile repositories.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';No parameters required.
Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Retrieve list of profiles from the vault
var profiles = await vault.listProfiles();
// Check if we have list of profiles
if (!profiles.isEmpty) {
profiles.forEach((profile) {
print('${profile.id} : ${profile.name}');
});
} else {
print('Vault profiles is empty.');
}Update Vault Profile
Update a profile associated with the vault.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profile
Profile
Requiredname) already changed locally.Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// Update profile name
profile.name = 'Test Profile 123';
// Update profile
await profileRepository.updateProfile(profile);
} else {
print('Profile not found.');
}Delete Vault Profile
Delete a profile associated with the vault.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profile
Profile
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// Delete profile
await profileRepository.deleteProfile(profile);
} else {
print('Profile not found.');
}Share Vault Profile
Share an entire profile with another user to grant access to all data within the profile.
Profile and item sharing requires the profile repository to implement the
ProfileAccessSharinginterface. TheVfsProfileRepositoryfromaffinidi_tdk_vault_data_managersupports both profile and item-level sharing.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profileId
String
RequiredtoDid
String
Requiredpermissions
Permissions
RequiredPermissions.read, Permissions.write, or Permissions.all.expiresAt
DateTime
OptionalExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Owner shares their profile with a grantee
final granteeDid = 'did:key:recipient-did';
final sharedProfile = await vault.shareProfile(
profileId: 'my-profile-id',
toDid: granteeDid,
permissions: Permissions.read, // or Permissions.write, Permissions.all
);
// Owner sends SharedProfileDto to grantee (via your application's communication channel)Accept Shared Profile
Accept a shared profile that was granted by another user (grantee side).
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profileId
String
RequiredsharedProfile
SharedProfileDto
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// Grantee accepts the shared profile received from owner
final updatedProfile = await vault.addSharedProfile(
profileId: 'grantee-profile-id',
sharedProfile: sharedProfile, // SharedProfileDto received from owner
);
print('Successfully accepted shared profile: ${updatedProfile.id}');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Share Individual Items
Share specific files or folders (items) with another user instead of sharing the entire profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
final granteeDid = 'did:key:recipient-did';
// 1. Get current permissions policy
final policy = await vault.getItemPermissionsPolicy(
profileId: 'my-profile-id',
granteeDid: granteeDid,
);
// 2. Add permission locally
policy.addPermission(
['file-123'], // List of item IDs (files or folders)
[Permissions.read, Permissions.write],
);
// 3. Set the complete policy on the backend
final kek = await vault.setItemAccess(
profileId: 'my-profile-id',
granteeDid: granteeDid,
policy: policy,
);
// 4. Create SharedItemsDto to send to grantee
final sharedItem = SharedItemsDto(
kek: kek,
ownerProfileId: 'my-profile-id',
ownerProfileDID: 'did:key:owner-did',
itemIds: ['file-123'],
);
// Owner sends SharedItemsDto to grantee (via your application's communication channel)
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Accept Shared Items
Accept shared items (files/folders) that were granted by another user (grantee side).
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profileId
String
RequiredsharedItems
SharedItemsDto
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// Grantee accepts the shared items received from owner
final updatedProfile = await vault.acceptSharedItems(
profileId: 'grantee-profile-id',
sharedItems: sharedItem, // SharedItemsDto received from owner
);
print('Successfully accepted shared items into ${updatedProfile.id}');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Share Items with Time-Bound Access
Share items, or an entire profile, with automatic expiration by specifying an expiresAt date/time.
- Time-bound expiry is available for both profile-level sharing (
shareProfile’sexpiresAt) and item-level sharing (ItemPermissionsPolicy.addPermission’sexpiresAt). - If
expiresAtis not provided, access remains valid until manually revoked (default behaviour). - If
expiresAtis set to zero or a negative duration from now, the backend rejects the request as an invalid time frame. - Once the specified time frame expires, the backend automatically revokes access.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
final granteeDid = 'did:key:recipient-did';
// Get current permissions policy
final policy = await vault.getItemPermissionsPolicy(
profileId: 'my-profile-id',
granteeDid: granteeDid,
);
// Add permission with expiration date
policy.addPermission(
['file-123'],
[Permissions.read],
expiresAt: DateTime.now().add(Duration(hours: 1)), // Access valid for 1 hour
);
// Set the policy
final kek = await vault.setItemAccess(
profileId: 'my-profile-id',
granteeDid: granteeDid,
policy: policy,
);
print('Time-bound access granted. Expires in 1 hour.');
// After the expiresAt DateTime passes, access is automatically revoked by the backend
// The grantee will no longer be able to access the shared item
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Get Item Access Permissions
Retrieve the current access permissions for shared items.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profileId
String
RequiredgranteeDid
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
final granteeDid = 'did:key:recipient-did';
// Get current item access permissions
final permissions = await vault.getItemAccess(
profileId: 'my-profile-id',
granteeDid: granteeDid,
);
permissions.forEach((permission) {
print('Item: ${permission.itemIds}');
print('Permissions: ${permission.rights}');
if (permission.expiresAt != null) {
print('Expires at: ${permission.expiresAt}');
}
});
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Read Shared Item Content
Read the content of a shared item (file/folder) that another user has shared with you.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
ownerProfileId
String
RequireditemId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// Read content from a shared item
final content = await vault.readSharedItem(
ownerProfileId: 'owner-profile-id',
itemId: 'file-123',
);
print('Shared item content: $content');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Revoke Profile Access
Revoke access to a profile for a specific user.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
profileId
String
RequiredgranteeDid
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
final granteeDid = 'did:key:recipient-did';
// Revoke profile access from grantee
await vault.revokeProfileAccess(
profileId: 'my-profile-id',
granteeDid: granteeDid,
);
print('Profile access revoked successfully');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Revoke Item Access
Revoke access to specific items (files/folders) for a user.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
final granteeDid = 'did:key:recipient-did';
// Get current permissions policy
final policy = await vault.getItemPermissionsPolicy(
profileId: 'my-profile-id',
granteeDid: granteeDid,
);
// Remove permission locally
policy.removePermission(
['file-123'],
[], // Empty list removes all permissions for the specified items
);
// Set the updated policy to revoke access
await vault.setItemAccess(
profileId: 'my-profile-id',
granteeDid: granteeDid,
policy: policy,
);
print('Item access revoked successfully');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Manage Vault Files
Create folders and upload files to manage documents associated with the vault.
Create Folder
Create a folder in the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
folderName
String
RequiredparentFolderId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// We'll create a folder under a profile (root folder)
final rootFolderId = profile.id;
await profile.defaultFileStorage!.createFolder(
folderName: '<Folder_Name>',
parentFolderId: rootFolderId,
);
} else {
print('Profile not found.');
}Get Folder
Get the items (files and subfolders) in a vault folder, with pagination support.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
folderId
String
Optionallimit
int
OptionalexclusiveStartItemId
String
OptionallastEvaluatedItemId from a previous page, to fetch the next page.Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// We'll retrieve the items under a profile (root folder)
final rootFolderId = profile.id;
// List items; getFolder returns a PaginatedList
final page = await profile.defaultFileStorage?.getFolder(folderId: rootFolderId);
page?.items.forEach((item) {
print('${item.id} : ${item.name}');
});
// page.hasMore is true when page.lastEvaluatedItemId is not null; pass it
// back as exclusiveStartItemId to fetch the next page.
} else {
print('Profile not found.');
}Rename Folder
Rename a folder in the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
folderId
String
RequirednewName
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// Rename the folder
await profile.defaultFileStorage!.renameFolder(
folderId: '<Folder_ID>',
newName: '<Folder_Name>',
);
} else {
print('Profile not found.');
}Delete Folder
Delete a folder in the vault’s profile. The folder must be empty.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
folderId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
try {
await profile.defaultFileStorage?.deleteFolder(folderId: '<Folder_ID>');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}
} else {
print('Profile not found.');
}Create File
Create a file in a folder in the vault’s profile.
The vault enforces two storage constraints on createFile: each individual file, after encryption, must fit within the per-file upload cap, and the vault has a cumulative storage cap of 500 MB across all files. Exceeding either limit throws a TdkException with code storage_limit_exceeded. Call Vault.getStorageUsage() before uploading large payloads to check current consumption.
Import
import 'dart:typed_data';
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
fileName
String
Requireddata
Uint8List
RequiredparentFolderId
String
OptionalonSendProgress
VaultProgressCallback
OptionalExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// We'll retrieve the folders under a profile (root folder)
final rootFolderId = profile.id;
// Create a dummy file
final fileContent = Uint8List.fromList([1, 2, 3]);
try {
// create a file, we'll create a file in the root folder, instead of subfolder.
await profile.defaultFileStorage!.createFile(
fileName: 'Doc Test File 1',
data: fileContent,
parentFolderId: rootFolderId,
);
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}
} else {
print('Profile not found.');
}Get File
Get the file metadata in the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
fileId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// Retrieve the file object
final file = await profile.defaultFileStorage!.getFile(fileId: '<File_ID>');
print(file.name);
} else {
print('Profile not found.');
}Get File Content
Get the content of a file in the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
fileId
String
RequiredonReceiveProgress
VaultProgressCallback
OptionalExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// Retrieve the file content
final fileData = await profile.defaultFileStorage!.getFileContent(
fileId: '<File_ID>',
);
print(fileData);
} else {
print('Profile not found.');
}Rename File
Rename a file in the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
fileId
String
RequirednewName
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
// Rename the file object
await profile.defaultFileStorage!.renameFile(
fileId: '<File_ID>',
newName: '<File_Name>',
);
} else {
print('Profile not found.');
}Delete File
Delete a file from the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
fileId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
if (profile != null) {
try {
await profile.defaultFileStorage!.deleteFile(fileId: '<File_ID>');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}
} else {
print('Profile not found.');
}Manage Vault Credentials
Manage credentials claimed from credential issuers into the vault.
Claim Credential
Claim the credential from the issuer.
Import
import 'dart:typed_data';
import 'package:affinidi_tdk_claim_verifiable_credential/oid4vci_claim_verifiable_credential.dart';
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
final keyDerivationPath = "m/44'/60'/0'/0/0";
final keyPair = await wallet.deriveKey(derivationPath: keyDerivationPath);
final didDocument = DidKey.generateDocument(keyPair.publicKey);
final signer = DidSigner(
didDocument: didDocument,
didKeyId: didDocument.verificationMethod.first.id,
keyPair: keyPair,
signatureScheme: SignatureScheme.ecdsa_secp256k1_sha256,
);
// Create a new instance of ClaimVerifiableCredentialService
final claimVerifiableCredentialService = OID4VCIClaimVerifiableCredentialService(
didSigner: signer,
);
final uri = Uri.parse(
'https://example.com/callback?credential_offer_uri=https://issuer.example.com/offer/123',
);
final context = await claimVerifiableCredentialService.loadCredentialOffer(uri);
VerifiableCredential? credential;
String? txCode = '<TX_CODE_FROM_ISSER>';
// Check if the credential offer is issued with Transaction Code
if (context.credentialOffer.isTxCodeRequired) {
// Claim credential with Transaction Code
// Transaction Code is generated and must be provided by the issuer
credential = await claimVerifiableCredentialService.claimCredential(
claimContext: context,
txCode: txCode,
);
} else {
// Claim credential without Transaction Code
credential = await claimVerifiableCredentialService.claimCredential(
claimContext: context,
);
}
print('Credential: $credential');
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Save Credential
Store a claimed credential into the vault’s profile.
Import
import 'dart:typed_data';
import 'package:affinidi_tdk_claim_verifiable_credential/oid4vci_claim_verifiable_credential.dart';
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
verifiableCredential
VerifiableCredential
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// ... claim the credential as shown above, then:
if (credential.id != null) {
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
// do we have a profile selected?
if (profile != null) {
// Save credential on the selected vault profile
await profile.defaultCredentialStorage?.saveCredential(
verifiableCredential: credential,
);
}
}
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}List Credentials
List credentials from the vault’s profile, with pagination support.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
limit
int
OptionalexclusiveStartItemId
String
OptionallastEvaluatedItemId from a previous page, to fetch the next page.Example
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
// do we have a profile selected?
if (profile != null) {
// listCredentials returns a PaginatedList
final page = await profile.defaultCredentialStorage?.listCredentials();
print('Credentials: ${page?.items}');
}
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Get Credential
Get a credential from the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
digitalCredentialId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
// do we have a profile selected?
if (profile != null) {
// Get credential from the selected vault profile
final credential = await profile.defaultCredentialStorage?.getCredential(
digitalCredentialId: '<Credential_ID>',
);
print('Credential: $credential');
}
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Delete Credential
Delete a credential from the vault’s profile.
Import
import 'package:affinidi_tdk_vault_data_manager/affinidi_tdk_vault_data_manager.dart';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
digitalCredentialId
String
RequiredExample
// Must initialize vault before being able to access any of the repositories
await vault.ensureInitialized();
try {
// Get the list of available profiles from the vault
var profiles = await vault.listProfiles();
// For demonstration purposes, we always get the last profile from the list
var profile = profiles.lastOrNull;
// do we have a profile selected?
if (profile != null) {
// Delete credential from the selected vault profile
await profile.defaultCredentialStorage?.deleteCredential(
digitalCredentialId: '<Credential_ID>',
);
}
} on TdkException catch (error) {
print([error.code, error.message, error.originalMessage].join('\n'));
} catch (e) {
print('Error: $e');
}Back Up and Restore the Vault
Vault backup and restore is the disaster-recovery path for a wallet: a backup is an encrypted snapshot of the wallet’s durable state, the Vault Store’s seed and account index, every local repository that implements Restorable, and any additional named components you explicitly register, such as consent history. Cloud repositories such as VfsProfileRepository are recorded by ID only; their remote data is not copied, and a vault restore reconnects them through their normal configuration instead.
Backup and restore is only meaningful for local repositories, whose data lives on the device. The examples below use EdgeProfileRepository, EdgeDriftRepositoryFactory, and EdgeEncryptionService from the affinidi_tdk_vault_edge_provider and affinidi_tdk_vault_edge_drift_provider packages, which provide a local, Drift-backed profile repository that implements Restorable. A vault made up only of cloud repositories has nothing local to back up.
Define a Passphrase Policy
PassphrasePolicy validates the strength of a backup passphrase before createBackup encrypts anything. VaultBackupService uses PassphrasePolicy.standard (minimum 12 characters, requiring an uppercase letter, a number, and a special character) unless you supply your own.
Import
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
minLength
int
Optional12.requireUppercase
bool
Optionaltrue.requireNumber
bool
Optionaltrue.requireSpecialCharacter
bool
Optionaltrue.Example
const passphrasePolicy = PassphrasePolicy(minLength: 16);
// Check a candidate passphrase without creating a backup.
final violation = passphrasePolicy.validate(candidatePassphraseBytes);
if (violation != null) {
print('Passphrase rejected: ${violation.code}');
}Create a Backup
Encrypt and export the vault’s durable state with VaultBackupService.createBackup. The result is a self-contained ByteData you can write to a file or upload to your own storage.
The passphrase is caller-supplied key material, not a value the SDK stores. Read it from a file or a secure prompt, never hard-code it, and overwrite the buffer with passphrase.fillRange(0, passphrase.length, 0) in a finally block once the call returns.
Import
import 'dart:io';
import 'dart:typed_data';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
vault
Vault
Requiredpassphrase
Uint8List
RequiredVaultBackupService’s configured passphrasePolicy.Example
final backupService = VaultBackupService(
passphrasePolicy: const PassphrasePolicy(minLength: 16),
);
final passphrase = await File(passphraseFilePath).readAsBytes();
late final ByteData backup;
try {
backup = await backupService.createBackup(
vault: vault,
passphrase: passphrase,
);
} finally {
passphrase.fillRange(0, passphrase.length, 0);
}
// Native applications can persist the bytes with dart:io.
await File('vault.backup').writeAsBytes(
backup.buffer.asUint8List(backup.offsetInBytes, backup.lengthInBytes),
);Restore a Backup
Restore a backup into empty storage with VaultBackupService.restoreBackup. Every restore destination, the VaultStore, each restorable repository, and each named component, must already be empty; restore rejects existing local data and never deletes it automatically.
Repository factories are keyed by the same stable repository ID used when the vault was created. Use ProfileRepositoryRegistration.withBackupData for a repository whose data is part of the backup (it receives the restored VaultStore so it can build its own encryption service before the vault opens normally), and .withoutBackupData for a cloud-backed repository that only needs its stable ID and factory restored, since its data never left the cloud.
Calls to restoreBackup and discardInterruptedRestore on the same VaultBackupService instance are serialised: a second concurrent call queues behind the first. This does not extend across separate VaultBackupService instances or processes, so avoid running concurrent restores against the same persistence layer through more than one instance.
Import
import 'dart:io';
import 'dart:typed_data';
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
backupData
ByteData
RequiredcreateBackup.passphrase
Uint8List
RequiredvaultStoreFactory
FutureOr<VaultStore> Function()
RequiredVaultStore.repositoryFactories
Map<String, ProfileRepositoryRegistration>
RequirednamedRestorableFactories
Map<String, RestorableFactory>
OptionalconsentHistory. Defaults to {}.Repository registrations
Each repositoryFactories value is a ProfileRepositoryRegistration, built with one of two constructors. ProfileRepositoryRegistration.withBackupData registers a repository whose data is included in the backup:
id
String
RequiredrepositoryFactories and the ID recorded in the backup manifest.factory
FutureOr<T> Function(VaultStore vaultStore)
RequiredVaultStore, so a local repository can construct its encryption service before the vault opens.asRestorable
Restorable Function(T repository)
RequiredRestorable view. Pass the built-in restorableIdentity helper when the repository already implements Restorable (as EdgeProfileRepository does); supply your own adapter only when it does not.ProfileRepositoryRegistration.withoutBackupData registers a cloud-backed repository that carries no backup data. It takes the same id and factory, but no asRestorable, since nothing local is restored for it. Both restorableIdentity and both registration constructors come from affinidi_tdk_vault.
Example
final fileBytes = await File('vault.backup').readAsBytes();
final passphrase = await File(passphraseFilePath).readAsBytes();
try {
final restoredVault = await VaultBackupService().restoreBackup(
backupData: ByteData.sublistView(fileBytes),
passphrase: passphrase,
// Every restore destination must be empty. Use a new persistent
// VaultStore and repository storage in an application instead of
// overwriting existing storage.
vaultStoreFactory: InMemoryVaultStore.new,
repositoryFactories: {
'edge': ProfileRepositoryRegistration.withBackupData(
id: 'edge',
factory: (vaultStore) => EdgeProfileRepository(
'edge',
EdgeDriftRepositoryFactory(database: edgeDatabase),
EdgeEncryptionService(vaultStore: vaultStore),
),
asRestorable: restorableIdentity,
),
// VFS data remains in cloud storage, so only its stable ID and
// factory are restored.
'vfs': ProfileRepositoryRegistration.withoutBackupData(
id: 'vfs',
factory: (_) => VfsProfileRepository('vfs'),
),
},
);
} finally {
passphrase.fillRange(0, passphrase.length, 0);
}Recover an Interrupted Restore
Persist your own restore-in-progress flag immediately before calling restoreBackup, and clear it once restore completes. If the flag is still set the next time the app starts, a previous restore was interrupted, for example by a crash or an app kill. Show a recovery interface, and after the user confirms the partial restore may be discarded, call discardInterruptedRestore to clear the leftover state before offering backup selection and retry as a separate action.
discardInterruptedRestore clears named components first, then repositories registered with .withBackupData, then the VaultStore last, and it is safe to call repeatedly. Repositories registered with .withoutBackupData are left completely untouched, since their data lives in the cloud and was never part of the interrupted local restore.
Import
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
vaultStoreFactory
FutureOr<VaultStore> Function()
RequiredVaultStore used by the interrupted restoreBackup call.repositoryFactories
Map<String, ProfileRepositoryRegistration>
RequiredrestoreBackup call.namedRestorableFactories
Map<String, RestorableFactory>
OptionalrestoreBackup call. Defaults to {}.Example
if (await restoreState.isInProgress()) {
await VaultBackupService().discardInterruptedRestore(
vaultStoreFactory: createVaultStore,
repositoryFactories: repositoryFactories,
namedRestorableFactories: namedRestorableFactories,
);
await restoreState.clear();
}Implement a Custom Restorable Component
Register your own storage as a named or repository-scoped component in a Vault backup by implementing Restorable. Extend a class that already implements it, such as VaultStore, where possible; implement the interface directly for a component with no existing Restorable base, such as your own local database wrapper.
Import
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';Parameters
export
Future<Map<String, dynamic>> Function()
RequiredvalidateImport
Future<void> Function(Map<String, dynamic> data)
Requiredimport needs, without mutating durable state.isEmpty
Future<bool> Function()
RequiredclearAllData
Future<void> Function()
Requiredimport
Future<void> Function(Map<String, dynamic> data)
RequiredrollbackImport
Future<void> Function()
Requiredimport. Must be a no-op when no import was attempted or the import completed successfully, and must be idempotent.Example
class MyRestorableComponent implements Restorable {
static const _schemaVersion = '1.0.0';
Map<String, dynamic>? _data;
bool _importPendingRollback = false;
@override
Future<Map<String, dynamic>> export() async {
return {'schemaVersion': _schemaVersion, ...?_data};
}
@override
Future<void> validateImport(Map<String, dynamic> data) async {
if (data['schemaVersion'] != _schemaVersion) {
throw VaultRestoreException.invalidBackupFormat();
}
}
@override
Future<bool> isEmpty() async => _data == null;
@override
Future<void> import(Map<String, dynamic> data) async {
await validateImport(data);
if (!await isEmpty()) {
throw VaultRestoreException.destinationNotEmpty('MyRestorableComponent');
}
_importPendingRollback = true;
_data = data;
_importPendingRollback = false;
}
@override
Future<void> clearAllData() async {
_data = null;
_importPendingRollback = false;
}
@override
Future<void> rollbackImport() async {
if (!_importPendingRollback) return;
await clearAllData();
}
}Register the component through restorableRepositories (keyed by profile repository ID) or namedRestorables (keyed by any component ID you choose) when creating the vault; see Create and Initialise Vault.
Back Up Consent History
affinidi_tdk_vault_iota’s consent records can participate in Vault backups without any custom Restorable code. FlutterSecureConsentStorage, from affinidi_tdk_vault_flutter_utils, implements both ConsentStorage (for IotaConsentRecordService) and Restorable. Register one instance in Vault.fromVaultStore as a named component, and inject the same instance into your IOTA services as ConsentStorage, so the two features share one store.
Import
import 'package:affinidi_tdk_vault/affinidi_tdk_vault.dart';
import 'package:affinidi_tdk_vault_flutter_utils/affinidi_tdk_vault_flutter_utils.dart';
import 'package:affinidi_tdk_vault_iota/affinidi_tdk_vault_iota.dart';Example
final consentStorage = FlutterSecureConsentStorage();
final vault = await Vault.fromVaultStore(
vaultStore,
profileRepositories: profileRepositories,
// Registering it here includes consent history in every backup and restore.
namedRestorables: {'consentHistory': consentStorage},
);
await vault.ensureInitialized();
// Inject the same instance into IotaConsentRecordService as its ConsentStorage.
final consentService = IotaConsentRecordService(
store: consentStorage,
cryptography: cryptography,
shareResponseService: responseService,
);Restoring a backup that includes consent history requires a matching entry in namedRestorableFactories, keyed by the same 'consentHistory' ID:
final restoredVault = await VaultBackupService().restoreBackup(
backupData: backupData,
passphrase: passphrase,
vaultStoreFactory: createVaultStore,
repositoryFactories: repositoryFactories,
namedRestorableFactories: {
'consentHistory': () => FlutterSecureConsentStorage(),
},
);Error Handling
All errors are thrown as TdkException, exposing the code that identifies the failure.
| Code | Thrown by | Description |
|---|---|---|
vault_not_initialized | Any profile, sharing, or storage-usage call before ensureInitialized(); Vault.fromVaultStore | The vault has not been initialised, or the VaultStore passed to fromVaultStore has no seed. |
invalid_profile_identifier | getProfileById, readSharedItem | No profile matches the given ID, no profiles exist for the current user, or a shared item’s owner profile cannot be found. |
invalid_profile_repository_identifier | Vault.fromVaultStore, repository lookups, createBackup | The named profile repository does not exist, defaultProfileRepositoryId does not match a supplied repository, or (during createBackup) a registered repository’s key does not match the repository’s own .id. |
invalid_file_storage_identifier | Profile.defaultFileStorageId setter | The assigned file storage ID does not match any file repository on the profile. |
invalid_credential_storage_identifier | Profile.defaultCredentialStorageId setter | The assigned credential storage ID does not match any credential repository on the profile. |
invalid_shared_storage_identifier | Profile.sharedStorage, addSharedStorage, removeSharedStorage | The requested shared storage does not exist, or one with the same ID already exists. |
missing_profile_repository | Vault.fromVaultStore | No profile repositories were supplied. |
unsupported_profile_access_sharing | shareProfile, addSharedProfile, acceptSharedItems, item permission calls | The profile’s repository does not implement ProfileAccessSharing. |
unsupported_profile_storage_usage_reporting | getStorageUsage | The profile’s repository does not support usage reporting. |
storage_limit_exceeded | createFile (via VfsProfileRepository) | The file exceeds the per-file upload cap, or uploading it would exceed the vault’s cumulative 500 MB storage cap. |
request_cancelled | Any call passed a cancelToken | The caller cancelled the operation via its VaultCancelToken. |
weak_passphrase | createBackup | The supplied passphrase fails the configured PassphrasePolicy. |
backup_creation_failed | createBackup | An unexpected error occurred while exporting or encrypting the backup. |
invalid_backup_format | createBackup, restoreBackup, discardInterruptedRestore, a Restorable implementation’s validateImport/import | The backup is malformed, uses an unsupported schema version, was tampered with, or its repository/named-component topology does not match what was registered. |
restore_destination_not_empty | restoreBackup | A restore destination, the VaultStore, a repository, or a named component, already contains data. |
restore_rollback_failed | restoreBackup, discardInterruptedRestore | A failed restore, or discardInterruptedRestore, could not fully clean up one or more destinations; the message lists which. |
Related
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.