@subwallet/ui-keyring
v0.1.14
Published
Basic packages of SubWallet
Readme
@subwallet/ui-keyring
A comprehensive UI layer for managing cryptographic keypairs with persistent storage, reactive observables, and advanced keyring management features. This library provides a high-level interface over @subwallet/keyring with browser/Node.js storage capabilities and reactive state management.
Overview
The @subwallet/ui-keyring serves as the bridge between the low-level cryptographic operations of @subwallet/keyring and user interface applications. It provides persistent storage, reactive observables for state management, and a comprehensive API for account lifecycle management.
Architecture Overview
%%{init: {"theme": "dark"}}%%
graph LR
A["@subwallet/ui-keyring"] --> B["Storage Layer"]
A --> C["Observable Layer"]
A --> D["Keyring Management"]
A --> E["Options & Filtering"]
%% Storage Layer
B --> F["BrowserStore<br/>localStorage/sessionStorage"]
B --> G["FileStore<br/>File System (Node.js)"]
B --> H["PasswordStore<br/>Master Password Storage"]
B --> I["Custom Store<br/>Database/Cloud Storage"]
%% Observable Layer
C --> J["Accounts Observable<br/>RxJS BehaviorSubject"]
C --> K["Addresses Observable<br/>Watch-only Accounts"]
C --> L["Contracts Observable<br/>Smart Contracts"]
C --> M["Combined Observable<br/>All Entities"]
%% Keyring Management
D --> N["Account Creation<br/>Mnemonic/Private Key"]
D --> O["Account Restoration<br/>JSON/Backup Files"]
D --> P["Master Password<br/>Encryption/Decryption"]
D --> Q["Multi-Chain Support<br/>Substrate/EVM/Bitcoin/TON"]
%% Options & Filtering
E --> R["Filtered Options<br/>Development/Production"]
E --> S["Grouped Options<br/>Accounts/Addresses/Contracts"]
E --> T["Recent Addresses<br/>Transaction History"]
E --> U["Testing Mode<br/>Development Accounts"]
%% Core Keyring Connection
D --> V["@subwallet/keyring<br/>Core Cryptographic Operations"]
V --> W["sr25519/ed25519/ecdsa<br/>Substrate Keypairs"]
V --> X["ethereum<br/>EVM Keypairs"]
V --> Y["bitcoin-*/bittest-*<br/>Bitcoin Keypairs"]
V --> Z["ton/ton-native<br/>TON Keypairs"]
V --> AA["cardano<br/>Cardano Keypairs"]
%% Styling
classDef storage fill:#1a237e,stroke:#3f51b5,stroke-width:2px
classDef observable fill:#2e7d32,stroke:#4caf50,stroke-width:2px
classDef management fill:#e65100,stroke:#ff9800,stroke-width:2px
classDef options fill:#4a148c,stroke:#9c27b0,stroke-width:2px
classDef keyring fill:#b71c1c,stroke:#f44336,stroke-width:2px
class B,F,G,H,I storage
class C,J,K,L,M observable
class D,N,O,P,Q management
class E,R,S,T,U options
class V,W,X,Y,Z,AA keyringCore Concepts
1. Storage Abstraction
The ui-keyring provides pluggable storage backends:
- BrowserStore: Uses browser localStorage for web applications
- FileStore: Uses file system for Node.js applications
- PasswordStore: Manages master password storage
- Custom Store: Allows integration with databases or cloud storage
2. Observable State Management
All keyring state is managed through RxJS observables:
- Reactive Updates: UI components automatically update when accounts change
- Filtered Views: Different views for accounts, addresses, and contracts
- Combined Observables: Unified state management across all entity types
3. Account Lifecycle Management
Complete account management from creation to deletion:
- Creation: From mnemonic, private key, or hardware wallet
- Storage: Encrypted storage with master password support
- Restoration: From backup files or browser storage
- Migration: Seamless upgrades between keyring versions
Main UI Keyring Workflow
%%{init: {"theme": "dark"}}%%
flowchart TD
A["Application Start"] --> B["Initialize UI Keyring"]
B --> C["Load Storage Options"]
C --> D["Restore from Storage"]
D --> E["Setup Observables"]
E --> F["UI Ready"]
%% Account Creation Flow
F --> G["User Action:<br/>Create Account"]
G --> H["Generate Mnemonic<br/>or Import Key"]
H --> I["Create KeyringPair<br/>via @subwallet/keyring"]
I --> J["Encrypt with<br/>Master Password"]
J --> K["Save to Store"]
K --> L["Update Observable"]
L --> M["UI Updates<br/>Automatically"]
%% Account Restoration Flow
F --> N["User Action:<br/>Restore Accounts"]
N --> O["Load JSON Backup"]
O --> P["Decrypt with Password"]
P --> Q["Validate Account Data"]
Q --> R["Restore KeyringPairs"]
R --> S["Update Storage"]
S --> T["Update Observables"]
T --> U["UI Reflects Changes"]
%% Runtime Operations
F --> V["Runtime Operations"]
V --> W["Sign Transactions"]
V --> X["Derive Accounts"]
V --> Y["Manage Addresses"]
V --> Z["Export Backups"]
%% Storage Persistence
K --> AA["Storage Layer"]
S --> AA
AA --> BB["BrowserStore<br/>localStorage"]
AA --> CC["FileStore<br/>File System"]
AA --> DD["Custom Store<br/>Database"]
%% Observable Updates
L --> EE["Observable Layer"]
T --> EE
EE --> FF["Accounts Subject"]
EE --> GG["Addresses Subject"]
EE --> HH["Contracts Subject"]
EE --> II["Combined Observable"]
%% UI Components
M --> JJ["UI Components"]
U --> JJ
JJ --> KK["Account List"]
JJ --> LL["Transaction Forms"]
JJ --> MM["Settings Panel"]
%% Styling
classDef start fill:#1b5e20,stroke:#4caf50,stroke-width:2px
classDef action fill:#0d47a1,stroke:#2196f3,stroke-width:2px
classDef crypto fill:#e65100,stroke:#ff9800,stroke-width:2px
classDef storage fill:#4a148c,stroke:#9c27b0,stroke-width:2px
classDef ui fill:#b71c1c,stroke:#f44336,stroke-width:2px
class A,B,C,D,E,F start
class G,H,N,O,V,W,X,Y,Z action
class I,J,P,Q,R crypto
class K,S,AA,BB,CC,DD storage
class L,T,EE,FF,GG,HH,II,M,U,JJ,KK,LL,MM uiReal-World Usage Examples
1. Initialize UI Keyring
import { keyring } from '@subwallet/ui-keyring';
import { BrowserStore, PasswordBrowserStore } from '@subwallet/ui-keyring/stores';
// Initialize with browser storage
keyring.loadAll({
type: 'sr25519', // Default keyring type
isDevelopment: false,
store: new BrowserStore(),
password_store: new PasswordBrowserStore(),
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3' // Polkadot
});
// Setup master password
keyring.changeMasterPassword('your-secure-master-password');2. Create New Account
import { keyring } from '@subwallet/ui-keyring';
import { mnemonicGenerate } from '@polkadot/util-crypto';
// Generate new mnemonic
const mnemonic = mnemonicGenerate();
// Create account with master password encryption
const { pair, json } = keyring.addUri(mnemonic, {
name: 'My New Account',
whenCreated: Date.now()
});
console.log('Account created:', pair.address);
console.log('Encrypted JSON:', json);
// Account is automatically saved to storage and observable is updated3. Restore Accounts from Storage
import { keyring } from '@subwallet/ui-keyring';
// This happens automatically when keyring.loadAll() is called
// But you can also restore from backup files
// Restore from JSON backup
const backupJson = {
/* encrypted backup data */
};
keyring.restoreAccounts(backupJson, 'backup-password', [
/* optional: specific addresses to restore */
]);
// Or restore single account
const accountJson = {
/* single account JSON */
};
const restoredPair = keyring.restoreAccount(
accountJson,
'account-password',
true // use master password
);4. Subscribe to Account Changes
import { keyring } from '@subwallet/ui-keyring';
// Subscribe to all accounts
keyring.accounts.subject.subscribe((accounts) => {
console.log('Accounts updated:', Object.keys(accounts));
// Update UI components
updateAccountList(accounts);
});
// Subscribe to filtered options for dropdowns
keyring.keyringOption.optionsSubject.subscribe((options) => {
console.log('Account options:', options.account);
console.log('Address options:', options.address);
console.log('Contract options:', options.contract);
// Update UI dropdowns
updateAccountDropdown(options.account);
});5. Export Account Backups
// Backup single account
const pair = keyring.getPair('account-address');
const accountBackup = keyring.backupAccount(pair, 'export-password');
// Backup all accounts
const allAccountsBackup = await keyring.backupAccounts(
'master-password',
['address1', 'address2'] // optional: specific addresses
);
// Save to file or provide download
const backupBlob = new Blob([JSON.stringify(allAccountsBackup)], {
type: 'application/json'
});Advanced Features
1. Custom Store Implementation
Create a custom store for database or cloud storage:
import { KeyringStore, KeyringJson } from '@subwallet/ui-keyring/types';
export class DatabaseStore implements KeyringStore {
private db: Database;
constructor(database: Database) {
this.db = database;
}
public all(fn: (key: string, value: KeyringJson) => void): void {
this.db.query('SELECT key, value FROM keyring_accounts')
.then(rows => {
rows.forEach(row => {
fn(row.key, JSON.parse(row.value));
});
});
}
public get(key: string, fn: (value: KeyringJson) => void): void {
this.db.query('SELECT value FROM keyring_accounts WHERE key = ?', [key])
.then(result => {
if (result.length > 0) {
fn(JSON.parse(result[0].value));
}
});
}
public set(key: string, value: KeyringJson, fn?: () => void): void {
this.db.query(
'INSERT OR REPLACE INTO keyring_accounts (key, value) VALUES (?, ?)',
[key, JSON.stringify(value)]
).then(() => {
fn && fn();
});
}
public remove(key: string, fn?: () => void): void {
this.db.query('DELETE FROM keyring_accounts WHERE key = ?', [key])
.then(() => {
fn && fn();
});
}
}
// Use custom store
keyring.loadAll({
type: 'sr25519',
store: new DatabaseStore(myDatabase),
password_store: new DatabasePasswordStore(myDatabase)
});2. Multi-Chain Account Management
// Create accounts for different networks
const substrateAccount = keyring.addUri(mnemonic, { name: 'Substrate Account' }, 'sr25519');
const ethereumAccount = keyring.addUri(mnemonic, { name: 'Ethereum Account' }, 'ethereum');
const bitcoinAccount = keyring.addUri(mnemonic, { name: 'Bitcoin Account' }, 'bitcoin-84');
// Filter accounts by network
const accounts = keyring.getAccounts();
const substrateAccounts = accounts.filter(acc => ['sr25519', 'ed25519', 'ecdsa'].includes(acc.type));
const ethereumAccounts = accounts.filter(acc => acc.type === 'ethereum');
const bitcoinAccounts = accounts.filter(acc => acc.type?.startsWith('bitcoin'));3. Development Mode and Testing
// Enable development mode
keyring.setDevMode(true);
// Create test accounts (only visible in development)
const testAccount = keyring.addUri('//Alice', {
name: 'Alice (Development)',
isTesting: true
});
// Test accounts are automatically filtered in production
keyring.setDevMode(false);
const productionAccounts = keyring.getAccounts(); // testAccount not included4. Address Management
// Save frequently used addresses
keyring.saveAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', {
name: 'Polkadot Treasury',
isRecent: false
});
// Mark recent addresses (automatically expire after 24 hours)
keyring.saveRecent('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
// Manage smart contracts
keyring.saveContract('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', {
name: 'My Smart Contract',
contract: {
abi: 'contract-abi-json',
genesisHash: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
}
});Integration with UI Frameworks
React Integration
import { useEffect, useState } from 'react';
import { keyring } from '@subwallet/ui-keyring';
function useKeyringAccounts() {
const [accounts, setAccounts] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
const subscription = keyring.accounts.subject.subscribe((accounts) => {
setAccounts(accounts);
setLoading(false);
});
return () => subscription.unsubscribe();
}, []);
return { accounts, loading };
}
// Usage in component
function AccountList() {
const { accounts, loading } = useKeyringAccounts();
if (loading) return <div>Loading accounts...</div>;
return (
<div>
{Object.values(accounts).map(account => (
<div key={account.json.address}>
{account.json.meta.name} - {account.json.address}
</div>
))}
</div>
);
}Vue Integration
import { ref, onMounted, onUnmounted } from 'vue';
import { keyring } from '@subwallet/ui-keyring';
export function useKeyringAccounts() {
const accounts = ref({});
const loading = ref(true);
let subscription = null;
onMounted(() => {
subscription = keyring.accounts.subject.subscribe((accountsData) => {
accounts.value = accountsData;
loading.value = false;
});
});
onUnmounted(() => {
if (subscription) {
subscription.unsubscribe();
}
});
return { accounts, loading };
}API Reference
Core Methods
// Account Management
keyring.addUri(suri: string, meta?: KeyringPair$Meta, type?: KeypairType): CreateResult
keyring.addExternal(address: string, meta?: KeyringPair$Meta): CreateResult
keyring.addPair(pair: KeyringPair, withMasterPassword: boolean, password?: string): CreateResult
// Backup & Restore
keyring.backupAccount(pair: KeyringPair, password: string): KeyringPair$Json
keyring.backupAccounts(password: string, addresses?: string[]): Promise<KeyringPairs$Json>
keyring.restoreAccount(json: KeyringPair$Json, password: string, withMasterPassword: boolean): KeyringPair
keyring.restoreAccounts(json: EncryptedJson, password: string, addresses?: string[]): void
// Master Password
keyring.changeMasterPassword(newPassphrase: string, oldPassphrase?: string): void
keyring.unlockKeyring(password: string): void
keyring.lockAll(isLockKering?: boolean): void
// Account Queries
keyring.getAccounts(): KeyringAddress[]
keyring.getAccount(address: string): KeyringAddress | undefined
keyring.getPair(address: string): KeyringPair
keyring.isAvailable(address: string): boolean
// Storage Management
keyring.loadAll(options: KeyringOptions): void
keyring.resetWallet(resetAll: boolean): voidObservable Subjects
// Account observables
keyring.accounts.subject: BehaviorSubject<SubjectInfo>
keyring.addresses.subject: BehaviorSubject<SubjectInfo>
keyring.contracts.subject: BehaviorSubject<SubjectInfo>
// Options observable
keyring.keyringOption.optionsSubject: BehaviorSubject<KeyringOptions>Installation
npm install @subwallet/ui-keyringDependencies
- @subwallet/keyring: Core cryptographic operations
- @polkadot/ui-settings: Network configuration
- @polkadot/util & @polkadot/util-crypto: Utilities and cryptography
- rxjs: Reactive programming with observables
- store: Browser storage abstraction
Security Best Practices
- Master Password: Always use a strong master password for production applications
- Storage Security: Consider using encrypted storage for sensitive data
- Memory Management: Pairs are automatically locked when not in use
- Network Isolation: Different networks are isolated in storage
- Development Mode: Never enable development mode in production
Contributing
Contributions are welcome! Please ensure:
- All tests pass
- New features include comprehensive tests
- Documentation is updated
- Security implications are considered
- Observable patterns are maintained
License
Apache-2.0
