p-sdk-wallet
Version:
A comprehensive wallet SDK for React Native (pwc), supporting multi-chain and multi-account features.
300 lines (299 loc) • 11.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QRCodeService = void 0;
const EncryptionService_1 = require("../crypto/EncryptionService");
/**
* Service for generating and scanning QR codes for wallet operations.
* Supports address sharing, mnemonic backup, and transaction signing.
*/
class QRCodeService {
/**
* Generates QR code data for a wallet address
* @param address - The wallet address to encode
* @param label - Optional label for the address
* @returns QR code data string
*/
static generateAddressQR(address, label) {
const qrData = {
type: 'address',
data: {
address: address,
label: label || '',
},
timestamp: Date.now(),
version: this.QR_VERSION,
};
return JSON.stringify(qrData);
}
/**
* Generates QR code data for encrypted mnemonic backup
* @param mnemonic - The mnemonic phrase to encrypt
* @param password - Password for encryption
* @param chainType - Type of blockchain (evm, solana, etc.)
* @param accountCount - Number of accounts to import
* @returns Promise resolving to QR code data string
* @throws Error if encryption fails
*/
static async generateMnemonicQR(mnemonic, password, chainType = 'evm', accountCount = 1) {
// Encrypt the mnemonic
const encryptedMnemonic = await EncryptionService_1.EncryptionService.encrypt(mnemonic, password);
const walletImportData = {
encryptedMnemonic,
chainType,
accountCount,
};
const qrData = {
type: 'wallet-import',
data: walletImportData,
timestamp: Date.now(),
version: this.QR_VERSION,
};
return JSON.stringify(qrData);
}
/**
* Generates QR code data for transaction signing
* @param transactionData - Transaction details
* @returns QR code data string
*/
static generateTransactionQR(transactionData) {
const qrData = {
type: 'transaction',
data: transactionData,
timestamp: Date.now(),
version: this.QR_VERSION,
};
return JSON.stringify(qrData);
}
/**
* Parses QR code data and validates its structure
* @param qrString - The QR code string to parse
* @returns Parsed QR code data
* @throws Error if QR data is invalid or unsupported
*/
static parseQRData(qrString) {
try {
const qrData = JSON.parse(qrString);
// Validate required fields
if (!qrData.type || !qrData.data || !qrData.timestamp || !qrData.version) {
throw new Error('Invalid QR code format: missing required fields');
}
// Validate type
const validTypes = ['address', 'mnemonic', 'transaction', 'wallet-import'];
if (!validTypes.includes(qrData.type)) {
throw new Error(`Unsupported QR code type: ${qrData.type}`);
}
// Validate timestamp (not too old, not in future)
const now = Date.now();
const maxAge = 24 * 60 * 60 * 1000; // 24 hours
if (qrData.timestamp > now || qrData.timestamp < now - maxAge) {
throw new Error('QR code is too old or has invalid timestamp');
}
return qrData;
}
catch (error) {
if (error instanceof SyntaxError) {
throw new Error('Invalid QR code: not a valid JSON');
}
throw error;
}
}
/**
* Extracts wallet address from QR code data
* @param qrString - The QR code string
* @returns The wallet address
* @throws Error if QR code doesn't contain address data
*/
static extractAddress(qrString) {
const qrData = this.parseQRData(qrString);
if (qrData.type !== 'address') {
throw new Error('QR code does not contain address data');
}
const address = qrData.data.address;
if (!address || typeof address !== 'string') {
throw new Error('Invalid address in QR code');
}
return address;
}
/**
* Extracts wallet import data from QR code
* @param qrString - The QR code string
* @returns Wallet import data
* @throws Error if QR code doesn't contain wallet import data
*/
static extractWalletImportData(qrString) {
const qrData = this.parseQRData(qrString);
if (qrData.type !== 'wallet-import') {
throw new Error('QR code does not contain wallet import data');
}
const importData = qrData.data;
// Validate required fields
if (!importData.encryptedMnemonic || !importData.chainType) {
throw new Error('Invalid wallet import data in QR code');
}
return importData;
}
/**
* Extracts transaction data from QR code
* @param qrString - The QR code string
* @returns Transaction data
* @throws Error if QR code doesn't contain transaction data
*/
static extractTransactionData(qrString) {
const qrData = this.parseQRData(qrString);
if (qrData.type !== 'transaction') {
throw new Error('QR code does not contain transaction data');
}
const txData = qrData.data;
// Validate required fields
if (!txData.txType || !txData.chainId || !txData.to || !txData.amount) {
throw new Error('Invalid transaction data in QR code');
}
return txData;
}
/**
* Validates if a string is a valid Ethereum address
* @param address - Address to validate
* @returns True if valid Ethereum address
*/
static isValidEthereumAddress(address) {
return /^0x[a-fA-F0-9]{40}$/.test(address);
}
/**
* Validates if a string is a valid Solana address
* @param address - Address to validate
* @returns True if valid Solana address
*/
static isValidSolanaAddress(address) {
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
}
/**
* Gets the address type based on format
* @param address - Address to check
* @returns 'ethereum', 'solana', or 'unknown'
*/
static getAddressType(address) {
if (this.isValidEthereumAddress(address)) {
return 'ethereum';
}
if (this.isValidSolanaAddress(address)) {
return 'solana';
}
return 'unknown';
}
/**
* Clears sensitive data from memory
* @param data - Sensitive data to clear
*/
static clearSensitiveData(data) {
// Overwrite the string with zeros (as much as possible in JS)
if (typeof data === 'string') {
// Note: JavaScript strings are immutable, so we can't directly clear them
// But we can help the garbage collector by removing references
setTimeout(() => {
// This helps reduce the time sensitive data stays in memory
}, 0);
}
}
// --- Utility Functions for External Use ---
/**
* Generates QR code data for a wallet address from Vault.
* Utility function to be used outside of Vault class.
* @param vault - The vault instance
* @param accountIndex - Index of the account to generate QR for (default: 0)
* @param label - Optional label for the address
* @returns QR code data string
* @throws Error if account index is invalid
*
* @example
* ```typescript
* import { QRCodeService, Vault } from 'pwc-wallet-sdk';
*
* // Generate QR for first account
* const qrData = QRCodeService.generateAddressQRFromVault(vault, 0, 'My Wallet');
* ```
*/
static generateAddressQRFromVault(vault, accountIndex = 0, label) {
const accounts = vault.getAccounts();
if (accountIndex < 0 || accountIndex >= accounts.length) {
throw new Error(`Invalid account index: ${accountIndex}. Available accounts: ${accounts.length}`);
}
const account = accounts[accountIndex];
return this.generateAddressQR(account.address, label || account.name);
}
/**
* Generates QR code data for encrypted mnemonic backup from Vault.
* Utility function to be used outside of Vault class.
* @param vault - The vault instance
* @param password - Password for encryption
* @param accountCount - Number of accounts to import (default: 1)
* @returns Promise resolving to QR code data string
* @throws Error if encryption fails or no HD keyring available
*
* @example
* ```typescript
* import { QRCodeService, Vault } from 'pwc-wallet-sdk';
*
* // Generate backup QR with password
* const qrData = await QRCodeService.generateMnemonicQRFromVault(vault, 'my-secure-password', 3);
* ```
*/
static async generateMnemonicQRFromVault(vault, password, accountCount = 1) {
const hdKeyring = vault.keyrings.find((k) => k.type === 'HD');
if (!hdKeyring) {
throw new Error('No HD keyring available. Cannot generate mnemonic QR.');
}
// Get the mnemonic from the HD keyring
const mnemonic = hdKeyring.getMnemonic();
if (!mnemonic) {
throw new Error('Mnemonic not available in HD keyring.');
}
const chainType = vault.chainId === 'solana' ? 'solana' : 'evm';
return this.generateMnemonicQR(mnemonic, password, chainType, accountCount);
}
/**
* Validates QR code data without processing it.
* @param qrString - QR code string to validate
* @returns Object containing validation result and data type
*
* @example
* ```typescript
* // Validate QR code before processing
* const validation = QRCodeService.validateQRCode(qrString);
* console.log('QR type:', validation.type);
* console.log('Is valid:', validation.isValid);
* ```
*/
static validateQRCode(qrString) {
try {
const qrData = this.parseQRData(qrString);
return {
isValid: true,
type: qrData.type,
data: qrData.data
};
}
catch (error) {
return {
isValid: false,
type: 'unknown',
data: null
};
}
}
/**
* Generates an EIP-681 compatible QR string for wallet address.
* This format is compatible with Metamask, Binance, Trust Wallet, etc.
* @param address - The wallet address to encode
* @returns EIP-681 URI string (e.g., 'ethereum:0x1234...')
* @example
* ```typescript
* const qrString = QRCodeService.generateEIP681AddressQR('0x1234...');
* // Use this string with a QR code generator for maximum wallet compatibility
* ```
*/
static generateEIP681AddressQR(address) {
return `ethereum:${address}`;
}
}
exports.QRCodeService = QRCodeService;
QRCodeService.QR_VERSION = '1.0';