"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ethers_1 = require("ethers"); const config_1 = require("./config"); const eip712_1 = require("./eip712"); const ipfs_1 = require("./ipfs"); const blockscout_1 = require("./blockscout"); const usdEnrich_1 = require("./usdEnrich"); const scanState_1 = require("./scanState"); const checkpoint_core_1 = require("@dbis/checkpoint-core"); const leafCodec_1 = require("./leafCodec"); const adminIngress_1 = require("./ingress/adminIngress"); const dualMirror_1 = require("./dualMirror"); const activityRegistry_1 = require("./activityRegistry"); const activityRegistryV2_1 = require("./activityRegistryV2"); const iso20022Enrich_1 = require("./iso20022Enrich"); const iso20022LocalStore_1 = require("./iso20022LocalStore"); const participantSurface_1 = require("./participantSurface"); const receiptsRoot_1 = require("./receiptsRoot"); (0, config_1.assertAggregatorConfig)(); const BLOCK_ORACLE_ABI = ['function setBlockHeader(uint256,bytes32,bytes32) external']; class CheckpointAggregator { chain138; mainnetWallet; checkpoint; ingress; blockOracle; buffer = []; nextBatchId = 1n; hubImplVersion = 3n; flushTimer; scanning = false; includedTxCache = new Map(); constructor() { const mainnet = new ethers_1.ethers.JsonRpcProvider(config_1.checkpointAggregatorConfig.mainnetRpc); this.mainnetWallet = new ethers_1.ethers.Wallet(process.env.PRIVATE_KEY, mainnet); this.checkpoint = (0, adminIngress_1.buildCheckpointContract)(this.mainnetWallet, config_1.checkpointAggregatorConfig.checkpointProxy, config_1.checkpointAggregatorConfig.submitMode); this.ingress = new adminIngress_1.AdminCheckpointIngress(this.checkpoint, config_1.checkpointAggregatorConfig.submitMode); this.chain138 = new ethers_1.ethers.JsonRpcProvider(config_1.checkpointAggregatorConfig.chain138Rpc); if (config_1.checkpointAggregatorConfig.blockOracleExtension) { this.blockOracle = new ethers_1.ethers.Contract(config_1.checkpointAggregatorConfig.blockOracleExtension, BLOCK_ORACLE_ABI, this.mainnetWallet); } } async start() { await this.syncBatchId(); const state = (0, scanState_1.loadScanState)(config_1.checkpointAggregatorConfig.scanStatePath, config_1.checkpointAggregatorConfig.scanFromBlock); console.log(`Checkpoint aggregator mode=${config_1.checkpointAggregatorConfig.submitMode} batch=${config_1.checkpointAggregatorConfig.batchSize} nextBatchId=${this.nextBatchId} impl=${this.hubImplVersion} blockscout=${config_1.checkpointAggregatorConfig.useBlockscout} emitter=${config_1.checkpointAggregatorConfig.batchEmitter138 || 'n/a'}`); this.schedulePartialFlush(); await this.runScanLoop(state); } async syncBatchId() { const latest = await this.checkpoint.getLatestBatchId(); this.nextBatchId = latest > 0n ? BigInt(latest) + 1n : 1n; try { this.hubImplVersion = await this.checkpoint.IMPLEMENTATION_VERSION(); } catch { this.hubImplVersion = 3n; } } schedulePartialFlush() { if (this.flushTimer) clearTimeout(this.flushTimer); this.flushTimer = setTimeout(() => { void this.flush(true).catch((e) => console.error('partial flush failed', e)); }, config_1.checkpointAggregatorConfig.maxWaitMs); } async runScanLoop(initialState) { const state = { ...initialState }; for (;;) { try { await this.scanCatchUp(state); } catch (e) { console.error('scanCatchUp', e); } await new Promise((r) => setTimeout(r, config_1.checkpointAggregatorConfig.scanPollMs)); } } async scanCatchUp(state) { if (this.scanning) return; this.scanning = true; try { const head = await this.chain138.getBlockNumber(); const safeHead = head - config_1.checkpointAggregatorConfig.confirmationBlocks; let from = state.lastScannedBlock + 1; while (from <= safeHead) { const to = Math.min(from + config_1.checkpointAggregatorConfig.scanChunkBlocks - 1, safeHead); let added = 0; if (config_1.checkpointAggregatorConfig.useBlockscout) { added = await this.scanBlockscoutRange(from, to); } else { for (let b = from; b <= to; b++) added += await this.scanBlockRpc(b); } state.lastScannedBlock = to; (0, scanState_1.saveScanState)(config_1.checkpointAggregatorConfig.scanStatePath, state); console.log(`Scanned blocks ${from}-${to} (+${added} txs) buffer=${this.buffer.length}`); from = to + 1; } } finally { this.scanning = false; } } async scanBlockscoutRange(from, to) { const txs = await (0, blockscout_1.fetchTransactionsInBlockRange)(config_1.checkpointAggregatorConfig.blockscoutApi, from, to); let added = 0; for (const tx of txs) { if (await this.txAlreadyIncluded(tx.hash)) continue; const receipt = await this.chain138.getTransactionReceipt(tx.hash); const leaf = { txHash: tx.hash, from: tx.from, to: tx.to, value: tx.value, blockNumber: tx.blockNumber, blockTimestamp: tx.blockTimestamp, gasUsed: receipt?.gasUsed ?? 0n, success: receipt?.status === 1, }; await this.enrichLeafFromBlockscout(leaf); if ((0, leafCodec_1.effectiveValue)(leaf) < config_1.checkpointAggregatorConfig.minValueWei) continue; this.buffer.push(leaf); added++; if (this.buffer.length >= config_1.checkpointAggregatorConfig.batchSize) await this.flush(false); } return added; } async scanBlockRpc(blockNumber) { const block = await this.chain138.getBlock(blockNumber, true); if (!block) return 0; let added = 0; const entries = block.prefetchedTransactions?.length ? block.prefetchedTransactions : block.transactions ?? []; for (const entry of entries) { const tx = typeof entry === 'string' ? await this.chain138.getTransaction(entry) : entry; if (!tx || typeof tx === 'string') continue; const value = tx.value ?? 0n; if (await this.txAlreadyIncluded(tx.hash)) continue; const receipt = await this.chain138.getTransactionReceipt(tx.hash); const leaf = { txHash: tx.hash, from: tx.from, to: tx.to ?? ethers_1.ethers.ZeroAddress, value, blockNumber, blockTimestamp: block.timestamp, gasUsed: receipt?.gasUsed ?? 0n, success: receipt?.status === 1, }; await this.enrichLeafFromBlockscout(leaf); if ((0, leafCodec_1.effectiveValue)(leaf) < config_1.checkpointAggregatorConfig.minValueWei) continue; this.buffer.push(leaf); added++; if (this.buffer.length >= config_1.checkpointAggregatorConfig.batchSize) await this.flush(false); } return added; } async enrichLeafFromBlockscout(leaf) { const { allErc20 } = await (0, usdEnrich_1.enrichLeafFromBlockscoutApi)(leaf, config_1.checkpointAggregatorConfig.blockscoutApi, config_1.checkpointAggregatorConfig.useBlockscout); await (0, usdEnrich_1.enrichLeafUsd)(leaf, { apiBaseUrl: config_1.checkpointAggregatorConfig.tokenAggregationApiUrl, chainId: 138, enabled: config_1.checkpointAggregatorConfig.usdEnrichEnabled, requestDelayMs: config_1.checkpointAggregatorConfig.usdRequestDelayMs, blockscoutApi: config_1.checkpointAggregatorConfig.blockscoutApi, useBlockscout: config_1.checkpointAggregatorConfig.useBlockscout, }, allErc20); } async txAlreadyIncluded(txHash) { const cached = this.includedTxCache.get(txHash); if (cached !== undefined) return cached; try { const [included] = await this.checkpoint.isTxIncluded(txHash); this.includedTxCache.set(txHash, included); return included; } catch { return false; } } async flush(partial) { if (this.buffer.length === 0) return; const leaves = this.buffer.splice(0, config_1.checkpointAggregatorConfig.batchSize); if (config_1.checkpointAggregatorConfig.attachReceiptMeta) { await (0, receiptsRoot_1.attachReceiptMeta)(this.chain138, leaves); } else { for (const leaf of leaves) { const meta = await (0, receiptsRoot_1.fetchReceiptMeta)(this.chain138, leaf.txHash); leaf.logCount = meta.logCount; leaf.receiptHash = meta.receiptHash; } } (0, iso20022Enrich_1.attachIso20022ToLeaves)(leaves); const root = (0, leafCodec_1.paymentsRoot)(138, leaves); const receiptMetas = leaves.map((l) => ({ logCount: l.logCount ?? 0, receiptHash: l.receiptHash ?? ethers_1.ethers.ZeroHash, })); const receiptsRootHash = (0, receiptsRoot_1.receiptsRoot)(leaves.map((l) => l.txHash), receiptMetas); const checkpointBlock = Math.max(...leaves.map((l) => l.blockNumber)); const block = await this.chain138.getBlock(checkpointBlock); const blockHash = block?.hash ?? ethers_1.ethers.ZeroHash; const stateRoot = block?.stateRoot ?? ethers_1.ethers.ZeroHash; if (config_1.checkpointAggregatorConfig.updateBlockOracle && this.blockOracle) { const tx = await this.blockOracle.setBlockHeader(checkpointBlock, blockHash, stateRoot); await tx.wait(); } const header = { batchId: this.nextBatchId, previousBatchId: this.nextBatchId > 1n ? this.nextBatchId - 1n : 0n, chainId: 138n, checkpointBlock: BigInt(checkpointBlock), startBlock: BigInt(Math.min(...leaves.map((l) => l.blockNumber))), endBlock: BigInt(checkpointBlock), blockHash, stateRoot, paymentsRoot: root, receiptsRoot: receiptsRootHash, txCount: partial && leaves.length < config_1.checkpointAggregatorConfig.batchSize ? leaves.length : config_1.checkpointAggregatorConfig.batchSize, flags: partial && leaves.length < config_1.checkpointAggregatorConfig.batchSize ? 1 : 0, submittedAt: 0n, submitter: ethers_1.ethers.ZeroAddress, contentURI: ethers_1.ethers.ZeroHash, }; const payload = { batchId: this.nextBatchId.toString(), chainId: 138, checkpointBlock, paymentsRoot: root, leaves: leaves.map((l) => ({ txHash: l.txHash, from: l.from, to: l.to, value: l.value.toString(), nativeValueWei: l.value.toString(), onChainValueWei: (0, leafCodec_1.effectiveValue)(l).toString(), blockNumber: l.blockNumber, blockTimestamp: l.blockTimestamp, gasUsed: l.gasUsed.toString(), success: l.success, ...(l.token ? { token: l.token, tokenSymbol: l.tokenSymbol, tokenDecimals: l.tokenDecimals, tokenValue: l.tokenValue?.toString(), tokenLogIndex: l.tokenLogIndex, } : {}), ...(l.valueUsd ? { valueUsd: l.valueUsd } : {}), ...(l.nativeValueUsd ? { nativeValueUsd: l.nativeValueUsd } : {}), ...(l.tokenValueUsd ? { tokenValueUsd: l.tokenValueUsd } : {}), ...(l.nativePriceUsd != null ? { nativePriceUsd: l.nativePriceUsd } : {}), ...(l.tokenPriceUsd != null ? { tokenPriceUsd: l.tokenPriceUsd } : {}), ...(l.priceSource ? { priceSource: l.priceSource } : {}), ...(l.priceEffectiveTimestamp ? { priceEffectiveTimestamp: l.priceEffectiveTimestamp } : {}), ...(l.totalTransfersUsd ? { totalTransfersUsd: l.totalTransfersUsd } : {}), ...(l.transfers?.length ? { transfers: l.transfers } : {}), ...(l.logCount != null ? { logCount: l.logCount } : {}), ...(l.receiptHash ? { receiptHash: l.receiptHash } : {}), ...(l.iso20022 ? { iso20022: l.iso20022 } : {}), })), submittedAt: new Date().toISOString(), ...(config_1.checkpointAggregatorConfig.isoEnrichEnabled ? { iso20022Schema: 'canonical-v1', isoMsgTypeDefault: 'chain138.synthetic' } : {}), ...(config_1.checkpointAggregatorConfig.usdEnrichEnabled ? { batchTotalUsd: (0, checkpoint_core_1.sumUsdStrings)(leaves.map((l) => l.totalTransfersUsd ?? l.valueUsd)) ?? '0.000000', usdEnrichedAt: new Date().toISOString(), } : {}), }; const contentURI = await (0, ipfs_1.publishBatchPayload)(this.nextBatchId, payload); header.contentURI = contentURI; const validatorSignatures = config_1.checkpointAggregatorConfig.requireValidatorSigs ? await (0, eip712_1.signBatchAttestation)(this.mainnetWallet, config_1.checkpointAggregatorConfig.checkpointProxy, header) : '0x01'; const txHashes = config_1.checkpointAggregatorConfig.submitMode === 'commitment' ? [] : leaves.map((l) => l.txHash); const extensionData = adminIngress_1.AdminCheckpointIngress.extensionDataForMode(config_1.checkpointAggregatorConfig.submitMode, leaves); const { hash } = await this.ingress.submit({ header, leaves, txHashes, extensionData, contentURI, validatorSignatures, }); const batchIdSubmitted = this.nextBatchId; console.log(`Submitted batch ${batchIdSubmitted} (${leaves.length} txs) mode=${config_1.checkpointAggregatorConfig.submitMode} tx=${hash}`); if (config_1.checkpointAggregatorConfig.dualWriteV1Mirror) { try { const mirrorTx = await (0, dualMirror_1.dualWriteV1Mirror)(this.mainnetWallet, config_1.checkpointAggregatorConfig.transactionMirrorMainnet, leaves); if (mirrorTx) console.log(` v1 TransactionMirror dual-write tx=${mirrorTx}`); } catch (e) { console.error(' v1 TransactionMirror dual-write failed', e); } } if (config_1.checkpointAggregatorConfig.recordAddressActivity && config_1.checkpointAggregatorConfig.addressActivityRegistry) { try { const activityTx = await (0, activityRegistry_1.recordActivityBatch)(this.mainnetWallet, config_1.checkpointAggregatorConfig.addressActivityRegistry, batchIdSubmitted, leaves); if (activityTx) console.log(` AddressActivityRegistry tx=${activityTx}`); } catch (e) { console.error(' AddressActivityRegistry record failed', e); } } if (config_1.checkpointAggregatorConfig.recordIsoAttestation && config_1.checkpointAggregatorConfig.addressActivityRegistryV2) { try { const isoTx = await (0, activityRegistryV2_1.recordIsoAttestationBatch)(this.mainnetWallet, config_1.checkpointAggregatorConfig.addressActivityRegistryV2, batchIdSubmitted, leaves); if (isoTx) console.log(` AddressActivityRegistryV2 tx=${isoTx}`); } catch (e) { console.error(' AddressActivityRegistryV2 record failed', e); } } if (config_1.checkpointAggregatorConfig.isoEnrichEnabled) { try { const stored = (0, iso20022LocalStore_1.persistIso20022ForLeaves)(leaves, batchIdSubmitted); if (stored.length) console.log(` ISO20022 OMNL store: ${stored.length} message(s)`); } catch (e) { console.error(' ISO20022 local store failed', e); } } if (config_1.checkpointAggregatorConfig.surfaceParticipants && config_1.checkpointAggregatorConfig.participantSurface) { try { const surfaceTx = await (0, participantSurface_1.surfaceParticipantsOnMainnet)(this.mainnetWallet, config_1.checkpointAggregatorConfig.participantSurface, batchIdSubmitted, leaves); if (surfaceTx) console.log(` Chain138ParticipantSurface tx=${surfaceTx}`); } catch (e) { console.error(' Chain138ParticipantSurface failed', e); } } if (config_1.checkpointAggregatorConfig.surfaceTopLevelZeroEth) { try { const n = await (0, participantSurface_1.surfaceTopLevelZeroEth)(this.mainnetWallet, leaves, batchIdSubmitted); if (n > 0) console.log(` Top-level 0 ETH surface txs=${n} (Etherscan Transactions tab)`); } catch (e) { console.error(' Top-level 0 ETH surface failed', e); } } this.nextBatchId += 1n; this.schedulePartialFlush(); } } new CheckpointAggregator().start().catch((e) => { console.error(e); process.exit(1); });