@empe/wallet-core v3.4.0
This guide explains how to get started with the @empe/wallet-core library for managing decentralized identity (DID, Verifiable Credentials, etc.) in a Node.js or any JavaScript/TypeScript environment (without React Native requirements).
Table of Contents
- Introduction
- Installation
- Basic Usage
- Creating a DID Document
- Issuer Flow (ClaimCredentialFlow)
- Verifier Flow (CredentialPresentationFlow)
- Backup and Restore Functionality
- APIs and Adapters
- Errors and Exception Handling
- Support and Contribution
Introduction
@empe/wallet-core is a TypeScript/JavaScript library that provides:
- Creation and storage of DID Documents.
- Generation/import of seeds (mnemonics).
- Key and signing management (KeyStorage).
- Handling Verifiable Credentials (VCs).
- Ready-made flow classes for issuance and verification processes:
- ClaimCredentialFlow – receiving a credential from an external issuer.
- CredentialPresentationFlow – presenting credentials to a verifier.
The library simplifies integration with the DID ecosystem and W3C standards such as DIDs, VCs, and Verifiable Presentations.
Installation
npm install @empe/wallet-core
# or
yarn add @empe/wallet-core
Note: This library is not specific to React or React Native. You can use it in any JS/TS environment.
Basic Usage
Below is a sample showing how to configure WalletCore
and create your first DID Document:
import {
WalletCore,
ClaimCredentialFlow, // Flow for receiving credentials
CredentialPresentationFlow, // Flow for presenting credentials
} from '@empe/wallet-core';
import {
IDidDocumentStorageAdapter,
IKeyStorageAdapter,
ISecretStorageAdapter,
IVerifiableCredentialStorageAdapter,
} from '@empe/wallet-core';
/**
* Sample in-memory adapters for testing purposes.
* In production, replace these with a database, files, or another storage solution.
*/
class InMemoryDidDocumentStorage implements IDidDocumentStorageAdapter {
private store = new Map<string, any>();
async add(network: string, value: any) {
this.store.set(network, value);
}
async get(network: string) {
return this.store.get(network) ?? null;
}
}
class InMemoryVCStorage implements IVerifiableCredentialStorageAdapter {
private vcs = new Map<string, any>();
async add(key: string, value: any) {
this.vcs.set(key, value);
}
async getAll() {
return [...this.vcs.values()];
}
}
class InMemorySecretStorage implements ISecretStorageAdapter {
private store = new Map<string, string>();
async add(key: string, value: string) {
this.store.set(key, value);
}
async get(key: string) {
return this.store.get(key) ?? null;
}
}
class InMemoryKeyStorage implements IKeyStorageAdapter {
private store = new Map<string, any>();
async add(k: string, v: any) {
this.store.set(k, v);
}
async get(k: string) {
return this.store.get(k) ?? null;
}
async getAll() {
return [];
}
async has(k: string) {
return this.store.has(k);
}
async remove(k: string) {
this.store.delete(k);
}
}
// Create instances of our adapters
const didDocumentStorage = new InMemoryDidDocumentStorage();
const vcStorage = new InMemoryVCStorage();
const seedStorage = new InMemorySecretStorage();
const keyStorage = new InMemoryKeyStorage();
// Initialize WalletCore
const walletCore = new WalletCore({
network: Network.TESTNET,
didDocumentStorage,
verifiableCredentialStorage: vcStorage,
seedStorage,
secretStorage: keyStorage,
});
// Example usage
(async () => {
// 1) Generate a seed
const { seed, mnemonics } = WalletCore.generateSeed();
console.log('Generated seed:', seed);
console.log('Mnemonic phrase:', mnemonics);
// 2) Create a DID Document
const didDoc = await walletCore.createDidDocument(seed);
console.log('DID Document:', didDoc.id().toString());
// 3) Retrieve the same DID Document
const sameDoc = await walletCore.getDidDocument('testnet');
console.log('Retrieved DID Document:', sameDoc.id().toString());
})();
Creating a DID Document
The createDidDocument(seed)
method creates a new DID Document based on the provided seed (hex string). Internally, it uses an HDKeyFactory
from @empe/identity
to generate keys. Example:
const { seed } = WalletCore.generateSeed();
const document = await walletCore.createDidDocument(seed);
console.log('DID:', document.id().toString());
After creation, the document is stored using the didDocumentStorage
adapter.
Issuer Flow (ClaimCredentialFlow)
ClaimCredentialFlow
is used to receive a credential from an external issuer. Typically, you start by scanning a QR code or fetching issuer endpoint data.
import { ClaimCredentialFlow } from '@empe/wallet-core';
// ...
const data = {
credential_id: 'BasicCredential',
credential_issuer: 'https://issuer.example.com',
credential_configuration_ids: ['...'],
display: { name: 'Example Issuer', locale: 'en-US', description: 'Sample' },
};
const claimFlow = new ClaimCredentialFlow({
data,
network: Network.TESTNET,
didDocumentStorage,
verifiableCredentialStorage: vcStorage,
secretStorage: keyStorage,
});
await claimFlow.process({
confirmCredentialClaim: async offering => {
// Show the user info about the credential (offering)
// Wait for them to confirm "Yes, I want to claim it"
console.log('Receiving credential from:', offering.credential_issuer);
},
});
If the process succeeds, the new credential (VC) is stored in your verifiableCredentialStorage
.
Verifier Flow (CredentialPresentationFlow)
CredentialPresentationFlow
is used to present your stored credentials (VCs) to a verifier. It also typically starts with a QR code that provides verifier data.
import { CredentialPresentationFlow } from '@empe/wallet-core';
const verifierData = {
client_id: 'verifier.example.com',
presentation_definition: {
input_descriptors: [
// Verifier requirements
],
},
state: 'abc123',
nonce: 'xyz789',
response_uri: 'https://verifier.example.com/submit',
// ...
};
const presentationFlow = new CredentialPresentationFlow({
qrData: verifierData,
network: Network.TESTNET,
didDocumentStorage,
verifiableCredentialStorage: vcStorage,
secretStorage: keyStorage,
});
const success = await presentationFlow.process({
onNoMatchingCredentials: async () => {
console.log('No local VCs match the verifier requirements.');
},
onUserConsent: async matchedVCs => {
// Prompt user to choose which of matchedVCs to present
console.log('Found matched VCs:', matchedVCs);
return matchedVCs; // present all of them
},
});
if (success) {
console.log('Successfully presented credentials to the verifier.');
}
Backup and Restore Functionality
BackupFlowManager
enables users to securely backup and restore their wallet data (DID documents, verifiable credentials, and cryptographic keys). This functionality is crucial for wallet recovery and cross-device synchronization.
import { BackupFlowManager } from '@empe/wallet-core';
// Initialize the backup manager
const backupFlowManager = new BackupFlowManager(
backupManager, // IBackupManager implementation
backupStorage, // IBackupStorage implementation
didDocumentStorage, // For storing DID documents
vcStorage, // For storing verifiable credentials
keyStorage, // For managing cryptographic keys
secretStorage // For storing sensitive data
);
// Create and export a backup
async function createBackup() {
const backupPath = await backupFlowManager.exportBackup(
'my-wallet-backup.json', // Filename for the backup
null, // Optional encryption key (uses wallet key if null)
{
// Optional callbacks for progress feedback
onBackupStarted: () => {
console.log('Backup process started');
},
onBackupProgress: progress => {
console.log(`Backup progress: ${progress}%`);
},
onBackupCompleted: backupPath => {
console.log(`Backup completed and saved to: ${backupPath}`);
},
}
);
return backupPath;
}
// Restore from backup file using mnemonic
async function restoreFromBackup(backupFilePath, mnemonic) {
const restoredData = await backupFlowManager.importFromFile(backupFilePath, mnemonic, {
// Optional callbacks for the restore process
onImportStarted: () => {
console.log('Import process started');
},
onImportProgress: progress => {
console.log(`Import progress: ${progress}%`);
},
onImportCompleted: restoredData => {
console.log('Import completed successfully', restoredData);
},
onImportFailed: error => {
console.error('Import failed:', error);
},
});
return restoredData;
}
// Restore using just a mnemonic (without a backup file)
async function restoreFromMnemonic(mnemonic) {
const restoredData = await backupFlowManager.importFromMnemonic(mnemonic, {
onBackupFileNotFound: async () => {
// This callback is triggered when no backup file is found
// You can prompt the user to provide a backup file path or return null
// to continue without a backup file
return null;
},
onInvalidMnemonic: async () => {
// This callback is triggered when the provided mnemonic is invalid
// You can prompt the user to provide a valid mnemonic or handle the error
console.error('Invalid mnemonic provided');
return await promptUserForValidMnemonic(); // Example function
},
onTypeDetected: didType => {
console.log(`DID type detected: ${didType}`);
},
onRestoreOffChainWithoutBackup: async () => {
// This callback is triggered when attempting to restore off-chain data without a backup file
// Return true to continue with off-chain restoration, false to abort
return await promptUserToConfirmOffChainRestore(); // Example function
},
onRestoreOnChainWithoutBackup: async () => {
// This callback is triggered when attempting to restore on-chain data without a backup file
// Return true to continue with on-chain restoration, false to abort
return await promptUserToConfirmOnChainRestore(); // Example function
},
});
return restoredData;
}
The BackupFlowCallbacks
interface provides a rich set of hooks for managing the backup and restore user experience:
Callback | Description |
---|---|
onBackupStarted | Called when a backup process begins |
onBackupProgress | Called periodically with progress percentage during backup |
onBackupCompleted | Called when backup completes successfully with the backup file path |
onImportStarted | Called when a restore/import process begins |
onBackupFileNotFound | Called when no backup file is found during restore; should return a file path or null |
onInvalidMnemonic | Called when the provided mnemonic is invalid; should return a valid mnemonic or null |
onTypeDetected | Called when the DID type is detected during restore |
onRestoreOffChainWithoutBackup | Called when attempting off-chain restore without backup; should return boolean |
onRestoreOnChainWithoutBackup | Called when attempting on-chain restore without backup; should return boolean |
onImportProgress | Called periodically with progress percentage during import/restore |
onImportCompleted | Called when import/restore completes successfully with restored data |
onImportFailed | Called when import/restore fails with the error |
APIs and Adapters
The library offers several interfaces for integrating with your storage system:
- IDidDocumentStorageAdapter – manages storing a DID Document per network.
- IVerifiableCredentialStorageAdapter – manages storing one or more VCs.
- ISecretStorageAdapter – stores seeds, typically tied to the DID (or another key).
- IKeyStorageAdapter – manages the keys (and includes signing logic).
In files like api.ts
, issuer-flow.ts
, and verifier-flow.ts
, you’ll find functions and classes that handle the HTTP communication with issuers and verifiers.
Errors and Exception Handling
The library uses custom error classes (WalletCoreSDKError
) for more descriptive exception handling (see error.ts
). HTTP functions rely on toNormalizedError
to convert errors (e.g., from Axios) into a consistent format.
Support and Contribution
If you have feedback, encounter bugs, or want to propose improvements to @empe/wallet-core, please open an issue or submit a pull request in the project repository. Every bit of help is appreciated!