"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionMirroringService = void 0;
const ethers_1 = require("ethers");
const dotenv = __importStar(require("dotenv"));
const path_1 = __importDefault(require("path"));
dotenv.config();
// Fill contract addresses from config/smart-contracts-master.json when not set in .env (CJS-safe)
try {
    const loaderPath = path_1.default.resolve(__dirname, '../../../../config/contracts-loader.cjs');
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const { loadContractsIntoProcessEnv } = require(loaderPath);
    if (typeof loadContractsIntoProcessEnv === 'function')
        loadContractsIntoProcessEnv([138, 1]);
}
catch (_) { /* optional: master JSON not found when run outside repo */ }
/**
 * Transaction Mirroring Service
 *
 * Monitors ChainID 138 transactions and mirrors them to TransactionMirror contract on Mainnet
 */
// Contract addresses (from .env or config/smart-contracts-master.json)
const MIRROR_ADDRESS = process.env.MIRROR_ADDRESS ||
    process.env.TRANSACTION_MIRROR_MAINNET ||
    '0x4CF42c4F1dBa748601b8938be3E7ABD732E87cE9';
const CHAIN138_RPC = process.env.CHAIN138_RPC_URL || 'http://192.168.11.211:8545';
const MAINNET_RPC = process.env.MAINNET_RPC_URL || 'https://eth.llamarpc.com';
const BATCH_INTERVAL_MS = parseInt(process.env.BATCH_INTERVAL_MS || '60000', 10);
const MAX_BATCH_SIZE = 100;
/** Skip txs below this wei value (default 0 = mirror all). */
const MIRROR_MIN_VALUE_WEI = BigInt(process.env.MIRROR_MIN_VALUE_WEI || '0');
/** If set, only mirror txs in blocks >= this height (pilot: set to current block on start). */
const MIRROR_MIN_BLOCK = process.env.MIRROR_MIN_BLOCK
    ? parseInt(process.env.MIRROR_MIN_BLOCK, 10)
    : undefined;
