<?php
/**
 * USDC Sender - PHP Only
 * Sends USDC from wallet to liquidation address using PHP-only signing
 * 
 * Requires: composer require web3p/web3.php
 */

require_once __DIR__ . '/config.php';

// Load Composer autoloader if available
$composerAutoload = __DIR__ . '/../vendor/autoload.php';
if (file_exists($composerAutoload)) {
    require_once $composerAutoload;
}

class USDCSender {
    private $alchemyUrl;
    private $usdcContract;
    private $chainId;
    
    public function __construct() {
        $this->alchemyUrl = ALCHEMY_URL;
        $this->usdcContract = USDC_CONTRACT; // Polygon USDC
        $this->chainId = 137; // Polygon mainnet
    }
    
    /**
     * Send USDC to address
     * @param string $fromAddress Sender wallet address
     * @param string $encryptedPrivateKey Encrypted private key
     * @param string $toAddress Recipient address (liquidation address)
     * @param float $amount Amount in USDC (will be converted to 6 decimals)
     * @return array ['success' => bool, 'txHash' => string|null, 'error' => string|null]
     */
    public function sendUSDC($fromAddress, $encryptedPrivateKey, $toAddress, $amount) {
        try {
            // Check if web3p library is available
            if (!class_exists('\Web3\Web3')) {
                return [
                    'success' => false, 
                    'error' => 'Web3 library not found. Please install: composer require web3p/web3.php'
                ];
            }
            
            // Decrypt private key
            $privateKey = $this->decryptPrivateKey($encryptedPrivateKey);
            
            if (!$privateKey) {
                return ['success' => false, 'error' => 'Failed to decrypt private key'];
            }
            
            // Remove 0x prefix if present
            $privateKey = str_replace('0x', '', $privateKey);
            
            // Convert amount to 6 decimals (USDC uses 6 decimals)
            $amountInTokens = $this->toWei($amount, 6);
            
            error_log("Preparing USDC transfer:");
            error_log("  From: $fromAddress");
            error_log("  To: $toAddress");
            error_log("  Amount (USDC): $amount");
            error_log("  Amount (tokens/wei): $amountInTokens");
            
            // Check USDC balance before sending
            $balance = $this->getUSDCBalance($fromAddress);
            if ($balance === false) {
                error_log("WARNING: Could not check USDC balance, proceeding anyway");
            } else {
                error_log("  Current USDC balance: $balance");
                $amountFloat = (float)$amount;
                if ($balance < $amountFloat) {
                    return [
                        'success' => false, 
                        'error' => "Insufficient USDC balance. Required: $amount USDC, Available: $balance USDC"
                    ];
                }
                error_log("  Balance check passed: $balance >= $amount");
            }
            
            // Check MATIC balance for gas fees (CRITICAL!)
            $maticBalance = $this->getMaticBalance($fromAddress);
            if ($maticBalance === false) {
                error_log("WARNING: Could not check MATIC balance, proceeding anyway");
            } else {
                error_log("  Current MATIC balance: $maticBalance MATIC");
                // Estimate gas cost: gasLimit * gasPrice
                // gasLimit = 100000, gasPrice = 100 Gwei = 0.0001 MATIC per Gwei
                // Total: 100000 * 0.0000001 = 0.01 MATIC minimum
                $estimatedGasCost = 0.02; // 0.02 MATIC for safety (100000 * 100 Gwei)
                if ($maticBalance < $estimatedGasCost) {
                    return [
                        'success' => false,
                        'error' => "Insufficient MATIC for gas fees. Required: ~$estimatedGasCost MATIC, Available: $maticBalance MATIC. Please add MATIC to your wallet."
                    ];
                }
                error_log("  MATIC balance check passed: $maticBalance >= $estimatedGasCost MATIC");
            }
            
            // Validate recipient address
            $toAddressClean = str_replace('0x', '', $toAddress);
            if (strlen($toAddressClean) !== 40) {
                return [
                    'success' => false,
                    'error' => "Invalid recipient address: $toAddress (length: " . strlen($toAddressClean) . ", expected: 40)"
                ];
            }
            
            // Initialize Web3
            // HttpProvider constructor takes (host, timeout) directly
            $web3 = new \Web3\Web3(new \Web3\Providers\HttpProvider($this->alchemyUrl, 10));
            $eth = $web3->eth;
            
            // Check for pending transactions and handle them
            $pendingTxs = $this->getPendingTransactionsFromAlchemy($fromAddress);
            $pendingCount = count($pendingTxs);
            
            if ($pendingCount > 0) {
                error_log("WARNING: Found $pendingCount pending transaction(s) from wallet $fromAddress");
                foreach ($pendingTxs as $idx => $pendingTx) {
                    $pendingNonce = isset($pendingTx['nonce']) ? hexdec($pendingTx['nonce']) : 'unknown';
                    $pendingGasPrice = isset($pendingTx['gasPrice']) ? hexdec($pendingTx['gasPrice']) : (isset($pendingTx['maxFeePerGas']) ? hexdec($pendingTx['maxFeePerGas']) : 0);
                    $pendingGasPriceGwei = $pendingGasPrice / 1000000000;
                    error_log("  Pending TX #$idx: nonce=$pendingNonce, gasPrice={$pendingGasPriceGwei} Gwei, hash=" . ($pendingTx['hash'] ?? 'unknown'));
                }
                
                // Strategy: Use 'latest' nonce + pending count to skip stuck transactions
                // This avoids "replacement transaction underpriced" errors
                error_log("Using 'latest' nonce + pending count to skip stuck transactions...");
            }
            
            // Get 'latest' nonce (confirmed transactions only)
            $latestNonce = null;
            $eth->getTransactionCount($fromAddress, 'latest', function ($err, $result) use (&$latestNonce) {
                if ($err !== null) {
                    error_log("Error getting nonce (latest): " . $err->getMessage());
                    return;
                }
                if (is_object($result) && method_exists($result, 'toString')) {
                    $latestNonce = (int)$result->toString();
                } else {
                    $latestNonce = is_string($result) ? hexdec(str_replace('0x', '', $result)) : (int)$result;
                }
            });
            
            if ($latestNonce === null) {
                return ['success' => false, 'error' => 'Failed to get nonce'];
            }
            
            // Calculate final nonce: latest confirmed + pending count
            // This skips all pending transactions and uses the next available nonce
            $finalNonce = $latestNonce + $pendingCount;
            $nonce = '0x' . dechex($finalNonce);
            
            if ($pendingCount > 0) {
                error_log("Nonce strategy: latest=$latestNonce, pending=$pendingCount, final=$finalNonce (skipping $pendingCount stuck transaction(s))");
            } else {
                error_log("Nonce retrieved: $nonce (latest confirmed: $latestNonce, no pending transactions)");
            }
            
            // Get fee data with proper minimums for Polygon network
            // Polygon requires higher gas prices than Ethereum to ensure transactions confirm
            // CRITICAL: Using VERY HIGH gas price (300-500 Gwei) when pending transactions exist
            // This ensures our transaction gets mined FIRST, even with stuck pending transactions
            $minPriorityFee = 30 * 1000000000; // 30 Gwei minimum (Polygon requirement)
            
            // If there are pending transactions, use MUCH higher gas to ensure we get mined first
            if ($pendingCount > 0) {
                // Use 300-500 Gwei when pending transactions exist (to replace/cancel them)
                $minMaxFee = 300 * 1000000000; // 300 Gwei minimum when pending transactions exist
                $maxFeeCap = 500 * 1000000000; // 500 Gwei cap (allows very high gas to cancel stuck transactions)
                error_log("CRITICAL: $pendingCount pending transaction(s) detected - using HIGH gas price (300-500 Gwei) to ensure transaction confirms");
            } else {
                // Normal gas price when no pending transactions
                $minMaxFee = 150 * 1000000000; // 150 Gwei minimum (normal operation)
                $maxFeeCap = 200 * 1000000000; // 200 Gwei cap (normal operation)
            }
            
            // Call Alchemy API directly for fee data
            $feeData = $this->getFeeData();
            
            $suggestedMaxFee = $feeData['maxFeePerGas'] ?? $minMaxFee;
            $suggestedPriorityFee = $feeData['maxPriorityFeePerGas'] ?? $minPriorityFee;
            
            // Enforce minimums (Polygon network requirements)
            if ($suggestedMaxFee < $minMaxFee) {
                $suggestedMaxFee = $minMaxFee; // Use minimum (150 Gwei normal, 300 Gwei if pending transactions)
            }
            if ($suggestedPriorityFee < $minPriorityFee) {
                $suggestedPriorityFee = $minPriorityFee; // At least 30 Gwei
            }
            
            // Cap maximum to prevent overpaying
            // But allow higher gas when pending transactions exist (to cancel them)
            if ($suggestedMaxFee > $maxFeeCap) {
                $suggestedMaxFee = $maxFeeCap; // Cap at configured max (200 Gwei normal, 500 Gwei if pending)
            }
            
            // CRITICAL: If pending transactions exist, ensure we use HIGH gas price
            // This helps replace/cancel the stuck transactions
            if ($pendingCount > 0 && $suggestedMaxFee < 300 * 1000000000) {
                $suggestedMaxFee = 300 * 1000000000; // Force 300 Gwei minimum when pending transactions exist
                error_log("Forcing gas price to 300 Gwei due to pending transactions (to replace/cancel them)");
            }
            
            // For legacy transactions, use the calculated max fee as gasPrice
            // This ensures transactions confirm even during network congestion
            $legacyGasPrice = $suggestedMaxFee;
            
            error_log("Fee data (with Polygon minimums):");
            error_log("  Network suggested maxFeePerGas: " . (($feeData['maxFeePerGas'] ?? 0) / 1000000000) . " Gwei");
            error_log("  Network suggested priorityFee: " . (($feeData['maxPriorityFeePerGas'] ?? 0) / 1000000000) . " Gwei");
            if ($pendingCount > 0) {
                error_log("  Final gasPrice (legacy, HIGH due to pending transactions): " . ($legacyGasPrice / 1000000000) . " Gwei");
                error_log("  WARNING: Using high gas price to ensure transaction confirms despite $pendingCount pending transaction(s)");
            } else {
                error_log("  Final gasPrice (legacy, normal): " . ($legacyGasPrice / 1000000000) . " Gwei");
            }
            
            // Build ERC20 transfer data
            $transferData = $this->buildTransferData($toAddress, $amountInTokens);
            
            if (!$transferData) {
                return ['success' => false, 'error' => 'Failed to build transfer data - invalid recipient address'];
            }
            
            error_log("Transfer data length: " . strlen($transferData) . " characters");
            error_log("Sending to address: $toAddress");
            error_log("Amount in tokens (wei): $amountInTokens");
            
            // Use legacy transaction format with high gas price
            // web3p/ethereum-tx v0.4 has issues with EIP-1559 (gas tip cap becomes 0)
            // Legacy transactions work reliably on Polygon with sufficient gas price
            // Use the calculated maxFeePerGas as gasPrice for legacy format
            // This ensures transactions confirm even during network congestion
            $legacyGasPrice = $suggestedMaxFee; // Use max fee as gas price (50-100 Gwei)
            
            $transaction = [
                'nonce' => $nonce,
                'gasPrice' => $this->intToHex($legacyGasPrice), // High gas price for reliable confirmation
                'gasLimit' => '0x186a0', // 100000 gas limit (same as mobile app)
                'to' => $this->usdcContract,
                'value' => '0x0',
                'data' => $transferData,
                'chainId' => 137 // Polygon chain ID
            ];
            
            error_log("Transaction params (Legacy format with high gas price):");
            error_log("  Nonce: $nonce");
            error_log("  gasPrice: " . $this->intToHex($legacyGasPrice) . " (" . ($legacyGasPrice / 1000000000) . " Gwei)");
            error_log("  gasLimit: 0x186a0 (100000)");
            error_log("  Note: Using legacy format (web3p/ethereum-tx v0.4 EIP-1559 bug) with high gas price for reliable confirmation");
            
            // Sign transaction manually
            $signedTx = $this->signTransaction($transaction, $privateKey);
            
            if (!$signedTx) {
                return ['success' => false, 'error' => 'Failed to sign transaction'];
            }
            
            // Ensure signed transaction has 0x prefix
            if (strpos($signedTx, '0x') !== 0) {
                $signedTx = '0x' . $signedTx;
            }
            
            // Send transaction directly via curl (same as mobile app relayer)
            // This is more reliable than web3p's async callback system
            $txHash = null;
            $maxRetries = 3;
            $retryCount = 0;
            
            while ($retryCount < $maxRetries && !$txHash) {
                $payload = [
                    'jsonrpc' => '2.0',
                    'id' => 1,
                    'method' => 'eth_sendRawTransaction',
                    'params' => [$signedTx]
                ];
                
                $ch = curl_init($this->alchemyUrl);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
                curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
                curl_setopt($ch, CURLOPT_TIMEOUT, 30);
                
                $response = curl_exec($ch);
                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $curlError = curl_error($ch);
                curl_close($ch);
                
                if ($curlError) {
                    error_log("CURL error (attempt " . ($retryCount + 1) . "): $curlError");
                    $retryCount++;
                    if ($retryCount < $maxRetries) {
                        sleep(1);
                    }
                    continue;
                }
                
                if ($httpCode !== 200) {
                    error_log("HTTP error (attempt " . ($retryCount + 1) . "): HTTP $httpCode");
                    $retryCount++;
                    if ($retryCount < $maxRetries) {
                        sleep(1);
                    }
                    continue;
                }
                
                $result = json_decode($response, true);
                
                if (isset($result['error'])) {
                    error_log("Alchemy API error (attempt " . ($retryCount + 1) . "): " . json_encode($result['error']));
                    $retryCount++;
                    if ($retryCount < $maxRetries) {
                        sleep(1);
                    }
                    continue;
                }
                
                if (isset($result['result'])) {
                    $txHash = $result['result'];
                    error_log("Transaction sent successfully via direct curl (attempt " . ($retryCount + 1) . ")");
                    break;
                } else {
                    error_log("No transaction hash in response (attempt " . ($retryCount + 1) . "): " . $response);
                    $retryCount++;
                    if ($retryCount < $maxRetries) {
                        sleep(1);
                    }
                }
            }
            
            if (!$txHash) {
                return ['success' => false, 'error' => 'Failed to send transaction after ' . $maxRetries . ' attempts. Last response: ' . ($response ?? 'no response')];
            }
            
            // Verify transaction was actually broadcast by checking its status
            error_log("Transaction broadcast successfully. TX Hash: $txHash");
            error_log("Verifying transaction status...");
            
            // Wait a moment for transaction to propagate
            sleep(2);
            
            // Check transaction status
            $txStatus = $this->getTransactionStatus($txHash);
            if ($txStatus !== false) {
                error_log("Transaction status check: " . ($txStatus['confirmed'] ? 'CONFIRMED' : 'PENDING'));
                if (isset($txStatus['blockNumber'])) {
                    error_log("Transaction confirmed in block: " . $txStatus['blockNumber']);
                }
            }
            
            return [
                'success' => true,
                'txHash' => $txHash,
                'status' => $txStatus
            ];
            
        } catch (Exception $e) {
            error_log("USDC Send Error: " . $e->getMessage());
            error_log("Stack trace: " . $e->getTraceAsString());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
    
    /**
     * Decrypt private key
     */
    private function decryptPrivateKey($encryptedKey) {
        try {
            require_once __DIR__ . '/functions.php';
            return decrypt_data($encryptedKey, ENCRYPTION_KEY);
        } catch (Exception $e) {
            error_log("Decrypt error: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Convert amount to wei (with decimals)
     */
    private function toWei($amount, $decimals = 6) {
        $multiplier = pow(10, $decimals);
        $amountStr = number_format($amount, $decimals, '.', '');
        $wei = bcmul($amountStr, (string)$multiplier, 0);
        return $wei;
    }
    
    /**
     * Build ERC20 transfer data
     */
    private function buildTransferData($toAddress, $amount) {
        // Function signature: transfer(address,uint256) = 0xa9059cbb
        $functionSignature = 'a9059cbb';
        
        // Remove 0x prefix if present and normalize address (lowercase, no 0x)
        $toAddress = str_replace('0x', '', $toAddress);
        $toAddress = strtolower($toAddress);
        
        // Address should be exactly 40 hex characters (20 bytes)
        if (strlen($toAddress) !== 40) {
            error_log("ERROR: Invalid recipient address length: " . strlen($toAddress) . " (expected 40)");
            error_log("Address: " . $toAddress);
            return null;
        }
        
        // Pad address to 64 characters (32 bytes) with leading zeros
        $toAddressPadded = str_pad($toAddress, 64, '0', STR_PAD_LEFT);
        
        // Convert amount to hex and pad to 64 characters (32 bytes)
        // Amount should already be in wei (6 decimals for USDC)
        $amountHex = $this->decToHex((string)$amount);
        $amountHexPadded = str_pad($amountHex, 64, '0', STR_PAD_LEFT);
        
        // Verify padding - amount should be exactly 64 hex characters
        if (strlen($amountHexPadded) !== 64) {
            error_log("ERROR: Amount hex padding failed! Expected 64 chars, got " . strlen($amountHexPadded));
            error_log("  Amount (decimal): $amount");
            error_log("  Amount (hex): $amountHex");
            error_log("  Amount (hex padded): $amountHexPadded");
            return null;
        }
        
        // Verify address padding - address should be exactly 64 hex characters
        if (strlen($toAddressPadded) !== 64) {
            error_log("ERROR: Address hex padding failed! Expected 64 chars, got " . strlen($toAddressPadded));
            error_log("  Address: 0x" . $toAddress);
            error_log("  Address (padded): $toAddressPadded");
            return null;
        }
        
        $transferData = '0x' . $functionSignature . $toAddressPadded . $amountHexPadded;
        
        // Full transfer data should be: 0x (2) + function sig (8) + address (64) + amount (64) = 138 characters
        $expectedLength = 2 + 8 + 64 + 64; // 138
        if (strlen($transferData) !== $expectedLength) {
            error_log("ERROR: Transfer data length mismatch! Expected $expectedLength chars, got " . strlen($transferData));
            error_log("  Transfer data: $transferData");
            return null;
        }
        
        error_log("Transfer data built successfully:");
        error_log("  Recipient: 0x" . $toAddress);
        error_log("  Amount (wei): $amount");
        error_log("  Amount (hex): $amountHex");
        error_log("  Amount (hex padded, 64 chars): $amountHexPadded");
        error_log("  Transfer data length: " . strlen($transferData) . " chars (expected: $expectedLength)");
        error_log("  Transfer data: $transferData");
        
        return $transferData;
    }
    
    /**
     * Convert decimal to hex
     */
    private function decToHex($decimal) {
        // Convert to string for bcmath operations
        $decimal = (string)$decimal;
        
        // Use gmp extension if available (faster and more reliable)
        if (function_exists('gmp_init')) {
            $gmp = gmp_init($decimal, 10);
            $hex = gmp_strval($gmp, 16);
            return strtolower($hex);
        }
        
        // Fallback to bcmath
        $hex = '';
        while (bccomp($decimal, '0') > 0) {
            $remainder = bcmod($decimal, '16');
            $hex = dechex((int)$remainder) . $hex;
            $decimal = bcdiv($decimal, '16', 0);
        }
        
        return $hex ?: '0';
    }
    
    /**
     * Sign Ethereum transaction using web3p/ethereum-tx
     * Fully automatic - no manual intervention needed
     */
    private function signTransaction($transaction, $privateKey) {
        try {
            // Check if ethereum-tx library is available
            if (!class_exists('\Web3p\EthereumTx\Transaction')) {
                error_log("ERROR: web3p/ethereum-tx library not found.");
                error_log("Please install: composer require web3p/ethereum-tx");
                error_log("Note: This requires PHP GMP extension. Install with: yum install php-gmp (or apt-get install php-gmp)");
                return null;
            }
            
            // Ensure all values are in hex format with 0x prefix
            $nonce = $this->ensureHex($transaction['nonce']);
            $chainId = is_int($transaction['chainId']) ? $transaction['chainId'] : $this->hexToInt($transaction['chainId']);
            
            // Remove 0x prefix from data if present, then add it back
            $data = $this->ensureHex($transaction['data']);
            
            // Remove 0x prefix from to address if present, then add it back
            $to = $this->ensureHex($transaction['to']);
            
            // Handle EIP-1559 (Polygon) or legacy transactions
            $txParams = [
                'nonce' => $nonce,
                'gasLimit' => $this->ensureHex($transaction['gasLimit'] ?? $transaction['gas'] ?? '0x186a0'),
                'to' => $to,
                'value' => $transaction['value'] ?? '0x0',
                'data' => $data,
                'chainId' => $chainId
            ];
            
            // Use EIP-1559 format if maxFeePerGas is present (same as mobile app)
            if (isset($transaction['maxFeePerGas']) && isset($transaction['maxPriorityFeePerGas'])) {
                // Ensure values are properly formatted as hex strings
                $maxFeePerGas = $transaction['maxFeePerGas'];
                $maxPriorityFeePerGas = $transaction['maxPriorityFeePerGas'];
                
                // Convert to integer if needed, then to hex
                if (is_string($maxFeePerGas) && strpos($maxFeePerGas, '0x') === 0) {
                    $maxFeeInt = hexdec(str_replace('0x', '', $maxFeePerGas));
                } else {
                    $maxFeeInt = is_numeric($maxFeePerGas) ? (int)$maxFeePerGas : hexdec(str_replace('0x', '', $maxFeePerGas));
                }
                
                if (is_string($maxPriorityFeePerGas) && strpos($maxPriorityFeePerGas, '0x') === 0) {
                    $priorityFeeInt = hexdec(str_replace('0x', '', $maxPriorityFeePerGas));
                } else {
                    $priorityFeeInt = is_numeric($maxPriorityFeePerGas) ? (int)$maxPriorityFeePerGas : hexdec(str_replace('0x', '', $maxPriorityFeePerGas));
                }
                
                // Convert to hex with proper padding
                $txParams['maxFeePerGas'] = '0x' . str_pad(dechex($maxFeeInt), 0, '0', STR_PAD_LEFT);
                $txParams['maxPriorityFeePerGas'] = '0x' . str_pad(dechex($priorityFeeInt), 0, '0', STR_PAD_LEFT);
                
                error_log("Using EIP-1559 transaction format:");
                error_log("  maxFeePerGas: " . $txParams['maxFeePerGas'] . " (" . ($maxFeeInt / 1000000000) . " Gwei)");
                error_log("  maxPriorityFeePerGas: " . $txParams['maxPriorityFeePerGas'] . " (" . ($priorityFeeInt / 1000000000) . " Gwei)");
                error_log("  Raw values - maxFeeInt: $maxFeeInt, priorityFeeInt: $priorityFeeInt");
            } elseif (isset($transaction['gasPrice'])) {
                // Fallback to legacy format if gasPrice is provided
                $gasPrice = $this->ensureHex($transaction['gasPrice']);
                $txParams['gasPrice'] = $gasPrice;
                
                $gasPriceInt = $this->hexToInt($gasPrice);
                error_log("Using legacy transaction format - gasPrice: $gasPrice ($gasPriceInt wei = " . ($gasPriceInt / 1000000000) . " Gwei)");
            } else {
                // Fallback to safe EIP-1559 values if nothing is set
                $txParams['maxFeePerGas'] = '0x174876e800'; // 100 Gwei
                $txParams['maxPriorityFeePerGas'] = '0x6fc23ac00'; // 30 Gwei
                error_log("Using default EIP-1559 values: maxFeePerGas=100 Gwei, maxPriorityFeePerGas=30 Gwei");
            }
            
            // Create transaction object
            // The library should auto-detect EIP-1559 from maxFeePerGas/maxPriorityFeePerGas presence
            // Log the exact parameters being passed to the library
            error_log("Transaction parameters being passed to library:");
            error_log("  " . json_encode($txParams, JSON_PRETTY_PRINT));
            
            try {
                $tx = new \Web3p\EthereumTx\Transaction($txParams);
                
                // Sign transaction with private key
                // Private key should be hex string with 0x prefix
                $signedTx = $tx->sign('0x' . $privateKey);
                
                error_log("Transaction signed successfully. Signed TX length: " . strlen($signedTx));
                
                return $signedTx;
            } catch (Exception $e) {
                error_log("ERROR: Transaction signing failed: " . $e->getMessage());
                error_log("Attempting fallback to legacy transaction format...");
                
                // Fallback: Use legacy transaction with high gas price
                // Polygon accepts legacy transactions but requires higher gas
                $legacyTxParams = [
                    'nonce' => $nonce,
                    'gasPrice' => isset($txParams['maxFeePerGas']) ? $txParams['maxFeePerGas'] : '0x174876e800', // Use maxFeePerGas as gasPrice
                    'gasLimit' => $txParams['gasLimit'],
                    'to' => $txParams['to'],
                    'value' => $txParams['value'],
                    'data' => $txParams['data'],
                    'chainId' => $chainId
                ];
                
                error_log("Using legacy transaction fallback with gasPrice: " . $legacyTxParams['gasPrice']);
                $tx = new \Web3p\EthereumTx\Transaction($legacyTxParams);
                $signedTx = $tx->sign('0x' . $privateKey);
                
                error_log("Legacy transaction signed successfully. Signed TX length: " . strlen($signedTx));
                return $signedTx;
            }
            
        } catch (Exception $e) {
            error_log("Transaction signing error: " . $e->getMessage());
            error_log("Stack trace: " . $e->getTraceAsString());
            return null;
        }
    }
    
    /**
     * Ensure value is in hex format with 0x prefix
     */
    private function ensureHex($value) {
        if (is_string($value)) {
            // Remove 0x if present
            $value = str_replace('0x', '', $value);
            // Add 0x prefix
            return '0x' . $value;
        } elseif (is_int($value)) {
            return '0x' . dechex($value);
        }
        return $value;
    }
    
    /**
     * Convert hex string to integer
     */
    private function hexToInt($hex) {
        if (is_string($hex)) {
            $hex = str_replace('0x', '', $hex);
            return hexdec($hex);
        }
        return $hex;
    }
    
    /**
     * Convert large integer to hex string (handles large numbers properly)
     */
    private function intToHex($int) {
        // Use gmp if available (handles large numbers better)
        if (function_exists('gmp_init') && function_exists('gmp_strval')) {
            $gmp = gmp_init((string)$int, 10);
            return '0x' . gmp_strval($gmp, 16);
        }
        
        // Fallback to dechex (may fail for very large numbers)
        if ($int > PHP_INT_MAX) {
            // For numbers larger than PHP_INT_MAX, use bcmath
            $hex = '';
            $num = (string)$int;
            while (bccomp($num, '0') > 0) {
                $remainder = bcmod($num, '16');
                $hex = dechex((int)$remainder) . $hex;
                $num = bcdiv($num, '16', 0);
            }
            return '0x' . ($hex ?: '0');
        }
        
        return '0x' . dechex($int);
    }
    
    /**
     * Get EIP-1559 fee data from Alchemy API (same as mobile app)
     */
    private function getFeeData() {
        try {
            // Call Alchemy API for fee data
            $payload = [
                'jsonrpc' => '2.0',
                'method' => 'eth_maxPriorityFeePerGas',
                'params' => [],
                'id' => 1
            ];
            
            $ch = curl_init($this->alchemyUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($httpCode !== 200) {
                error_log("Fee data fetch failed: HTTP $httpCode, using defaults");
                return [
                    'maxFeePerGas' => 100 * 1000000000, // 100 Gwei
                    'maxPriorityFeePerGas' => 30 * 1000000000 // 30 Gwei
                ];
            }
            
            $result = json_decode($response, true);
            $maxPriorityFeePerGas = isset($result['result']) ? hexdec(str_replace('0x', '', $result['result'])) : (30 * 1000000000);
            
            // Get base fee from latest block
            $blockPayload = [
                'jsonrpc' => '2.0',
                'method' => 'eth_getBlockByNumber',
                'params' => ['latest', false],
                'id' => 2
            ];
            
            $ch = curl_init($this->alchemyUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($blockPayload));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            
            $blockResponse = curl_exec($ch);
            curl_close($ch);
            
            $blockResult = json_decode($blockResponse, true);
            $baseFee = isset($blockResult['result']['baseFeePerGas']) 
                ? hexdec(str_replace('0x', '', $blockResult['result']['baseFeePerGas'])) 
                : (70 * 1000000000); // Default 70 Gwei base fee
            
            // Calculate maxFeePerGas = baseFee * 2 + maxPriorityFeePerGas (standard formula)
            $maxFeePerGas = ($baseFee * 2) + $maxPriorityFeePerGas;
            
            // Ensure minimums for Polygon
            $minPriorityFee = 30 * 1000000000; // 30 Gwei
            $minMaxFee = 100 * 1000000000; // 100 Gwei
            
            if ($maxPriorityFeePerGas < $minPriorityFee) {
                $maxPriorityFeePerGas = $minPriorityFee;
            }
            if ($maxFeePerGas < $minMaxFee) {
                $maxFeePerGas = $minMaxFee;
            }
            
            return [
                'maxFeePerGas' => $maxFeePerGas,
                'maxPriorityFeePerGas' => $maxPriorityFeePerGas
            ];
            
        } catch (Exception $e) {
            error_log("Error fetching fee data: " . $e->getMessage());
            // Return safe defaults
            return [
                'maxFeePerGas' => 100 * 1000000000, // 100 Gwei
                'maxPriorityFeePerGas' => 30 * 1000000000 // 30 Gwei
            ];
        }
    }
    
    /**
     * Get USDC balance from wallet address
     */
    private function getUSDCBalance($walletAddress) {
        try {
            // Call balanceOf(address) on USDC contract
            $data = '0x70a08231' . str_pad(str_replace('0x', '', strtolower($walletAddress)), 64, '0', STR_PAD_LEFT);
            
            $payload = [
                'jsonrpc' => '2.0',
                'method' => 'eth_call',
                'params' => [
                    [
                        'to' => $this->usdcContract,
                        'data' => $data
                    ],
                    'latest'
                ],
                'id' => 1
            ];
            
            $ch = curl_init($this->alchemyUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($httpCode !== 200) {
                error_log("Balance check failed: HTTP $httpCode");
                return false;
            }
            
            $result = json_decode($response, true);
            
            if (isset($result['error'])) {
                error_log("Balance check error: " . ($result['error']['message'] ?? 'Unknown error'));
                return false;
            }
            
            if (!isset($result['result'])) {
                return false;
            }
            
            // Convert hex result to decimal (USDC has 6 decimals)
            $balanceHex = str_replace('0x', '', $result['result']);
            $balanceWei = hexdec($balanceHex);
            $balanceUSDC = $balanceWei / 1000000; // 6 decimals
            
            return $balanceUSDC;
            
        } catch (Exception $e) {
            error_log("Error checking USDC balance: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Get MATIC balance from wallet address (for gas fees)
     */
    private function getMaticBalance($walletAddress) {
        try {
            $payload = [
                'jsonrpc' => '2.0',
                'method' => 'eth_getBalance',
                'params' => [$walletAddress, 'latest'],
                'id' => 1
            ];
            
            $ch = curl_init($this->alchemyUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($httpCode !== 200) {
                error_log("MATIC balance check failed: HTTP $httpCode");
                return false;
            }
            
            $result = json_decode($response, true);
            
            if (isset($result['error'])) {
                error_log("MATIC balance check error: " . ($result['error']['message'] ?? 'Unknown error'));
                return false;
            }
            
            if (!isset($result['result'])) {
                error_log("MATIC balance check: No result in response");
                return false;
            }
            
            // Convert from wei to MATIC (18 decimals)
            $balanceInWei = $result['result'];
            $balanceInWei = str_replace('0x', '', $balanceInWei);
            $balanceInWei = hexdec($balanceInWei);
            $balanceInMatic = $balanceInWei / 1000000000000000000; // 10^18
            
            return (float)$balanceInMatic;
        } catch (Exception $e) {
            error_log("Error getting MATIC balance: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Get transaction status from blockchain
     */
    private function getTransactionStatus($txHash) {
        try {
            $payload = [
                'jsonrpc' => '2.0',
                'method' => 'eth_getTransactionReceipt',
                'params' => [$txHash],
                'id' => 1
            ];
            
            $ch = curl_init($this->alchemyUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            
            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            
            if ($httpCode !== 200) {
                return false;
            }
            
            $result = json_decode($response, true);
            
            if (isset($result['error']) || !isset($result['result']) || $result['result'] === null) {
                // Transaction not confirmed yet (still pending)
                return ['confirmed' => false, 'pending' => true];
            }
            
            $receipt = $result['result'];
            return [
                'confirmed' => true,
                'blockNumber' => $receipt['blockNumber'] ?? null,
                'status' => isset($receipt['status']) ? ($receipt['status'] === '0x1' ? 'success' : 'failed') : 'unknown'
            ];
        } catch (Exception $e) {
            error_log("Error checking transaction status: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Get pending transactions from a wallet address
     * Returns array of pending transaction objects
     */
    private function getPendingTransactions($walletAddress) {
        try {
            // Use Alchemy's pendingTransactions API if available, or check manually
            // For now, we'll use a simple approach: check if there are transactions in the mempool
            // Note: This is a simplified check - full implementation would query mempool
            
            // Alternative: Use eth_getTransactionCount with 'pending' vs 'latest' to detect pending
            // But we need actual transaction details, so we'll use a different approach
            
            // For Polygon, we can use Alchemy's getAssetTransfers or check pending transactions
            // Since web3p doesn't have direct mempool access, we'll return empty array
            // The nonce difference will handle this
            
            // TODO: Implement full pending transaction detection using Alchemy API
            // For now, return empty array - the nonce strategy will still work
            return [];
            
        } catch (Exception $e) {
            error_log("Error getting pending transactions: " . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Get pending transactions using Alchemy API directly
     * This provides actual pending transaction details
     */
    private function getPendingTransactionsFromAlchemy($walletAddress) {
        try {
            // Use Alchemy's eth_getPendingTransactions or similar
            // Note: This requires Alchemy API support for pending transactions
            
            // For now, we'll use a workaround: check transaction count difference
            // 'pending' count - 'latest' count = number of pending transactions
            
            $payloadPending = [
                'jsonrpc' => '2.0',
                'method' => 'eth_getTransactionCount',
                'params' => [$walletAddress, 'pending'],
                'id' => 1
            ];
            
            $payloadLatest = [
                'jsonrpc' => '2.0',
                'method' => 'eth_getTransactionCount',
                'params' => [$walletAddress, 'latest'],
                'id' => 2
            ];
            
            // Get both counts
            $ch = curl_init($this->alchemyUrl);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadPending));
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            $responsePending = curl_exec($ch);
            
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadLatest));
            $responseLatest = curl_exec($ch);
            curl_close($ch);
            
            $resultPending = json_decode($responsePending, true);
            $resultLatest = json_decode($responseLatest, true);
            
            $pendingCount = 0;
            if (isset($resultPending['result']) && isset($resultLatest['result'])) {
                $pendingNonce = hexdec(str_replace('0x', '', $resultPending['result']));
                $latestNonce = hexdec(str_replace('0x', '', $resultLatest['result']));
                $pendingCount = max(0, $pendingNonce - $latestNonce);
            }
            
            if ($pendingCount > 0) {
                error_log("Detected $pendingCount pending transaction(s) for wallet $walletAddress");
            }
            
            // Return array with count info (we don't have full transaction details without mempool access)
            return array_fill(0, $pendingCount, ['nonce' => 'pending', 'count' => $pendingCount]);
            
        } catch (Exception $e) {
            error_log("Error getting pending transactions from Alchemy: " . $e->getMessage());
            return [];
        }
    }
}
