p-sdk-wallet
Version:
A comprehensive wallet SDK for React Native (pwc), supporting multi-chain and multi-account features.
189 lines (188 loc) • 7.95 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BatchProcessor = void 0;
const ChainService_1 = require("../chain/ChainService");
const SolanaChainService_1 = require("../chain/SolanaChainService");
const ethers_1 = require("ethers");
/**
* Handles batch processing of transfers for both native tokens and ERC-20/SPL tokens.
* Processes transfers in batches to optimize gas usage and provide progress tracking.
*/
class BatchProcessor {
/**
* Processes a batch of native token transfers.
* @param chainService - The chain service instance (EVM or Solana)
* @param recipients - Array of recipients to transfer to
* @param onProgress - Optional progress callback
* @returns Promise resolving to batch processing results
*/
static async processNativeTokenBatch(chainService, recipients, onProgress) {
const transactions = [];
const failed = [];
let totalGasUsed = BigInt(0);
for (let i = 0; i < recipients.length; i++) {
const recipient = recipients[i];
try {
let txResult;
if (chainService instanceof SolanaChainService_1.SolanaChainService) {
// Solana native token transfer
txResult = await chainService.sendTransaction(recipient.address, recipient.amount);
}
else {
// EVM native token transfer
txResult = await chainService.sendTransaction({
to: recipient.address,
value: ethers_1.ethers.parseEther(recipient.amount)
});
}
transactions.push(txResult);
// Update gas used (only for EVM chains that support it)
if (chainService instanceof ChainService_1.ChainService && txResult && typeof txResult === 'object' && 'gasUsed' in txResult) {
const gasUsed = txResult.gasUsed;
if (gasUsed && typeof gasUsed === 'bigint') {
totalGasUsed += gasUsed;
}
}
// Progress callback
if (onProgress) {
onProgress(i + 1, recipients.length, txResult.hash);
}
// Small delay to prevent rate limiting
if (i < recipients.length - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
failed.push({
recipient,
error: new Error(`Failed to transfer to ${recipient.address}: ${errorMessage}`)
});
// Still call progress callback for failed transfers
if (onProgress) {
onProgress(i + 1, recipients.length, 'FAILED');
}
}
}
return {
transactions,
failed,
gasUsed: totalGasUsed
};
}
/**
* Processes a batch of ERC-20/SPL token transfers.
* @param chainService - The chain service instance (EVM or Solana)
* @param tokenAddress - The token contract address
* @param recipients - Array of recipients to transfer to
* @param onProgress - Optional progress callback
* @returns Promise resolving to batch processing results
*/
static async processTokenBatch(chainService, tokenAddress, recipients, onProgress) {
const transactions = [];
const failed = [];
let totalGasUsed = BigInt(0);
for (let i = 0; i < recipients.length; i++) {
const recipient = recipients[i];
try {
let txResult;
if (chainService instanceof SolanaChainService_1.SolanaChainService) {
// Solana SPL token transfer
txResult = await chainService.sendToken(tokenAddress, recipient.address, recipient.amount);
}
else {
// EVM ERC-20 token transfer
txResult = await chainService.sendToken(tokenAddress, recipient.address, recipient.amount);
}
transactions.push(txResult);
// Update gas used (only for EVM chains that support it)
if (chainService instanceof ChainService_1.ChainService && txResult && typeof txResult === 'object' && 'gasUsed' in txResult) {
const gasUsed = txResult.gasUsed;
if (gasUsed && typeof gasUsed === 'bigint') {
totalGasUsed += gasUsed;
}
}
// Progress callback
if (onProgress) {
onProgress(i + 1, recipients.length, txResult.hash);
}
// Small delay to prevent rate limiting
if (i < recipients.length - 1) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
failed.push({
recipient,
error: new Error(`Failed to transfer token to ${recipient.address}: ${errorMessage}`)
});
// Still call progress callback for failed transfers
if (onProgress) {
onProgress(i + 1, recipients.length, 'FAILED');
}
}
}
return {
transactions,
failed,
gasUsed: totalGasUsed
};
}
/**
* Splits recipients into batches of specified size.
* @param recipients - Array of all recipients
* @param batchSize - Size of each batch
* @returns Array of recipient batches
*/
static splitIntoBatches(recipients, batchSize) {
const batches = [];
for (let i = 0; i < recipients.length; i += batchSize) {
batches.push(recipients.slice(i, i + batchSize));
}
return batches;
}
/**
* Validates recipient addresses and amounts.
* @param recipients - Array of recipients to validate
* @returns Validation result
*/
static validateRecipients(recipients) {
const errors = [];
if (!recipients || recipients.length === 0) {
errors.push('No recipients provided');
return { isValid: false, errors };
}
if (recipients.length > 100) {
errors.push('Maximum 100 recipients allowed per operation');
}
const addresses = new Set();
recipients.forEach((recipient, index) => {
// Validate address
if (!recipient.address || recipient.address.trim() === '') {
errors.push(`Recipient ${index + 1}: Invalid address`);
}
else if (addresses.has(recipient.address.toLowerCase())) {
errors.push(`Recipient ${index + 1}: Duplicate address ${recipient.address}`);
}
else {
addresses.add(recipient.address.toLowerCase());
}
// Validate amount
if (!recipient.amount || recipient.amount.trim() === '') {
errors.push(`Recipient ${index + 1}: Invalid amount`);
}
else {
const amount = parseFloat(recipient.amount);
if (isNaN(amount) || amount <= 0) {
errors.push(`Recipient ${index + 1}: Amount must be a positive number`);
}
}
});
return {
isValid: errors.length === 0,
errors
};
}
}
exports.BatchProcessor = BatchProcessor;