// Contract ABI (simplified)
const MIRROR_ABI = [
    "function mirrorTransaction(bytes32 txHash, address from, address to, uint256 value, uint256 blockNumber, uint256 blockTimestamp, uint256 gasUsed, bool success, bytes calldata data) external",
    "function mirrorBatchTransactions(bytes32[] calldata txHashes, address[] calldata froms, address[] calldata tos, uint256[] calldata values, uint256[] calldata blockNumbers, uint256[] calldata blockTimestamps, uint256[] calldata gasUseds, bool[] calldata successes, bytes[] calldata datas) external",
    "function processed(bytes32) external view returns (bool)"
];
class TransactionMirroringService {
    constructor() {
        this.transactionQueue = [];
        this.isRunning = false;
        if (!process.env.PRIVATE_KEY) {
            throw new Error('PRIVATE_KEY environment variable is required');
        }
        this.chain138Provider = new ethers_1.ethers.JsonRpcProvider(CHAIN138_RPC);
        this.mainnetProvider = new ethers_1.ethers.JsonRpcProvider(MAINNET_RPC);
        this.mainnetWallet = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, this.mainnetProvider);
        this.mirrorContract = new ethers_1.ethers.Contract(MIRROR_ADDRESS, MIRROR_ABI, this.mainnetWallet);
    }
    /**
     * Start monitoring transactions and mirroring them
     */
    async start() {
        if (this.isRunning) {
            console.log('Service already running');
            return;
        }
        this.isRunning = true;
        console.log('Starting Transaction Mirroring Service...');
        console.log(`ChainID 138 RPC: ${CHAIN138_RPC}`);
        console.log(`Mainnet RPC: ${MAINNET_RPC}`);
        console.log(`TransactionMirror: ${MIRROR_ADDRESS}`);
        console.log(`Admin: ${this.mainnetWallet.address}`);
        console.log(`Batch interval: ${BATCH_INTERVAL_MS}ms`);
        console.log(`Max batch size: ${MAX_BATCH_SIZE}`);
        if (MIRROR_MIN_BLOCK !== undefined) {
            console.log(`Mirror min block: ${MIRROR_MIN_BLOCK}`);
        }
        if (MIRROR_MIN_VALUE_WEI > 0n) {
            console.log(`Mirror min value wei: ${MIRROR_MIN_VALUE_WEI}`);
        }
        // Monitor new blocks
        this.chain138Provider.on('block', async (blockNumber) => {
            if (MIRROR_MIN_BLOCK !== undefined && blockNumber < MIRROR_MIN_BLOCK) {
                return;
            }
            try {
                await this.processBlockTransactions(blockNumber);
            }
            catch (error) {
                console.error(`Error processing block ${blockNumber}:`, error);
            }
        });
        // Start periodic batch submission
        this.batchInterval = setInterval(async () => {
            if (this.transactionQueue.length > 0) {
                await this.submitBatch();
            }
        }, BATCH_INTERVAL_MS);
        console.log('Service started. Monitoring blocks...');
    }
    /**
     * Process all transactions in a block
     */
    async processBlockTransactions(blockNumber) {
        const block = await this.chain138Provider.getBlock(blockNumber, true);
        if (!block || !block.transactions || block.transactions.length === 0) {
            return;
        }
        console.log(`Processing block ${blockNumber} with ${block.transactions.length} transactions...`);
        for (const txHash of block.transactions) {
            try {
                const blockTimestamp = block.timestamp ? BigInt(block.timestamp) : 0n;
                await this.processTransaction(txHash.toString(), blockNumber, blockTimestamp);
            }
            catch (error) {
                console.error(`Error processing transaction ${txHash}:`, error);
            }
        }
    }
    /**
     * Process a single transaction
     */
    async processTransaction(txHash, blockNumber, blockTimestamp) {
        // Get transaction details
        const tx = await this.chain138Provider.getTransaction(txHash);
        const receipt = await this.chain138Provider.getTransactionReceipt(txHash);
        if (!tx || !receipt) {
            return;
        }
        if (MIRROR_MIN_VALUE_WEI > 0n && tx.value < MIRROR_MIN_VALUE_WEI) {
            return;
        }
        // Check if already mirrored (optional - can track in database)
        try {
            const processed = await this.mirrorContract.processed(txHash);
            if (processed) {
                return; // Already mirrored
            }
        }
        catch (error) {
            // Continue if check fails
        }
        // Create mirrored transaction object
        const mirroredTx = {
            txHash: txHash,
            from: tx.from,
            to: tx.to || '0x0000000000000000000000000000000000000000',
            value: tx.value,
            blockNumber: blockNumber,
            blockTimestamp: Number(blockTimestamp),
            gasUsed: receipt.gasUsed,
            success: receipt.status === 1,
            data: tx.data
        };
        // Add to queue
        this.transactionQueue.push(mirroredTx);
        // Submit batch if queue is full
        if (this.transactionQueue.length >= MAX_BATCH_SIZE) {
            await this.submitBatch();
        }
    }
    /**
     * Submit a batch of transactions to TransactionMirror
     */
    async submitBatch() {
        if (this.transactionQueue.length === 0) {
            return;
        }
        // Take up to MAX_BATCH_SIZE transactions
        const batch = this.transactionQueue.splice(0, MAX_BATCH_SIZE);
        try {
            console.log(`Submitting batch of ${batch.length} transactions...`);
            // Prepare batch arrays
            const txHashes = batch.map(tx => tx.txHash);
            const froms = batch.map(tx => tx.from);
            const tos = batch.map(tx => tx.to);
            const values = batch.map(tx => tx.value);
            const blockNumbers = batch.map(tx => tx.blockNumber);
            const blockTimestamps = batch.map(tx => tx.blockTimestamp);
            const gasUseds = batch.map(tx => tx.gasUsed);
            const successes = batch.map(tx => tx.success);
            const datas = batch.map(tx => tx.data);
            const tx = await this.mirrorContract.mirrorBatchTransactions(txHashes, froms, tos, values, blockNumbers, blockTimestamps, gasUseds, successes, datas);
            console.log(`Transaction submitted: ${tx.hash}`);
            await tx.wait();
            console.log(`✓ Mirrored ${batch.length} transactions (tx: ${tx.hash})`);
        }
        catch (error) {
            console.error(`Failed to mirror batch:`, error);
            // Put transactions back in queue for retry
            this.transactionQueue.unshift(...batch);
            // TODO: Implement exponential backoff retry logic
        }
    }
    /**
     * Stop the service
     */
    stop() {
        this.isRunning = false;
        if (this.batchInterval) {
            clearInterval(this.batchInterval);
        }
        this.chain138Provider.removeAllListeners('block');
        // Submit any remaining transactions
        if (this.transactionQueue.length > 0) {
            console.log(`Submitting remaining ${this.transactionQueue.length} transactions...`);
            this.submitBatch().catch(console.error);
        }
        console.log('Service stopped');
    }
}
exports.TransactionMirroringService = TransactionMirroringService;
// Run service if executed directly
if (require.main === module) {
    const service = new TransactionMirroringService();
    service.start().catch(console.error);
    // Graceful shutdown
    process.on('SIGINT', () => {
        console.log('\nShutting down...');
        service.stop();
        process.exit(0);
    });
}
