$ npm install @tetherto/wdk-react-native-secure-storageSecure storage abstractions for React Native - provides secure storage for sensitive data (encrypted seeds, keys) using react-native-keychain.
npm install @tetherto/wdk-react-native-secure-storage
If the package is not yet published to npm, you can install directly from GitHub:
npm install https://github.com/tetherto/wdk-react-native-secure-storage.git
Or add to your package.json:
{
"dependencies": {
"@tetherto/wdk-react-native-secure-storage": "github:tetherto/wdk-react-native-secure-storage"
}
}
Then run npm install.
npm install react-native@">=0.70.0"
import { createSecureStorage } from '@tetherto/wdk-react-native-secure-storage'
// Create storage instance
const storage = createSecureStorage()
// Store encryption key
await storage.setEncryptionKey('my-encryption-key', 'user@example.com')
// Retrieve encryption key
const key = await storage.getEncryptionKey('user@example.com')
if (key) {
console.log('Key retrieved:', key)
}
// Store encrypted seed
await storage.setEncryptedSeed('encrypted-seed-data', 'user@example.com')
// Store encrypted entropy
await storage.setEncryptedEntropy('encrypted-entropy-data', 'user@example.com')
// Get all encrypted data
const allData = await storage.getAllEncrypted('user@example.com')
console.log('All data:', allData)
// Check if wallet exists
const exists = await storage.hasWallet('user@example.com')
// Delete wallet
await storage.deleteWallet('user@example.com')
import { createSecureStorage, defaultLogger, LogLevel } from '@tetherto/wdk-react-native-secure-storage'
// Configure logger
defaultLogger.setLevel(LogLevel.INFO)
// Create storage with custom options
const storage = createSecureStorage({
logger: customLogger, // Optional custom logger
authentication: {
promptMessage: 'Authenticate to access your wallet',
cancelLabel: 'Cancel',
disableDeviceFallback: false,
},
timeoutMs: 30000, // 30 seconds default
})
// Use storage
await storage.setEncryptionKey('key', 'user@example.com')
import {
createSecureStorage,
ValidationError,
KeychainWriteError,
KeychainReadError,
AuthenticationError,
TimeoutError,
} from '@tetherto/wdk-react-native-secure-storage'
const storage = createSecureStorage()
try {
await storage.setEncryptionKey('my-key', 'user@example.com')
} catch (error) {
if (error instanceof ValidationError) {
console.error('Invalid input:', error.message)
} else if (error instanceof KeychainWriteError) {
console.error('Failed to write to keychain:', error.message)
} else if (error instanceof TimeoutError) {
console.error('Operation timed out:', error.message)
} else {
console.error('Unexpected error:', error)
}
}
try {
const key = await storage.getEncryptionKey('user@example.com')
if (!key) {
console.log('Key not found')
}
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Authentication failed:', error.message)
} else if (error instanceof KeychainReadError) {
console.error('Failed to read from keychain:', error.message)
}
}
The identifier parameter allows you to support multiple wallets:
// Store data for different users
await storage.setEncryptionKey('key1', 'user1@example.com')
await storage.setEncryptionKey('key2', 'user2@example.com')
// Retrieve specific user's data
const key1 = await storage.getEncryptionKey('user1@example.com')
const key2 = await storage.getEncryptionKey('user2@example.com')
createSecureStorage(options?)Creates a new instance of secure storage. Each call returns a new instance with the specified options. For most apps, you should create one instance and reuse it throughout your application.
Options:
logger?: Logger - Custom logger instanceauthentication?: AuthenticationOptions - Authentication prompt configurationtimeoutMs?: number - Timeout for keychain operations (default: 30000ms, min: 1000ms, max: 300000ms)Returns: SecureStorage instance
SecureStorage InterfacesetEncryptionKey(key: string, identifier?: string): Promise<void>Stores an encryption key securely.
Parameters:
key: string - The encryption key (max 10KB, non-empty)identifier?: string - Optional identifier for multiple wallets (max 256 chars)Throws:
ValidationError - If input is invalidKeychainWriteError - If keychain operation failsTimeoutError - If operation times outgetEncryptionKey(identifier?: string): Promise<string | null>Retrieves an encryption key.
Parameters:
identifier?: string - Optional identifierReturns: The encryption key or null if not found
Throws:
ValidationError - If identifier is invalidAuthenticationError - If authentication failsKeychainReadError - If keychain operation failsTimeoutError - If operation times outsetEncryptedSeed(encryptedSeed: string, identifier?: string): Promise<void>Stores encrypted seed data.
getEncryptedSeed(identifier?: string): Promise<string | null>Retrieves encrypted seed data.
setEncryptedEntropy(encryptedEntropy: string, identifier?: string): Promise<void>Stores encrypted entropy data.
getEncryptedEntropy(identifier?: string): Promise<string | null>Retrieves encrypted entropy data.
getAllEncrypted(identifier?: string): Promise<{encryptedSeed: string | null, encryptedEntropy: string | null, encryptionKey: string | null}>Retrieves all encrypted wallet data at once.
hasWallet(identifier?: string): Promise<boolean>Checks if wallet credentials exist.
deleteWallet(identifier?: string): Promise<void>Deletes all wallet credentials.
Throws:
ValidationError - If identifier is invalidSecureStorageError - If deletion fails (with details of which items failed)TimeoutError - If operation times outisBiometricAvailable(): Promise<boolean>Checks if biometric authentication is available.
authenticate(): Promise<boolean>Authenticates with biometrics. Returns true if successful, false otherwise.
Throws:
AuthenticationError - If authentication failsThe module provides a Logger interface for structured logging. The default logger can be configured:
import { defaultLogger, LogLevel } from '@tetherto/wdk-react-native-secure-storage'
// Set the minimum log level (logs below this level will be ignored)
defaultLogger.setLevel(LogLevel.INFO)
// Available log levels: DEBUG, INFO, WARN, ERROR
You can also provide a custom logger that implements the Logger interface:
const customLogger: Logger = {
debug: (message, context) => { /* ... */ },
info: (message, context) => { /* ... */ },
warn: (message, context) => { /* ... */ },
error: (message, error, context) => { /* ... */ },
setLevel: (level) => { /* optional */ },
}
const storage = createSecureStorage({ logger: customLogger })
The module has no shared state or cleanup requirements. Each storage instance is independent and can be used without any module-level lifecycle management.
SecureStorageError - Base error classKeychainError - Keychain operation errorsKeychainWriteError - Keychain write failuresKeychainReadError - Keychain read failuresAuthenticationError - Authentication failuresValidationError - Input validation failuresTimeoutError - Operation timeout errors# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Type checking
npm run typecheck
# Linting
npm run lint
npm run lint:fix
# Formatting
npm run format
npm run format:check
Note: ESLint and Prettier are configured but need to be installed as dev dependencies:
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier
This module is production-ready and includes:
✅ Built and tested - All code is compiled to JavaScript with TypeScript definitions
✅ Proper exports - Only necessary files are included in the npm package
✅ Security hardened - Input validation and secure storage with device-level protections
✅ Error handling - Comprehensive error types for all failure scenarios
✅ Logging - Structured logging with configurable levels (defaults to ERROR in production)
✅ Type safety - Full TypeScript support with exported types
✅ Documentation - Complete API documentation and usage examples
Configure Logging: Set appropriate log levels for your environment:
import { defaultLogger, LogLevel } from '@tetherto/wdk-react-native-secure-storage'
// In production, only log errors and warnings
defaultLogger.setLevel(LogLevel.WARN)
// In development, you might want more verbose logging
if (__DEV__) {
defaultLogger.setLevel(LogLevel.DEBUG)
}
Error Handling: Always handle errors appropriately:
try {
await storage.setEncryptionKey(key, identifier)
} catch (error) {
// Log error to your error tracking service (e.g., Sentry)
// Never log sensitive data like keys or seeds
if (error instanceof ValidationError) {
// Handle validation errors
} else if (error instanceof KeychainWriteError) {
// Handle keychain errors
}
}
Single Instance: Create one storage instance and reuse it:
// Good: Create once and reuse
const storage = createSecureStorage({ logger: customLogger })
// Avoid: Creating multiple instances unnecessarily
⚠️ Important Security Notes:
Timeout Resource Usage: The timeout implementation uses Promise.race() which does NOT cancel the underlying keychain operation. The operation continues executing even after timeout, though its result is ignored. This means:
Device Authentication: On devices without authentication (no PIN/password/biometrics), data is still encrypted at rest but accessible when the device is unlocked. This is a limitation of the underlying platform security model.
Device-Level Rate Limiting: The module relies on device-level keychain/keystore rate limiting and lockout mechanisms. These are more robust than app-level rate limiting and persist across app restarts.
Contributions are welcome! Please follow these steps:
git checkout -b feature/amazing-feature)npm test)npm run typecheck)npm run lint)npm run format)npm run build)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)# Clone the repository
git clone https://github.com/tetherto/wdk-react-native-secure-storage.git
cd wdk-react-native-secure-storage
# Install dependencies
npm install
# Run tests
npm test
# Run type checking
npm run typecheck
# Run linting
npm run lint
# Format code
npm run format
# Build
npm run build
Apache-2.0