"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlockchainNormalizer = void 0;
const block_size_calculator_1 = require("./utils/block-size-calculator");
const value_normalization_1 = require("./value-normalization");
const DEFAULT_FIELD_POLICY = {
    allowLegacyReceiptRoot: true,
    allowMissingTotalDifficulty: true,
    allowMissingLogsBloom: true,
    allowMissingNonce: true,
};
class BlockchainNormalizer {
    networkConfig;
    fieldPolicy;
    constructor(networkConfig) {
        this.networkConfig = networkConfig;
        this.fieldPolicy = { ...DEFAULT_FIELD_POLICY, ...(networkConfig.fieldPolicy ?? {}) };
    }
    normalizeBlock(rawBlock) {
        this.assertRequired(rawBlock, ['hash', 'parentHash', 'transactionsRoot', 'stateRoot', 'miner', 'extraData'], 'Block');
        if (rawBlock.blockNumber === undefined || rawBlock.blockNumber === null) {
            throw new Error('Block is missing required blockNumber field');
        }
        const txs = rawBlock.transactions ?? [];
        const fullTransactions = txs.filter((tx) => typeof tx !== 'string');
        const transactionHashes = txs
            .map((tx) => (typeof tx === 'string' ? tx : tx.hash))
            .filter(Boolean)
            .map((tx) => (0, value_normalization_1.normalizeHex)(tx));
        const block = {
            hash: (0, value_normalization_1.normalizeHex)(rawBlock.hash),
            parentHash: (0, value_normalization_1.normalizeHex)(rawBlock.parentHash),
            blockNumber: rawBlock.blockNumber,
            transactionsRoot: (0, value_normalization_1.normalizeHex)(rawBlock.transactionsRoot),
            stateRoot: (0, value_normalization_1.normalizeHex)(rawBlock.stateRoot),
            miner: (0, value_normalization_1.normalizeAddress)(rawBlock.miner),
            extraData: (0, value_normalization_1.normalizeHex)(rawBlock.extraData),
            size: 0,
            sizeWithoutReceipts: 0,
            gasLimit: rawBlock.gasLimit,
            gasUsed: rawBlock.gasUsed,
            timestamp: rawBlock.timestamp,
            uncles: rawBlock.uncles.map((u) => (0, value_normalization_1.normalizeHex)(u)),
            transactionHashes,
        };
        if (rawBlock.nonce !== undefined || !this.fieldPolicy.allowMissingNonce) {
            block.nonce = (0, value_normalization_1.normalizeHex)(rawBlock.nonce);
        }
        if (rawBlock.sha3Uncles !== undefined)
            block.sha3Uncles = (0, value_normalization_1.normalizeHex)(rawBlock.sha3Uncles);
        if (rawBlock.logsBloom !== undefined || !this.fieldPolicy.allowMissingLogsBloom) {
            block.logsBloom = (0, value_normalization_1.normalizeHex)(rawBlock.logsBloom);
        }
        if (rawBlock.receiptsRoot !== undefined)
            block.receiptsRoot = (0, value_normalization_1.normalizeHex)(rawBlock.receiptsRoot);
        if (rawBlock.difficulty !== undefined)
            block.difficulty = (0, value_normalization_1.quantityToDecimalString)(rawBlock.difficulty);
        if (rawBlock.totalDifficulty !== undefined) {
            block.totalDifficulty = (0, value_normalization_1.quantityToDecimalString)(rawBlock.totalDifficulty);
        }
        else if (!this.fieldPolicy.allowMissingTotalDifficulty) {
            throw new Error('Block is missing required totalDifficulty field');
        }
        if (this.networkConfig.hasEIP1559 && rawBlock.baseFeePerGas !== undefined) {
            block.baseFeePerGas = (0, value_normalization_1.quantityToDecimalString)(rawBlock.baseFeePerGas);
        }
        if (this.networkConfig.hasWithdrawals) {
            if (rawBlock.withdrawals) {
                block.withdrawals = rawBlock.withdrawals.map((w) => ({
                    index: (0, value_normalization_1.quantityToDecimalString)(w.index),
                    validatorIndex: (0, value_normalization_1.quantityToDecimalString)(w.validatorIndex),
                    address: (0, value_normalization_1.normalizeAddress)(w.address),
                    amount: (0, value_normalization_1.quantityToDecimalString)(w.amount),
                }));
            }
            if (rawBlock.withdrawalsRoot)
                block.withdrawalsRoot = (0, value_normalization_1.normalizeHex)(rawBlock.withdrawalsRoot);
        }
        if (this.networkConfig.hasBlobTransactions) {
            if (rawBlock.blobGasUsed !== undefined)
                block.blobGasUsed = (0, value_normalization_1.quantityToDecimalString)(rawBlock.blobGasUsed);
            if (rawBlock.excessBlobGas !== undefined)
                block.excessBlobGas = (0, value_normalization_1.quantityToDecimalString)(rawBlock.excessBlobGas);
            if (rawBlock.parentBeaconBlockRoot)
                block.parentBeaconBlockRoot = (0, value_normalization_1.normalizeHex)(rawBlock.parentBeaconBlockRoot);
        }
        if (fullTransactions.length) {
            block.transactions = fullTransactions.map((tx) => this.normalizeTransaction(tx));
        }
        if (rawBlock.receipts) {
            block.receipts = rawBlock.receipts.map((receipt) => this.normalizeTransactionReceipt(receipt));
        }
        this.calculateBlockSizes(block, rawBlock);
        return block;
    }
    calculateBlockSizes(block, rawBlock) {
        if (rawBlock.size && rawBlock.size > 0) {
            block.sizeWithoutReceipts = rawBlock.size;
        }
        else {
            block.sizeWithoutReceipts = block_size_calculator_1.BlockSizeCalculator.calculateBlockSizeFromDecodedTransactions(block);
        }
        if (block.receipts && block.receipts.length > 0) {
            const receiptsSize = block_size_calculator_1.BlockSizeCalculator.calculateReceiptsSize(block.receipts);
            block.size = block.sizeWithoutReceipts + receiptsSize;
        }
        else {
            block.size = block.sizeWithoutReceipts;
        }
    }
    normalizeTransaction(rawTx) {
        this.assertRequired(rawTx, ['hash', 'from', 'value', 'gas', 'input'], 'Transaction');
        const transaction = {
            hash: (0, value_normalization_1.normalizeHex)(rawTx.hash),
            blockHash: rawTx.blockHash ? (0, value_normalization_1.normalizeHex)(rawTx.blockHash) : null,
            blockNumber: rawTx.blockNumber ?? null,
            transactionIndex: rawTx.transactionIndex ?? null,
            nonce: rawTx.nonce,
            from: (0, value_normalization_1.normalizeAddress)(rawTx.from),
            to: rawTx.to ? (0, value_normalization_1.normalizeAddress)(rawTx.to) : null,
            value: (0, value_normalization_1.quantityToDecimalString)(rawTx.value),
            gas: rawTx.gas,
            input: (0, value_normalization_1.normalizeHex)(rawTx.input),
            type: rawTx.type ? (0, value_normalization_1.normalizeHex)(rawTx.type) : '0x0',
            chainId: rawTx.chainId ?? this.networkConfig.chainId,
        };
        const gasPrice = (0, value_normalization_1.optionalQuantityToDecimalString)(rawTx.gasPrice);
        const maxFeePerGas = (0, value_normalization_1.optionalQuantityToDecimalString)(rawTx.maxFeePerGas);
        const maxPriorityFeePerGas = (0, value_normalization_1.optionalQuantityToDecimalString)(rawTx.maxPriorityFeePerGas);
        if (gasPrice)
            transaction.gasPrice = gasPrice;
        if (maxFeePerGas)
            transaction.maxFeePerGas = maxFeePerGas;
        if (maxPriorityFeePerGas)
            transaction.maxPriorityFeePerGas = maxPriorityFeePerGas;
        if (rawTx.v)
            transaction.v = (0, value_normalization_1.normalizeHex)(rawTx.v);
        if (rawTx.r)
            transaction.r = (0, value_normalization_1.normalizeHex)(rawTx.r);
        if (rawTx.s)
            transaction.s = (0, value_normalization_1.normalizeHex)(rawTx.s);
        if (rawTx.accessList) {
            transaction.accessList = rawTx.accessList.map((entry) => ({
                address: (0, value_normalization_1.normalizeAddress)(entry.address),
                storageKeys: entry.storageKeys.map((key) => (0, value_normalization_1.normalizeHex)(key)),
            }));
        }
        const maxFeePerBlobGas = (0, value_normalization_1.optionalQuantityToDecimalString)(rawTx.maxFeePerBlobGas);
        if (maxFeePerBlobGas)
            transaction.maxFeePerBlobGas = maxFeePerBlobGas;
        if (rawTx.blobVersionedHashes)
            transaction.blobVersionedHashes = rawTx.blobVersionedHashes.map((h) => (0, value_normalization_1.normalizeHex)(h));
        return transaction;
    }
    normalizeTransactionReceipt(rawReceipt) {
        this.assertRequired(rawReceipt, ['transactionHash', 'blockHash', 'from', 'cumulativeGasUsed', 'gasUsed'], 'TransactionReceipt');
        if (rawReceipt.blockNumber === undefined || rawReceipt.blockNumber === null) {
            throw new Error('TransactionReceipt is missing required blockNumber field');
        }
        if (rawReceipt.status === undefined && !rawReceipt.root && !this.fieldPolicy.allowLegacyReceiptRoot) {
            throw new Error('TransactionReceipt is missing status field');
        }
        const receipt = {
            transactionHash: (0, value_normalization_1.normalizeHex)(rawReceipt.transactionHash),
            transactionIndex: rawReceipt.transactionIndex,
            blockHash: (0, value_normalization_1.normalizeHex)(rawReceipt.blockHash),
            blockNumber: rawReceipt.blockNumber,
            from: (0, value_normalization_1.normalizeAddress)(rawReceipt.from),
            to: rawReceipt.to ? (0, value_normalization_1.normalizeAddress)(rawReceipt.to) : null,
            cumulativeGasUsed: rawReceipt.cumulativeGasUsed,
            gasUsed: rawReceipt.gasUsed,
            contractAddress: rawReceipt.contractAddress ? (0, value_normalization_1.normalizeAddress)(rawReceipt.contractAddress) : null,
            logs: rawReceipt.logs.map((log) => this.normalizeLog(log)),
            type: rawReceipt.type ? (0, value_normalization_1.normalizeHex)(rawReceipt.type) : '0x0',
        };
        if (rawReceipt.logsBloom !== undefined || !this.fieldPolicy.allowMissingLogsBloom) {
            receipt.logsBloom = (0, value_normalization_1.normalizeHex)(rawReceipt.logsBloom);
        }
        if (rawReceipt.status !== undefined)
            receipt.status = rawReceipt.status;
        if (rawReceipt.root)
            receipt.root = (0, value_normalization_1.normalizeHex)(rawReceipt.root);
        const effectiveGasPrice = (0, value_normalization_1.optionalQuantityToDecimalString)(rawReceipt.effectiveGasPrice);
        if (effectiveGasPrice)
            receipt.effectiveGasPrice = effectiveGasPrice;
        if (this.networkConfig.hasBlobTransactions) {
            if (rawReceipt.blobGasUsed !== undefined)
                receipt.blobGasUsed = (0, value_normalization_1.quantityToDecimalString)(rawReceipt.blobGasUsed);
            if (rawReceipt.blobGasPrice !== undefined)
                receipt.blobGasPrice = (0, value_normalization_1.quantityToDecimalString)(rawReceipt.blobGasPrice);
        }
        return receipt;
    }
    normalizeLog(rawLog) {
        this.assertRequired(rawLog, ['address', 'topics', 'data'], 'Log');
        return {
            address: (0, value_normalization_1.normalizeAddress)(rawLog.address),
            topics: rawLog.topics.map((topic) => (0, value_normalization_1.normalizeHex)(topic)),
            data: (0, value_normalization_1.normalizeHex)(rawLog.data),
            blockNumber: rawLog.blockNumber ?? null,
            transactionHash: rawLog.transactionHash ? (0, value_normalization_1.normalizeHex)(rawLog.transactionHash) : null,
            transactionIndex: rawLog.transactionIndex ?? null,
            blockHash: rawLog.blockHash ? (0, value_normalization_1.normalizeHex)(rawLog.blockHash) : null,
            logIndex: rawLog.logIndex ?? null,
            removed: rawLog.removed ?? false,
        };
    }
    getNetworkConfig() {
        return this.networkConfig;
    }
    assertRequired(obj, fields, modelName) {
        for (const field of fields) {
            if (obj[field] === undefined || obj[field] === null || obj[field] === '') {
                throw new Error(`${modelName} is missing required ${field} field`);
            }
        }
    }
}
exports.BlockchainNormalizer = BlockchainNormalizer;
