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.
- ๐ฏ Simplified Vanity Wallets: Generate vanity addresses with just a password - uses optimal defaults automatically.
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);
}
#### **Multi-Transfer Operations** ๐
The SDK provides powerful multi-transfer functionality for sending tokens to multiple recipients efficiently. This is perfect for airdrops, payroll, or bulk payments.
##### **Send Native Tokens to Multiple Recipients**
```typescript
import { type Recipient } from 'p-sdk-wallet';
const senderAddress = '0x1234...';
const chainId: ChainId = '1'; // Ethereum
// Define recipients
const recipients: Recipient[] = [
{ address: '0x1111...', amount: '0.01' }, // 0.01 ETH
{ address: '0x2222...', amount: '0.02' }, // 0.02 ETH
{ address: '0x3333...', amount: '0.005' }, // 0.005 ETH
{ address: '0x4444...', amount: '0.015' }, // 0.015 ETH
];
try {
const result = await vault.multiTransferNativeTokens(
senderAddress,
recipients,
chainId,
(completed, total, txHash) => {
console.log(`Progress: ${completed}/${total} - TX: ${txHash}`);
}
);
console.log('Multi-transfer completed:', {
successful: result.successfulCount,
failed: result.failedCount,
totalAmount: result.totalAmount,
totalGasUsed: result.totalGasUsed.toString()
});
// Check failed transfers
if (result.failed.length > 0) {
console.log('Failed transfers:', result.failed);
}
} catch (error) {
console.error('Multi-transfer failed:', error.message);
}Send ERC-20/SPL Tokens to Multiple Recipients
const tokenAddress = '0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C'; // USDC
const recipients: Recipient[] = [
{ address: '0x1111...', amount: '100' }, // 100 USDC
{ address: '0x2222...', amount: '250' }, // 250 USDC
{ address: '0x3333...', amount: '75' }, // 75 USDC
{ address: '0x4444...', amount: '300' }, // 300 USDC
];
try {
const result = await vault.multiTransferTokens(
senderAddress,
tokenAddress,
recipients,
chainId,
(completed, total, txHash) => {
console.log(`Progress: ${completed}/${total} - TX: ${txHash}`);
}
);
console.log('Token multi-transfer completed:', {
successful: result.successfulCount,
failed: result.failedCount,
totalAmount: result.totalAmount
});
} catch (error) {
console.error('Token multi-transfer failed:', error.message);
}Multi-Transfer Features
- Batch Processing: Automatically processes transfers in batches of 10 (configurable)
- Progress Tracking: Real-time progress updates via callback
- Error Handling: Continues processing even if some transfers fail
- Balance Validation: Checks sufficient balance before starting
- Cross-Chain Support: Works with both EVM and Solana chains
- Gas Optimization: Efficient gas usage with batch processing
- Detailed Results: Returns comprehensive success/failure information
Advanced Multi-Transfer Options
import { type MultiTransferOptions } from 'p-sdk-wallet';
const options: MultiTransferOptions = {
batchSize: 5, // Process 5 transfers per batch
onProgress: (completed, total, txHash) => {
console.log(`Batch progress: ${completed}/${total}`);
if (txHash !== 'FAILED') {
console.log(`Transaction hash: ${txHash}`);
}
},
onError: (error, recipient) => {
console.error(`Failed to send to ${recipient.address}:`, error.message);
}
};
const result = await vault.multiTransferNativeTokens(
senderAddress,
recipients,
chainId,
options.onProgress
);React Native Multi-Transfer Component
import React, { useState } from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { Vault, type Recipient } from 'p-sdk-wallet';
interface MultiTransferProps {
vault: Vault;
senderAddress: string;
chainId: string;
}
const MultiTransferComponent: React.FC<MultiTransferProps> = ({
vault,
senderAddress,
chainId
}) => {
const [isProcessing, setIsProcessing] = useState(false);
const [progress, setProgress] = useState(0);
const [total, setTotal] = useState(0);
const handleMultiTransfer = async () => {
const recipients: Recipient[] = [
{ address: '0x1111...', amount: '0.01' },
{ address: '0x2222...', amount: '0.02' },
{ address: '0x3333...', amount: '0.005' },
];
setIsProcessing(true);
setTotal(recipients.length);
setProgress(0);
try {
const result = await vault.multiTransferNativeTokens(
senderAddress,
recipients,
chainId as any,
(completed, total, txHash) => {
setProgress(completed);
console.log(`Progress: ${completed}/${total} - TX: ${txHash}`);
}
);
Alert.alert(
'Multi-Transfer Complete',
`Success: ${result.successfulCount}\nFailed: ${result.failedCount}`
);
} catch (error) {
Alert.alert('Error', error instanceof Error ? error.message : 'Unknown error');
} finally {
setIsProcessing(false);
}
};
return (
<View>
<Button
title={isProcessing ? 'Processing...' : 'Start Multi-Transfer'}
onPress={handleMultiTransfer}
disabled={isProcessing}
/>
{isProcessing && (
<Text>Progress: {progress}/{total}</Text>
)}
</View>
);
};Get Transaction History
// Get recent transactions for an account
const transactions = await vault.getTransactionHistory(accountAddress, chainId);
console.log('Transaction history:', transactions);Gas Estimation โฝ
The SDK provides comprehensive gas estimation APIs to help you display gas costs to users before sending transactions.
Estimate Native Token Transfer Gas
// Estimate gas for sending native tokens (ETH, BNB, MATIC, etc.)
const gasEstimate = await vault.estimateNativeTransferGas(
'0x1234...', // sender address
'0x5678...', // recipient address
'0.01', // amount (0.01 ETH)
'1' // Ethereum chain
);
console.log('Estimated gas:', gasEstimate.toString());
// Output: Estimated gas: 21000
// Calculate estimated cost in ETH
const gasPrice = ethers.parseUnits('20', 'gwei'); // 20 gwei
const estimatedCost = gasEstimate * gasPrice;
console.log('Estimated cost:', ethers.formatEther(estimatedCost), 'ETH');
// Output: Estimated cost: 0.00042 ETHEstimate ERC-20/SPL Token Transfer Gas
// Estimate gas for sending ERC-20 tokens
const gasEstimate = await vault.estimateTokenTransferGas(
'0x1234...', // sender address
'0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C', // USDC contract
'0x5678...', // recipient address
'100', // amount (100 USDC)
'1' // Ethereum chain
);
console.log('Estimated gas for token transfer:', gasEstimate.toString());
// Output: Estimated gas for token transfer: 65000Estimate Multi-Transfer Gas
const recipients = [
{ address: '0x1111...', amount: '0.01' },
{ address: '0x2222...', amount: '0.02' },
{ address: '0x3333...', amount: '0.005' }
];
// Estimate gas for native token multi-transfer
const nativeGasEstimate = await vault.estimateMultiTransferGas(
'0x1234...',
recipients,
'1', // Ethereum
true // Native tokens
);
console.log('Native multi-transfer gas:', nativeGasEstimate.toString());
// Output: Native multi-transfer gas: 63000 (21000 * 3)
// Estimate gas for token multi-transfer
const tokenGasEstimate = await vault.estimateMultiTransferGas(
'0x1234...',
recipients,
'1', // Ethereum
false, // ERC-20 tokens
'0xA0b86a33E6441b8c4C8C8C8C8C8C8C8C8C8C8C8C' // USDC
);
console.log('Token multi-transfer gas:', tokenGasEstimate.toString());
// Output: Token multi-transfer gas: 195000 (65000 * 3)React Native Gas Estimation Component
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button } from 'react-native';
import { Vault } from 'p-sdk-wallet';
import { ethers } from 'ethers';
interface GasEstimationProps {
vault: Vault;
fromAddress: string;
chainId: string;
}
const GasEstimationComponent: React.FC<GasEstimationProps> = ({
vault,
fromAddress,
chainId
}) => {
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const [gasEstimate, setGasEstimate] = useState<bigint | null>(null);
const [isEstimating, setIsEstimating] = useState(false);
const estimateGas = async () => {
if (!recipient || !amount) return;
setIsEstimating(true);
try {
const estimate = await vault.estimateNativeTransferGas(
fromAddress,
recipient,
amount,
chainId as any
);
setGasEstimate(estimate);
} catch (error) {
console.error('Gas estimation failed:', error);
} finally {
setIsEstimating(false);
}
};
return (
<View>
<TextInput
placeholder="Recipient Address"
value={recipient}
onChangeText={setRecipient}
/>
<TextInput
placeholder="Amount"
value={amount}
onChangeText={setAmount}
keyboardType="numeric"
/>
<Button
title={isEstimating ? 'Estimating...' : 'Estimate Gas'}
onPress={estimateGas}
disabled={isEstimating}
/>
{gasEstimate && (
<Text>Estimated Gas: {gasEstimate.toString()}</Text>
)}
</View>
);
};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
EVM Vanity Wallets
// Generate vanity wallet with default prefix 'aaa'
// Only requires password - uses optimal default settings
const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet('password');
console.log(`Found address: ${foundAddress} after ${attempts} attempts`);
// Output: Found address: 0xaaa1234567890abcdef... after 12345 attemptsWith Progress Tracking
// Generate with progress callback
const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet(
'password',
(attempts, address) => {
console.log(`Attempt ${attempts}: ${address}`);
}
);
console.log(`Found address: ${foundAddress} after ${attempts} attempts`);Configuration
The function automatically uses optimal settings from VANITY_WALLET_CONFIG:
- Prefix:
'aaa'(default) - Max attempts:
1,000,000(default) - Case sensitive:
false(default) - Chain:
'evm'(Ethereum-compatible) - Progress updates: Every 1,000 attempts
- Non-blocking: Every 10,000 attempts
// No need to specify complex parameters anymore!
// The function is optimized for the most common use case8. 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, Button, Alert } from 'react-native';
import { Vault } from 'p-sdk-wallet';
import { WalletManager } from './WalletManager';
const WalletScreen: React.FC = () => {
const [vault, setVault] = useState<Vault | null>(null);
const [accounts, setAccounts] = useState<any[]>([]);
const createWallet = async () => {
try {
const { vault: newVault, encryptedVault } = await Vault.createNew('my-password');
await WalletManager.saveVault(encryptedVault);
setVault(newVault);
setAccounts(newVault.getAccounts());
} catch (error) {
Alert.alert('Error', 'Failed to create wallet');
}
};
const loadWallet = async () => {
try {
const loadedVault = await WalletManager.loadVault('my-password');
setVault(loadedVault);
setAccounts(loadedVault.getAccounts());
} catch (error) {
Alert.alert('Error', 'Failed to load wallet');
}
};
const addSolanaAccount = async () => {
if (!vault) return;
try {
const newAccount = await vault.addNewSolanaAccount();
setAccounts(vault.getAccounts());
} catch (error) {
Alert.alert('Error', 'Failed to add Solana account');
}
};
return (
<View>
<Text>Wallet Accounts:</Text>
{accounts.map((account, index) => (
<Text key={index}>
{account.name}: {account.address} ({account.type})
</Text>
))}
<Button title="Create Wallet" onPress={createWallet} />
<Button title="Load Wallet" onPress={loadWallet} />
<Button title="Add Solana Account" onPress={addSolanaAccount} />
</View>
);
};Simplified Vanity Wallet Generation
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert } from 'react-native';
import { Vault } from 'p-sdk-wallet';
const VanityWalletGenerator: React.FC = () => {
const [password, setPassword] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const generateVanityWallet = async () => {
if (!password) {
Alert.alert('Error', 'Please enter a password');
return;
}
setIsGenerating(true);
try {
// Super simple - just need password!
const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet(password);
Alert.alert(
'Success!',
`Found vanity address: ${foundAddress}\nAttempts: ${attempts}`
);
// Save the vault
await saveToSecureStorage(encryptedVault);
} catch (error) {
Alert.alert('Error', error instanceof Error ? error.message : 'Failed to generate vanity wallet');
} finally {
setIsGenerating(false);
}
};
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 20 }}>
Generate Vanity Wallet
</Text>
<TextInput
placeholder="Enter password"
value={password}
onChangeText={setPassword}
secureTextEntry
style={{ borderWidth: 1, padding: 10, marginBottom: 20 }}
/>
<TouchableOpacity
onPress={generateVanityWallet}
disabled={isGenerating}
style={{
backgroundColor: isGenerating ? '#ccc' : '#007AFF',
padding: 15,
borderRadius: 8,
alignItems: 'center'
}}
>
<Text style={{ color: 'white', fontWeight: 'bold' }}>
{isGenerating ? 'Generating...' : 'Generate Vanity Wallet (prefix: aaa)'}
</Text>
</TouchableOpacity>
<Text style={{ marginTop: 10, fontSize: 12, color: '#666' }}>
Uses default prefix 'aaa' and optimal settings automatically
</Text>
</View>
);
};Contributing
Contributions are welcome! Please open an issue or submit a pull request.
License
MIT
๐ New: Solana Support
The SDK now supports Solana blockchain with full HD wallet functionality, SPL token operations, and vanity wallet generation!
Solana Features:
- โ HD Wallet Support - Generate and manage multiple Solana accounts from a single mnemonic
- โ SPL Token Operations - Send and receive SPL tokens with associated token accounts
- โ Vanity Wallet Generation - Create Solana wallets with custom address prefixes
- โ Multi-Chain Support - Seamlessly switch between EVM and Solana chains
- โ React Native Compatible - Works perfectly in mobile applications
๐ Quick Start
Installation
npm install pwc-wallet-sdk
# or
yarn add pwc-wallet-sdkBasic Usage
import { Vault } from 'pwc-wallet-sdk';
// Create a new wallet (EVM by default)
const { vault, encryptedVault } = await Vault.createNew('your-password');
// Create a new Solana wallet
const { vault: solanaVault, encryptedVault: solanaEncryptedVault } =
await Vault.createNew('your-password', 'solana');
// Load existing wallet
const loadedVault = await Vault.load('your-password', encryptedVault);
// Get all accounts
const accounts = vault.getAccounts();
console.log('Accounts:', accounts);๐ API Reference
Vault Management
Creating Vaults
// Create new EVM vault
const { vault, encryptedVault } = await Vault.createNew('password');
// Create new Solana vault
const { vault, encryptedVault } = await Vault.createNew('password', 'solana');
// Create from existing mnemonic (EVM)
const { vault, encryptedVault } = await Vault.createFromMnemonic(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'password'
);
// Create from existing mnemonic (Solana)
const { vault, encryptedVault } = await Vault.createFromMnemonic(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'password',
'solana'
);
// Load encrypted vault
const vault = await Vault.load('password', encryptedVault);Account Management
// Add new HD account (EVM)
const newAccount = await vault.addNewHDAccount();
// Add new Solana account
const newSolanaAccount = await vault.addNewSolanaAccount();
// Import account from private key
const importedAccount = await vault.importAccount('0x123...');
// Get all accounts
const accounts = vault.getAccounts();
// Returns: [
// { address: '0x123...', type: 'HD', name: 'Account 1' },
// { address: 'ABC123...', type: 'Solana', name: 'Solana 1' },
// { address: '0x456...', type: 'Simple', name: 'Imported 1' }
// ]Blockchain Operations
Native Token Operations
// Get native balance (works for both EVM and Solana)
const ethBalance = await vault.getNativeBalance('0x123...', '1'); // Ethereum
const solBalance = await vault.getNativeBalance('ABC123...', 'solana'); // Solana
// Send native tokens
const tx = await vault.sendNativeToken(
'0x123...', // from address
'0x456...', // to address
'0.1', // amount
'1' // chain ID (Ethereum)
);
const solTx = await vault.sendNativeToken(
'ABC123...', // from address
'DEF456...', // to address
'0.5', // amount in SOL
'solana' // chain ID (Solana)
);Token Operations
// Get token info
const tokenInfo = await vault.getTokenInfo('0x123...', '1'); // ERC-20
const splTokenInfo = await vault.getTokenInfo('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'solana'); // SPL
// Get token balance
const balance = await vault.getTokenBalance('0x123...', '0x456...', '1');
const splBalance = await vault.getTokenBalance('ABC123...', 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', 'solana');
// Send tokens
const tokenTx = await vault.sendToken(
'0x123...', // from address
'0x456...', // to address
'100', // amount
'0x789...', // token contract address
'1' // chain ID
);
const splTx = await vault.sendToken(
'ABC123...', // from address
'DEF456...', // to address
'50', // amount
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // SPL token address
'solana' // chain ID
);Vanity Wallet Generation
EVM Vanity Wallets
// Generate vanity wallet with prefix
const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet(
'aaa', // prefix (without 0x)
'password',
'evm',
1000000, // max attempts
false, // case sensitive
(attempts, address) => {
console.log(`Attempt ${attempts}: ${address}`);
}
);
console.log(`Found address: ${foundAddress} after ${attempts} attempts`);Solana Vanity Wallets
// Generate Solana vanity wallet
const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet(
'abc', // prefix
'password',
'solana',
1000000, // max attempts
false, // case sensitive
(attempts, address) => {
console.log(`Attempt ${attempts}: ${address}`);
}
);
console.log(`Found Solana address: ${foundAddress} after ${attempts} attempts`);Security Operations
Export Mnemonic (Password Protected)
// Export mnemonic with password verification
const mnemonic = await vault.exportMnemonic('password');
console.log('Mnemonic:', mnemonic);Get Private Key
// Get private key for specific address (vault must be unlocked)
const privateKey = await vault.getPrivateKeyFor('0x123...');
console.log('Private key:', privateKey);๐ Supported Chains
EVM Chains
- Ethereum (
1) - Mainnet - BNB Smart Chain (
56) - Mainnet - Polygon (
137) - Mainnet - Arbitrum One (
42161) - Mainnet - OP Mainnet (
10) - Mainnet - Base (
8453) - Mainnet
Solana Chains
- Solana (
solana) - Mainnet - Solana Devnet (
solana-devnet) - Devnet
๐ Security Features
Encryption & Storage
- PBKDF2 encryption with configurable iterations
- AES-256-GCM for secure data encryption
- Salt generation for each encryption operation
- Memory protection with secure key clearing
Rate Limiting & Protection
- Export mnemonic rate limiting (5 attempts per 30 seconds)
- Audit logging for security-sensitive operations
- Vault identification for tracking
- Memory protection against timing attacks
Best Practices
- Password verification for sensitive operations
- Private key isolation in memory
- Secure random generation for mnemonics
- Input validation and sanitization
๐ฑ React Native Integration
Secure Storage Setup
import * as Keychain from 'react-native-keychain';
import { Vault } from 'pwc-wallet-sdk';
class WalletManager {
static async saveVault(encryptedVault: any, password: string): Promise<void> {
try {
await Keychain.setInternetCredentials(
'pwc-wallet-vault',
'user',
JSON.stringify(encryptedVault)
);
} catch (error) {
console.error('Failed to save vault:', error);
throw error;
}
}
static async loadVault(password: string): Promise<Vault> {
try {
const credentials = await Keychain.getInternetCredentials('pwc-wallet-vault');
if (!credentials || !credentials.password) {
throw new Error('No vault found');
}
const encryptedVault = JSON.parse(credentials.password);
return await Vault.load(password, encryptedVault);
} catch (error) {
console.error('Failed to load vault:', error);
throw error;
}
}
static async deleteVault(): Promise<void> {
try {
await Keychain.resetInternetCredentials('pwc-wallet-vault');
} catch (error) {
console.error('Failed to delete vault:', error);
throw error;
}
}
}React Native Component Example
import React, { useState, useEffect } from 'react';
import { View, Text, Button, Alert } from 'react-native';
import { Vault } from 'pwc-wallet-sdk';
import { WalletManager } from './WalletManager';
const WalletScreen: React.FC = () => {
const [vault, setVault] = useState<Vault | null>(null);
const [accounts, setAccounts] = useState<any[]>([]);
const createWallet = async () => {
try {
const { vault: newVault, encryptedVault } = await Vault.createNew('my-password');
await WalletManager.saveVault(encryptedVault, 'my-password');
setVault(newVault);
setAccounts(newVault.getAccounts());
} catch (error) {
Alert.alert('Error', 'Failed to create wallet');
}
};
const loadWallet = async () => {
try {
const loadedVault = await WalletManager.loadVault('my-password');
setVault(loadedVault);
setAccounts(loadedVault.getAccounts());
} catch (error) {
Alert.alert('Error', 'Failed to load wallet');
}
};
const addSolanaAccount = async () => {
if (!vault) return;
try {
const newAccount = await vault.addNewSolanaAccount();
setAccounts(vault.getAccounts());
} catch (error) {
Alert.alert('Error', 'Failed to add Solana account');
}
};
return (
<View>
<Text>Wallet Accounts:</Text>
{accounts.map((account, index) => (
<Text key={index}>
{account.name}: {account.address} ({account.type})
</Text>
))}
<Button title="Create Wallet" onPress={createWallet} />
<Button title="Load Wallet" onPress={loadWallet} />
<Button title="Add Solana Account" onPress={addSolanaAccount} />
</View>
);
};Simplified Vanity Wallet Generation
import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert } from 'react-native';
import { Vault } from 'p-sdk-wallet';
const VanityWalletGenerator: React.FC = () => {
const [password, setPassword] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const generateVanityWallet = async () => {
if (!password) {
Alert.alert('Error', 'Please enter a password');
return;
}
setIsGenerating(true);
try {
// Super simple - just need password!
const { vault, encryptedVault, attempts, foundAddress } = await Vault.generateVanityHDWallet(password);
Alert.alert(
'Success!',
`Found vanity address: ${foundAddress}\nAttempts: ${attempts}`
);
// Save the vault
await saveToSecureStorage(encryptedVault);
} catch (error) {
Alert.alert('Error', error instanceof Error ? error.message : 'Failed to generate vanity wallet');
} finally {
setIsGenerating(false);
}
};
return (
<View style={{ padding: 20 }}>
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 20 }}>
Generate Vanity Wallet
</Text>
<TextInput
placeholder="Enter password"
value={password}
onChangeText={setPassword}
secureTextEntry
style={{ borderWidth: 1, padding: 10, marginBottom: 20 }}
/>
<TouchableOpacity
onPress={generateVanityWallet}
disabled={isGenerating}
style={{
backgroundColor: isGenerating ? '#ccc' : '#007AFF',
padding: 15,
borderRadius: 8,
alignItems: 'center'
}}
>
<Text style={{ color: 'white', fontWeight: 'bold' }}>
{isGenerating ? 'Generating...' : 'Generate Vanity Wallet (prefix: aaa)'}
</Text>
</TouchableOpacity>
<Text style={{ marginTop: 10, fontSize: 12, color: '#666' }}>
Uses default prefix 'aaa' and optimal settings automatically
</Text>
</View>
);
};๐งช Testing
# Run all tests
npm test
# Run Solana-specific tests
npm test -- solana.test.ts
# Run with coverage
npm run test:coverage๐ฆ Build & Publish
# Build the package
npm run build
# Run tests
npm test
# Publish to npm
npm publish๐ง Configuration
Vanity Wallet Settings
import { VANITY_WALLET_CONFIG } from 'p-sdk-wallet';
// Default configuration
console.log(VANITY_WALLET_CONFIG);
// {
// DEFAULT_PREFIX: 'aaa',
// DEFAULT_MAX_ATTEMPTS: 1000000,
// DEFAULT_CASE_SENSITIVE: false,
// PROGRESS_UPDATE_INTERVAL: 1000,
// NON_BLOCKING_INTERVAL: 10000,
// MAX_PREFIX_LENGTH: 10,
// MAX_ATTEMPTS_LIMIT: 10000000
// }๐จ Error Handling
try {
const vault = await Vault.load('wrong-password', encryptedVault);
} catch (error) {
if (error.message.includes('Invalid password')) {
console.log('Wrong password provided');
} else if (error.message.includes('Corrupted vault')) {
console.log('Vault data is corrupted');
}
}
try {
const mnemonic = await vault.exportMnemonic('password');
} catch (error) {
if (error.message.includes('Rate limit exceeded')) {
console.log('Too many export attempts');
} else if (error.message.includes('Invalid password')) {
console.log('Wrong password');
}
}๐ License
MIT License - see LICENSE file for details.
๐ค Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
๐ Support
For support and questions:
- Create an issue on GitHub
- Check the documentation
- Review the test files for usage examples