Context
The EvmTransactionProcessor is the core domain service that takes a batch of confirmed EVM transactions and an associated block, classifies each transaction, and writes the results to the appropriate repositories in parallel using virtual threads. It mirrors the existing Solana TransactionProcessor pattern exactly: split transactions into categories (successful, failed, large transfers, token transfers), write each category to its repository concurrently, save the block, and record batch metrics.
Specification
File
prism/src/main/java/com/stablebridge/prism/domain/service/EvmTransactionProcessor.java
Constructor dependencies (injected via Avaje)
EvmTransactionRepository — for successful transactions (COPY protocol)
EvmFailedTransactionRepository — for failed/reverted transactions
EvmTransferRepository — for large native transfers
EvmTokenTransferRepository — for ERC-20 token transfers
EvmBlockRepository — for block metadata
MetricsRecorder — for recording batch processing metrics
IndexerConfig — for large transfer threshold
Processing logic
public EvmBatchResult processBatch(List<EvmTransaction> transactions, EvmBlock block) {
// 1. Split transactions
var successful = transactions.stream().filter(EvmTransaction::status).toList();
var failed = transactions.stream().filter(tx -> !tx.status()).toList();
// 2. Extract large native transfers (successful only, exclude OP deposit type 126)
var threshold = config.largeTransferThreshold();
var largeTransfers = successful.stream()
.filter(tx -> tx.type() != 126) // exclude OP Stack deposits
.filter(tx -> EvmLargeTransferFilter.isLargeTransfer(tx.value(), threshold))
.map(EvmTransaction::toLargeTransfer)
.toList();
// 3. Extract ERC-20 token transfers from all transaction logs
var tokenTransfers = transactions.stream()
.flatMap(tx -> tx.extractTokenTransfers().stream())
.toList();
// 4. Parallel writes via virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var txFuture = executor.submit(() -> transactionRepo.saveAll(successful));
var failedFuture = executor.submit(() -> failedTransactionRepo.saveAll(
failed.stream().map(EvmTransaction::toFailedTransaction).toList()));
var transferFuture = executor.submit(() -> transferRepo.saveAll(largeTransfers));
var tokenFuture = executor.submit(() -> tokenTransferRepo.saveAll(tokenTransfers));
var blockFuture = executor.submit(() -> blockRepo.save(block));
// Wait for all writes to complete
txFuture.get();
failedFuture.get();
transferFuture.get();
tokenFuture.get();
blockFuture.get();
}
// 5. Record metrics
var result = new EvmBatchResult(
successful.size(), failed.size(),
largeTransfers.size(), tokenTransfers.size(), 0);
metricsRecorder.recordBatch(result);
return result;
}
Design decisions
- Follow existing
TransactionProcessor.java pattern exactly
- 5 parallel virtual thread writes via
Executors.newVirtualThreadPerTaskExecutor() in try-with-resources
- OP Stack deposit transactions (type 126) are excluded from large transfer detection — these are L1-to-L2 bridge deposits that appear as native value transfers but are system operations
- Token transfers are extracted from ALL transactions (not just successful) because logs in failed transactions can still be relevant for indexing
MetricsRecorder integration for observability
@Singleton annotation (Avaje DI), @RequiredArgsConstructor (Lombok)
Test class
prism/src/test/java/com/stablebridge/prism/domain/service/EvmTransactionProcessorTest.java
Test cases:
- Batch with all successful transactions writes to transaction repo only
- Batch with failed transactions writes to failed transaction repo
- Large native transfer above threshold is written to transfer repo
- OP Stack deposit (type 126) above threshold is NOT written to transfer repo
- ERC-20 token transfers are extracted and written to token transfer repo
- Block is saved to block repo
- All 5 writes happen in parallel (verify via mock interactions)
- Metrics are recorded after processing
- Empty batch produces zero-count result
- Exception in one write propagates correctly
Acceptance Criteria
Dependencies
References
Context
The
EvmTransactionProcessoris the core domain service that takes a batch of confirmed EVM transactions and an associated block, classifies each transaction, and writes the results to the appropriate repositories in parallel using virtual threads. It mirrors the existing SolanaTransactionProcessorpattern exactly: split transactions into categories (successful, failed, large transfers, token transfers), write each category to its repository concurrently, save the block, and record batch metrics.Specification
File
prism/src/main/java/com/stablebridge/prism/domain/service/EvmTransactionProcessor.javaConstructor dependencies (injected via Avaje)
EvmTransactionRepository— for successful transactions (COPY protocol)EvmFailedTransactionRepository— for failed/reverted transactionsEvmTransferRepository— for large native transfersEvmTokenTransferRepository— for ERC-20 token transfersEvmBlockRepository— for block metadataMetricsRecorder— for recording batch processing metricsIndexerConfig— for large transfer thresholdProcessing logic
Design decisions
TransactionProcessor.javapattern exactlyExecutors.newVirtualThreadPerTaskExecutor()in try-with-resourcesMetricsRecorderintegration for observability@Singletonannotation (Avaje DI),@RequiredArgsConstructor(Lombok)Test class
prism/src/test/java/com/stablebridge/prism/domain/service/EvmTransactionProcessorTest.javaTest cases:
Acceptance Criteria
EvmTransactionProcessorexists with correct constructor dependenciesprocessBatch()splits transactions into successful, failed, large transfers, token transfersExecutors.newVirtualThreadPerTaskExecutor()EvmBlockRepositoryMetricsRecorderEvmBatchResult./gradlew buildpassesDependencies
References