Package Exports
- p-sdk-wallet
- p-sdk-wallet/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (p-sdk-wallet) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
p-sdk-wallet
A comprehensive, secure, and easy-to-use wallet SDK for React Native applications from PWC.
This SDK provides a high-level Vault API to manage multiple accounts (from mnemonic or private keys) under a single password, similar to MetaMask. It's built with high security in mind, using AES and PBKDF2 for strong encryption.
Features
- 🔐 High Security: Uses AES and PBKDF2 for strong encryption of the entire vault.
- 🏛️ Vault Architecture: Manages multiple accounts under a single password.
- 🔑 Multi-Account: Supports HD wallets (BIP-44) and imported private key accounts.
- 🌐 Multi-Chain: Ready for any EVM-compatible chain.
- 👨💻 Dev-Friendly API: A simple, high-level API that abstracts away cryptographic complexity.
Installation
npm install p-sdk-wallet
# or
yarn add p-sdk-wallet⚠️ Important: Setup Required
To use this SDK, your React Native application needs some configuration. Please follow these steps carefully.
Step 1: Install All Required Packages
Install the SDK itself, its peer dependencies, and all necessary polyfill packages with one command.
yarn add p-sdk-wallet ethers react-native-keychain @react-native-async-storage/async-storage react-native-get-random-values buffer process text-encoding stream-browserify eventsStep 2: Configure Metro for Node.js Core Modules
Some libraries used by the SDK depend on Node.js core modules (stream, events) that don't exist in React Native. You need to tell Metro (the bundler) to use the browser-compatible versions you just installed.
Modify your metro.config.js at the root of your project:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @type {import('@react-native/metro-config').MetroConfig}
*/
const config = {
resolver: {
extraNodeModules: {
// Polyfill for stream and events
stream: require.resolve('stream-browserify'),
events: require.resolve('events/'),
},
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);Note: After changing metro.config.js, you must restart your bundler with a cache reset: npx react-native start --reset-cache.
Step 3: Set up Global Polyfills
Finally, load the remaining polyfills into the global environment of your app.
In your main entry file (index.js or App.tsx), add the following code at the very top, before any other imports:
// --- Start of Polyfills ---
// Required for crypto operations
import 'react-native-get-random-values';
// Polyfill for Buffer
if (typeof global.Buffer === 'undefined') {
global.Buffer = require('buffer').Buffer;
}
// Polyfill for process (required by some dependencies)
global.process = require('process');
global.process.env.NODE_ENV = __DEV__ ? 'development' : 'production';
// Polyfill for TextEncoder/TextDecoder (required by some dependencies)
import { TextEncoder, TextDecoder } from 'text-encoding';
if (typeof global.TextEncoder === 'undefined') {
global.TextEncoder = TextEncoder;
}
if (typeof global.TextDecoder === 'undefined') {
global.TextDecoder = TextDecoder;
}
// --- End of Polyfills ---
// Your other imports and AppRegistry call...API Reference & Usage Examples
1. Vault Creation & Management
Create New Vault
import { Vault, type EncryptedData } from 'p-sdk-wallet';
// Create a new vault with a fresh mnemonic
const { vault, encryptedVault } = await Vault.createNew('your-secure-password');
// Save encryptedVault securely (iOS Keychain, AsyncStorage, etc.)
await saveToSecureStorage(encryptedVault);
console.log('Vault created with accounts:', vault.getAccounts());Load Existing Vault
// Load vault from encrypted data
const encryptedVault: EncryptedData = await loadFromSecureStorage();
const vault = await Vault.load('your-secure-password', encryptedVault);
console.log('Vault loaded successfully');Export Encrypted Vault
// Get the current encrypted vault data for backup
const encryptedVault = vault.exportEncryptedVault();
console.log('Encrypted vault data:', encryptedVault);2. Account Management
Add New HD Account
// Add a new HD account derived from the mnemonic
const newAccount = await vault.addNewHDAccount();
console.log('New HD Account:', {
address: newAccount.address,
name: newAccount.name,
type: newAccount.type
});Import Account from Private Key
// Import an existing account using private key
const privateKey = '0x1234567890abcdef...'; // 64-character hex string
const importedAccount = await vault.importAccount(privateKey);
console.log('Imported Account:', {
address: importedAccount.address,
name: importedAccount.name,
type: importedAccount.type
});Get All Accounts
// Get all accounts (HD + imported)
const allAccounts = vault.getAccounts();
console.log('All accounts:', allAccounts);
// Filter by account type
const hdAccounts = allAccounts.filter(acc => acc.type === 'HD');
const importedAccounts = allAccounts.filter(acc => acc.type === 'Simple');Remove Account
// Remove an imported account (HD accounts cannot be removed)
const accountToRemove = vault.getAccounts().find(acc => acc.type === 'Simple');
if (accountToRemove) {
vault.removeAccount(accountToRemove.address);
console.log('Account removed:', accountToRemove.address);
}3. Security Operations
Export Mnemonic (Recovery Phrase)
// Export the 24-word mnemonic phrase (requires password verification)
try {
const mnemonic = await vault.exportMnemonic('your-secure-password');
console.log('Recovery phrase:', mnemonic);
// IMPORTANT: Show this to user securely and never log it
// User should write it down and store it safely
} catch (error) {
console.error('Failed to export mnemonic:', error.message);
}Get Private Key for Account
// Get private key for a specific account (vault must be unlocked)
const accountAddress = '0x1234...';
const privateKey = vault.getPrivateKeyFor(accountAddress);
console.log('Private key:', privateKey);
// WARNING: Handle private key securely, never expose itCheck Vault Status
// Check if vault is unlocked
const isUnlocked = vault.isUnlocked();
console.log('Vault unlocked:', isUnlocked);
// Get vault information
const vaultInfo = vault.getVaultInfo();
console.log('Vault info:', vaultInfo);4. Balance & Token Operations
Get Native Token Balance
import { type ChainId } from 'p-sdk-wallet';
import { ethers } from 'ethers';
const accountAddress = '0x1234...';
const chainId: ChainId = '1'; // Ethereum mainnet
// Get native token balance (ETH, BNB, MATIC, etc.)
const balance = await vault.getNativeBalance(accountAddress, chainId);
console.log('Balance:', ethers.formatEther(balance), 'ETH');Get ERC-20 Token Balance
const tokenAddress = '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C'; // USDC
const tokenBalance = await vault.getTokenBalance(accountAddress, tokenAddress, chainId);
console.log('Token balance:', tokenBalance.toString());Get Token Information
// Get token metadata (name, symbol, decimals)
const tokenInfo = await vault.getTokenInfo(tokenAddress, chainId);
console.log('Token info:', {
name: tokenInfo.name,
symbol: tokenInfo.symbol,
decimals: tokenInfo.decimals,
totalSupply: tokenInfo.totalSupply.toString()
});5. Transaction Operations
Send Native Tokens
const senderAddress = '0x1234...';
const recipientAddress = '0x5678...';
const amount = '0.01'; // 0.01 ETH
const chainId: ChainId = '1';
try {
const txResponse = await vault.sendNativeToken(
senderAddress,
recipientAddress,
amount,
chainId
);
console.log('Transaction sent:', {
hash: txResponse.hash,
from: txResponse.from,
to: txResponse.to,
value: txResponse.value.toString()
});
// Wait for confirmation
const receipt = await txResponse.wait();
console.log('Transaction confirmed:', receipt.blockNumber);
} catch (error) {
console.error('Transaction failed:', error.message);
}Send ERC-20 Tokens
const tokenAddress = '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C'; // USDC
const amount = '100'; // 100 USDC
try {
const txResponse = await vault.sendToken(
senderAddress,
recipientAddress,
tokenAddress,
amount,
chainId
);
console.log('Token transaction sent:', txResponse.hash);
} catch (error) {
console.error('Token transaction failed:', error.message);
}Get Transaction History
// Get recent transactions for an account
const transactions = await vault.getTransactionHistory(accountAddress, chainId);
console.log('Transaction history:', transactions);6. Multi-Chain Support
Supported Networks
import { SUPPORTED_CHAINS } from 'p-sdk-wallet';
console.log('Supported chains:', SUPPORTED_CHAINS);
// Output:
// {
// '1': { name: 'Ethereum', symbol: 'ETH', ... },
// '56': { name: 'BNB Smart Chain', symbol: 'BNB', ... },
// '137': { name: 'Polygon', symbol: 'MATIC', ... },
// '42161': { name: 'Arbitrum One', symbol: 'ETH', ... },
// '10': { name: 'Optimism', symbol: 'ETH', ... },
// '8453': { name: 'Base', symbol: 'ETH', ... }
// }Cross-Chain Operations
// Work with multiple chains simultaneously
const ethereumBalance = await vault.getNativeBalance(accountAddress, '1');
const bscBalance = await vault.getNativeBalance(accountAddress, '56');
const polygonBalance = await vault.getNativeBalance(accountAddress, '137');
console.log('Multi-chain balances:', {
ethereum: ethers.formatEther(ethereumBalance),
bsc: ethers.formatEther(bscBalance),
polygon: ethers.formatEther(polygonBalance)
});7. Vanity Wallet Generation
Generate Vanity HD Wallet
// Generate a wallet where the first account address starts with '0xaaa' (default from config)
const { mnemonic, vault, encryptedVault, address, attempts, elapsedMs } = await Vault.generateVanityHDWallet({
password: 'password'
});
console.log('Vanity address:', address);
console.log('Attempts:', attempts);
console.log('Time taken:', elapsedMs, 'ms');
// Output: 0xaaa1234567890abcdef...Custom Vanity Prefix
// Generate with custom prefix
const { mnemonic, vault, encryptedVault, address } = await Vault.generateVanityHDWallet({
prefix: 'dead', // Custom prefix (without 0x)
password: 'password'
});
console.log('Custom vanity address:', address);
// Output: 0xdead1234567890abcdef...Advanced Vanity Configuration
// Full control over vanity generation
const { mnemonic, vault, encryptedVault, address } = await Vault.generateVanityHDWallet({
prefix: 'cool',
password: 'password',
maxAttempts: 50000,
caseSensitive: false,
onProgress: (attempts: number, elapsedMs: number) => {
console.log(`Attempt ${attempts}, elapsed: ${elapsedMs}ms`);
}
});8. Error Handling Examples
Comprehensive Error Handling
const handleWalletOperations = async () => {
try {
// Create or load vault
const vault = await Vault.load(password, encryptedVault);
// Add account
const account = await vault.addNewHDAccount();
// Get balance
const balance = await vault.getNativeBalance(account.address, '1');
// Send transaction
const tx = await vault.sendNativeToken(
account.address,
recipientAddress,
'0.01',
'1'
);
console.log('All operations successful');
} catch (error) {
if (error instanceof Error) {
switch (error.message) {
case 'Decryption failed':
console.error('Incorrect password');
break;
case 'Insufficient balance':
console.error('Not enough funds for transaction');
break;
case 'Invalid address':
console.error('Invalid recipient address');
break;
case 'Network error':
console.error('Network connection issue');
break;
default:
console.error('Unknown error:', error.message);
}
}
}
};Rate Limiting for Export Mnemonic
// The exportMnemonic function has built-in rate limiting
// It will throw an error if called too frequently
try {
const mnemonic = await vault.exportMnemonic(password);
// Success
} catch (error) {
if (error.message.includes('Rate limit exceeded')) {
console.error('Please wait before trying again');
}
}9. Advanced Usage Patterns
Batch Operations
// Perform multiple operations efficiently
const batchOperations = async (vault: Vault) => {
const accounts = vault.getAccounts();
const chainId: ChainId = '1';
// Get balances for all accounts
const balancePromises = accounts.map(account =>
vault.getNativeBalance(account.address, chainId)
);
const balances = await Promise.all(balancePromises);
accounts.forEach((account, index) => {
console.log(`${account.name}: ${ethers.formatEther(balances[index])} ETH`);
});
};Wallet Backup and Restore
// Complete backup process
const backupWallet = async (vault: Vault, password: string) => {
// Export encrypted vault
const encryptedVault = vault.exportEncryptedVault();
// Export mnemonic (for recovery)
const mnemonic = await vault.exportMnemonic(password);
// Save both securely
const backup = {
encryptedVault,
mnemonic,
timestamp: Date.now(),
version: '1.0.0'
};
return backup;
};
// Restore from backup
const restoreWallet = async (backup: any, password: string) => {
const vault = await Vault.load(password, backup.encryptedVault);
return vault;
};Security Best Practices
// Secure wallet initialization
const secureWalletInit = async () => {
// 1. Generate strong password
const password = generateStrongPassword();
// 2. Create vault with vanity address (uses default config)
const { mnemonic, vault, encryptedVault, address } = await Vault.generateVanityHDWallet({
password: password
});
// 3. Save encrypted vault securely
await saveToSecureStorage(encryptedVault);
// 4. Export and display mnemonic securely
const exportedMnemonic = await vault.exportMnemonic(password);
displayMnemonicSecurely(exportedMnemonic);
// 5. Clear sensitive data from memory
clearSensitiveData();
return vault;
};10. TypeScript Types
import {
Vault,
type Account,
type ChainId,
type EncryptedData,
type TokenInfo,
type TransactionResponse,
type VaultInfo
} from 'p-sdk-wallet';
// Use types for better development experience
const accounts: Account[] = vault.getAccounts();
const chainId: ChainId = '1';
const tokenInfo: TokenInfo = await vault.getTokenInfo(tokenAddress, chainId);This comprehensive API reference covers all the functions available in the PWC Wallet SDK. Each example includes proper error handling and follows security best practices.
Loading a Vault from iOS Device
The SDK uses iOS Keychain for secure storage of the encrypted vault. Here's how to properly implement vault loading on iOS:
Step 1: Install react-native-keychain
yarn add react-native-keychain
cd ios && pod installStep 2: iOS-specific Setup
Add the following to your ios/YourApp/Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>This app uses Face ID to securely access your wallet</string>Step 3: Vault Storage and Loading Implementation
import { Vault, type EncryptedData } from 'p-sdk-wallet';
import * as Keychain from 'react-native-keychain';
class WalletManager {
private static readonly VAULT_KEY = 'pwc-wallet-vault';
private static readonly VAULT_SERVICE = 'com.pwc.wallet';
/**
* Save the encrypted vault to iOS Keychain
*/
static async saveVault(encryptedVault: EncryptedData): Promise<void> {
try {
const vaultData = JSON.stringify(encryptedVault);
await Keychain.setGenericPassword(
this.VAULT_KEY,
vaultData,
{
service: this.VAULT_SERVICE,
accessControl: Keychain.ACCESS_CONTROL.BIOMETRIC_ANY,
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
authenticationType: Keychain.AUTHENTICATION_TYPE.BIOMETRICS,
}
);
console.log('Vault saved to Keychain successfully');
} catch (error) {
console.error('Failed to save vault to Keychain:', error);
throw new Error('Failed to save wallet securely');
}
}
/**
* Load the encrypted vault from iOS Keychain
*/
static async loadVault(password: string): Promise<Vault> {
try {
// Retrieve the encrypted vault from Keychain
const credentials = await Keychain.getGenericPassword({
service: this.VAULT_SERVICE,
authenticationPrompt: {
title: 'Unlock Wallet',
subtitle: 'Use Face ID or Touch ID to access your wallet',
description: 'Authenticate to unlock your wallet',
cancel: 'Cancel',
},
});
if (!credentials || !credentials.password) {
throw new Error('No wallet found. Please create a new wallet first.');
}
// Parse the encrypted vault data
const encryptedVault: EncryptedData = JSON.parse(credentials.password);
// Load the vault using the provided password
const vault = await Vault.load(password, encryptedVault);
console.log('Vault loaded successfully');
return vault;
} catch (error) {
if (error instanceof Error) {
if (error.message.includes('Decryption failed')) {
throw new Error('Incorrect password. Please try again.');
}
if (error.message.includes('No wallet found')) {
throw new Error('No wallet found. Please create a new wallet first.');
}
}
console.error('Failed to load vault:', error);
throw new Error('Failed to load wallet. Please try again.');
}
}
/**
* Check if a wallet exists on the device
*/
static async hasExistingWallet(): Promise<boolean> {
try {
const credentials = await Keychain.getGenericPassword({
service: this.VAULT_SERVICE,
});
return !!credentials && !!credentials.password;
} catch (error) {
return false;
}
}
/**
* Delete the wallet from Keychain (for logout/reset)
*/
static async deleteVault(): Promise<void> {
try {
await Keychain.resetGenericPassword({
service: this.VAULT_SERVICE,
});
console.log('Vault deleted from Keychain');
} catch (error) {
console.error('Failed to delete vault:', error);
throw new Error('Failed to delete wallet');
}
}
}
// Usage Example:
const initializeWallet = async (password: string) => {
try {
// Check if wallet exists
const hasWallet = await WalletManager.hasExistingWallet();
if (hasWallet) {
// Load existing wallet
const vault = await WalletManager.loadVault(password);
console.log('Wallet loaded:', vault.getAccounts());
return vault;
} else {
// Create new wallet
const { vault: newVault, encryptedVault } = await Vault.createNew(password);
await WalletManager.saveVault(encryptedVault);
console.log('New wallet created:', vault.getAccounts());
return vault;
}
} catch (error) {
console.error('Wallet initialization failed:', error);
throw error;
}
};Step 4: React Native Component Example
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert } from 'react-native';
import { Vault } from 'p-sdk-wallet';
import { WalletManager } from './WalletManager';
const WalletScreen: React.FC = () => {
const [password, setPassword] = useState('');
const [vault, setVault] = useState<Vault | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
checkExistingWallet();
}, []);
const checkExistingWallet = async () => {
const hasWallet = await WalletManager.hasExistingWallet();
if (!hasWallet) {
Alert.alert(
'Welcome!',
'No wallet found. Please create a new wallet to get started.',
[{ text: 'OK' }]
);
}
};
const handleUnlockWallet = async () => {
if (!password.trim()) {
Alert.alert('Error', 'Please enter your password');
return;
}
setLoading(true);
try {
const loadedVault = await WalletManager.loadVault(password);
setVault(loadedVault);
setPassword('');
const accounts = loadedVault.getAccounts();
Alert.alert('Success', `Wallet unlocked! Found ${accounts.length} account(s)`);
} catch (error) {
Alert.alert('Error', error instanceof Error ? error.message : 'Failed to unlock wallet');
} finally {
setLoading(false);
}
};
const handleCreateWallet = async () => {
if (!password.trim()) {
Alert.alert('Error', 'Please enter a password');
return;
}
setLoading(true);
try {
const { vault: newVault, encryptedVault } = await Vault.createNew(password);
await WalletManager.saveVault(encryptedVault);
setVault(newVault);
setPassword('');
Alert.alert(
'Wallet Created!',
'Your wallet has been created successfully. Please save your recovery phrase in a secure location.',
[{ text: 'OK' }]
);
} catch (error) {
Alert.alert('Error', 'Failed to create wallet');
} finally {
setLoading(false);
}
};
return (
<View style={{ flex: 1, padding: 20 }}>
<Text style={{ fontSize: 24, marginBottom: 20 }}>PWC Wallet</Text>
<TextInput
style={{ borderWidth: 1, borderColor: '#ccc', padding: 10, marginBottom: 20 }}
placeholder="Enter password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
<TouchableOpacity
style={{ backgroundColor: '#007AFF', padding: 15, marginBottom: 10 }}
onPress={handleUnlockWallet}
disabled={loading}
>
<Text style={{ color: 'white', textAlign: 'center' }}>
{loading ? 'Loading...' : 'Unlock Wallet'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ backgroundColor: '#34C759', padding: 15 }}
onPress={handleCreateWallet}
disabled={loading}
>
<Text style={{ color: 'white', textAlign: 'center' }}>
{loading ? 'Creating...' : 'Create New Wallet'}
</Text>
</TouchableOpacity>
{vault && (
<View style={{ marginTop: 20 }}>
<Text style={{ fontSize: 18, marginBottom: 10 }}>Your Accounts:</Text>
{vault.getAccounts().map((account, index) => (
<Text key={index} style={{ marginBottom: 5 }}>
{account.name}: {account.address}
</Text>
))}
</View>
)}
</View>
);
};
export default WalletScreen;Key iOS Security Features
- Biometric Authentication: Uses Face ID/Touch ID for additional security
- Device-Only Access: Vault is only accessible when the device is unlocked
- Secure Storage: Uses iOS Keychain, the most secure storage on iOS
- Access Control: Prevents unauthorized access even if the device is compromised
Error Handling Best Practices
- Always handle Keychain errors gracefully
- Provide user-friendly error messages
- Implement proper loading states
- Validate password strength for new wallets
- Handle biometric authentication failures
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
License
MIT