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).
- 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
@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.
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.
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: '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());
})();
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.
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: '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
.
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: '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.');
}
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 |
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.
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.
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!