Overview
SubWallet Extension uses a store-based state management system built on Chrome storage API and RxJS Subjects. The system provides persistent storage with reactive updates across the extension.
Store Architecture
Base Store Classes
BaseStore
Foundation class providing Chrome storage integration.
abstract class BaseStore<T> {
constructor(prefix: string | null);
get(key: string, update: (value: T) => void): void;
set(key: string, value: T, update?: () => void): void;
remove(key: string, update?: () => void): void;
all(update: (key: string, value: T) => void): void;
allMap(update: (value: Record<string, T>) => void): void;
getPrefix(): string;
}
Storage key prefix to namespace store data
SubscribableStore
Extends BaseStore with RxJS Subject for reactive updates.
abstract class SubscribableStore<T> extends BaseStore<T> {
getSubject(): Subject<T>;
asyncGet(key: string): Promise<T>;
removeAll(): void;
set(key: string, value: T, update?: () => void): void; // Emits to subject
}
Core Stores
AccountsStore
Manages keyring account data.
import { AccountsStore } from '@subwallet/extension-base/stores';
class AccountsStore extends BaseStore<KeyringJson> implements KeyringStore {
constructor();
}
Usage
const accountsStore = new AccountsStore();
// Get account
accountsStore.get('account:0x123...', (account) => {
console.log('Account:', account);
});
// Set account
accountsStore.set('account:0x123...', keyringJson);
// Get all accounts
accountsStore.allMap((accounts) => {
Object.entries(accounts).forEach(([key, value]) => {
console.log(key, value);
});
});
CurrentAccountStore
Manages the currently selected account.
import { CurrentAccountStore } from '@subwallet/extension-base/stores';
class CurrentAccountStore extends SubscribableStore<CurrentAccountInfo> {
constructor();
}
interface CurrentAccountInfo {
address: string;
}
Usage
const currentAccountStore = new CurrentAccountStore();
// Subscribe to current account changes
currentAccountStore.getSubject().subscribe((accountInfo) => {
console.log('Current account:', accountInfo.address);
});
// Set current account
currentAccountStore.set('current', { address: '0x123...' });
// Get current account asynchronously
const currentAccount = await currentAccountStore.asyncGet('current');
SettingsStore
Manages user preferences and settings.
import SettingsStore from '@subwallet/extension-base/stores/Settings';
class SettingsStore extends SubscribableStore<RequestSettingsType> {
constructor();
}
Usage
const settingsStore = new SettingsStore();
// Subscribe to settings changes
settingsStore.getSubject().subscribe((settings) => {
console.log('Settings updated:', settings);
});
// Update settings
settingsStore.set('user-settings', {
theme: 'dark',
language: 'en',
currency: 'usd'
});
AssetSettingStore
Manages visibility and configuration of assets/tokens.
import AssetSettingStore from '@subwallet/extension-base/stores/AssetSetting';
class AssetSettingStore extends SubscribableStore<Record<string, AssetSetting>> {
constructor();
}
interface AssetSetting {
visible: boolean;
// ... other asset settings
}
Usage
const assetSettingStore = new AssetSettingStore();
// Get all asset settings
const settings = await assetSettingStore.asyncGet('asset-settings');
// Update specific asset visibility
assetSettingStore.set('asset-settings', {
...settings,
'polkadot-dot': { visible: true },
'kusama-ksm': { visible: false }
});
ChainlistStore
Manages chain configuration and patch versions.
import ChainlistStore from '@subwallet/extension-base/stores/ChainlistStore';
interface ChainlistConfig {
patchVersion: string;
}
class ChainlistStore extends SubscribableStore<ChainlistConfig> {
constructor();
}
Usage
const chainlistStore = new ChainlistStore();
chainlistStore.set('config', { patchVersion: '1.2.3' });
Manages chain metadata.
import { MetadataStore } from '@subwallet/extension-base/stores';
class MetadataStore extends BaseStore<MetadataDef> {
constructor();
}
CurrentCurrencyStore
Manages the selected fiat currency for price display.
import { CurrentCurrencyStore } from '@subwallet/extension-base/stores';
class CurrentCurrencyStore extends SubscribableStore<CurrencyType> {
constructor();
}
Store Methods
BaseStore Methods
get
Retrieve a value from storage.
get(key: string, update: (value: T) => void): void
Storage key (without prefix)
Callback receiving the stored value
Example:
store.get('my-key', (value) => {
console.log('Value:', value);
});
set
Store a value in storage.
set(key: string, value: T, update?: () => void): void
Storage key (without prefix)
Optional callback after storage completes
Example:
store.set('my-key', { data: 'value' }, () => {
console.log('Saved!');
});
remove
Remove a value from storage.
remove(key: string, update?: () => void): void
Optional callback after removal
Example:
store.remove('my-key', () => {
console.log('Removed!');
});
all
Iterate over all stored values.
all(update: (key: string, value: T) => void): void
Example:
store.all((key, value) => {
console.log(`${key}:`, value);
});
allMap
Retrieve all values as a single object.
allMap(update: (value: Record<string, T>) => void): void
Example:
store.allMap((allValues) => {
console.log('All data:', allValues);
});
SubscribableStore Methods
getSubject
Get the RxJS Subject for reactive updates.
Returns: RxJS Subject<T>
Example:
const subscription = store.getSubject().subscribe((value) => {
console.log('Updated:', value);
});
// Cleanup
subscription.unsubscribe();
asyncGet
Promise-based retrieval.
asyncGet(key: string): Promise<T>
Returns: Promise<T> resolving to stored value
Example:
const value = await store.asyncGet('my-key');
console.log('Value:', value);
removeAll
Remove all items from the store.
Example:
Storage Keys
Stores use prefixed keys to avoid collisions:
const EXTENSION_PREFIX = 'subwallet-';
// Example keys:
// subwallet-accounts:account:0x123...
// subwallet-current_account:current
// subwallet-settings:user-settings
// subwallet-asset-setting:asset-settings
Reactive Patterns
React Hook Integration
import { useEffect, useState } from 'react';
import { CurrentAccountStore } from '@subwallet/extension-base/stores';
function useCurrentAccount() {
const [account, setAccount] = useState<CurrentAccountInfo | null>(null);
useEffect(() => {
const store = new CurrentAccountStore();
const subscription = store.getSubject().subscribe(setAccount);
// Load initial value
store.get('current', setAccount);
return () => subscription.unsubscribe();
}, []);
return account;
}
Observable Chains
import { map, filter } from 'rxjs/operators';
const store = new SettingsStore();
store.getSubject().pipe(
filter(settings => settings.theme !== undefined),
map(settings => settings.theme)
).subscribe(theme => {
console.log('Theme changed:', theme);
});
Best Practices
-
Use SubscribableStore for reactive data: Prefer
SubscribableStore when you need to react to changes across the extension
-
Cleanup subscriptions: Always unsubscribe from RxJS Subjects to prevent memory leaks
const subscription = store.getSubject().subscribe(...);
// Later:
subscription.unsubscribe();
- Namespace your keys: Use consistent key naming conventions
// Good
store.set('user-settings', data);
store.set('network-config', data);
// Bad
store.set('settings', data);
store.set('config', data);
- Use asyncGet for cleaner code: Prefer
asyncGet over callback-based get when possible
// Async/await
const value = await store.asyncGet('key');
// vs callback
store.get('key', (value) => {
// nested callback hell
});
- Handle storage errors: Chrome storage can fail; always handle errors
try {
const value = await store.asyncGet('key');
} catch (error) {
console.error('Storage error:', error);
}
Storage Limits
Chrome extension storage has limits:
- Local storage: 5MB limit (can request unlimited storage permission)
- Sync storage: 100KB limit with 8KB per item
- Session storage: 10MB limit
SubWallet uses chrome.storage.local for most data.