JSPM

  • Created
  • Published
  • Downloads 14
  • Score
    100M100P100Q54327F
  • License MIT

A comprehensive wallet SDK for React Native (pwc), supporting multi-chain and multi-account features.

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 events

Step 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 it

Check 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 ETH
Estimate 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: 65000
Estimate 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 attempts

With 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 case

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 install

Step 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-sdk

Basic 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

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

๐Ÿ“ž Support

For support and questions:

  • Create an issue on GitHub
  • Check the documentation
  • Review the test files for usage examples