The Batching Pipeline
Overviewβ
Understanding how an input flows through the Batcher system is essential for effective integration and customization. This document provides a comprehensive, step-by-step overview of an input's lifecycleβfrom the initial API call to final chain confirmation.
This knowledge helps you:
- Understand where adapter customization hooks in
- Configure appropriate confirmation levels for your use case
- Debug issues in the batching flow
- Optimize batch submission timing
Target Audienceβ
Developers building on or integrating with the Paima platform who need to batch transactions, especially those who want to:
- Customize adapter behavior for different blockchains
- Understand validation and security guarantees
- Configure confirmation levels appropriately
- Implement custom batching strategies
The Input Lifecycleβ
Every input submitted to the Batcher goes through a well-defined pipeline. Here's the complete journey:
Step 1: Ingestionβ
An HTTP POST request is made to the /send-input endpoint with a payload conforming to the DefaultBatcherInput type:
{
"data": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"addressType": 0, // EVM address type
"input": "myGameCommand|arg1|arg2",
"signature": "0x...",
"timestamp": "1234567890",
"target": "evm" // Optional, uses defaultTarget if not specified
},
"confirmationLevel": "wait-receipt" // Optional
}
The API accepts the request and begins processing it through the pipeline.
Step 2: Targetingβ
The batcher examines the input.target field to determine which blockchain adapter should handle this input. If target is not provided, it falls back to the defaultTarget specified in the PaimaBatcherConfig.
For example, if your configuration has:
{
adapters: {
evm: evmAdapter,
midnight: midnightAdapter
},
defaultTarget: "evm"
}
Then an input without a target field will be routed to the evmAdapter.
Step 3: Validation (Adapter-Driven)β
Once the target adapter is identified, the batcher performs two crucial validation steps before the input is queued:
3a. Signature Verificationβ
The batcher calls the adapter's verifySignature() method. This is adapter-specific because different blockchains have different signature schemes:
- EVM adapters: Use the default EVM signature verification (via
CryptoManager.Evm().verifySignature()) - Custom adapters: Can override this method to implement their own logic or bypass it entirely
verifySignature(input: DefaultBatcherInput): boolean {
// Bypass the default EVM signature check
return true;
}
If signature verification fails, the input is rejected immediately with a 401 Unauthorized error.
3b. Input Validationβ
The adapter's validateInput() method is called to perform semantic validation specific to that blockchain:
- Check circuit argument formats (e.g., for zero-knowledge chains)
- Validate payload structure
- Verify account formats
- Check any other chain-specific requirements
// Example validation
validateInput(input: DefaultBatcherInput): ValidationResult {
if (!isValidCircuitArgs(input.input)) {
return {
valid: false,
error: "Invalid circuit arguments format"
};
}
return { valid: true };
}
If validation fails, the input is rejected immediately with a 400 Bad Request error containing the validation error message.
Pre-queue validation ensures that only valid inputs reach persistent storage. This prevents storage pollution and makes the system more robust during restarts and crashes.
Step 4: Queuingβ
After passing validation, the input is saved to the BatcherStorage (e.g., FileStorage, PostgreSQL, Redis).
Important: Storage is the single source of truth. There are no in-memory queues that could be lost on restart.
Confirmation Level: "no-wait"β
If confirmationLevel is set to "no-wait", the API call returns immediately at this point with a success response:
{
"success": true,
"message": "Input queued for batching",
"inputsProcessed": 1
}
The caller doesn't wait for the batch to be submitted or confirmed.
Step 5: Polling & Criteria Checkβ
The main PaimaBatcher loop runs at intervals defined by pollingIntervalMs (e.g., every 1000ms). On each iteration:
- For each adapter target (e.g., "evm", "midnight"), the batcher checks the batching criteria
- The criteria determine if enough inputs have accumulated or enough time has passed
- Different targets can have different criteria
Batching Criteria Typesβ
| Criteria Type | Description | Example |
|---|---|---|
| time | Submit every N milliseconds | { criteriaType: "time", timeWindowMs: 5000 } |
| size | Submit when N inputs accumulate | { criteriaType: "size", maxBatchSize: 10 } |
| value | Submit when accumulated value reaches threshold | { criteriaType: "value", targetValue: 100 } |
| hybrid | Submit when time OR size criteria met | { criteriaType: "hybrid", timeWindowMs: 5000, maxBatchSize: 10 } |
| custom | Custom function determines readiness | { criteriaType: "custom", isBatchReadyFn: (inputs) => {...} } |
Example configuration:
batchingCriteria: {
evm: {
criteriaType: "time",
timeWindowMs: 5000
},
midnight: {
criteriaType: "hybrid",
timeWindowMs: 10000,
maxBatchSize: 20
}
}
When the criteria for a target is satisfied, the batcher proceeds to build and submit the batch.
Step 6: Building (Adapter-Driven)β
Once a target is ready for batching, the batcher:
- Retrieves pending inputs for that target from storage
- Calls the adapter's
buildBatchData()method with these inputs
The adapter's buildBatchData() method is responsible for:
- Serializing inputs into a blockchain-specific format
- Handling size constraints (respecting
maxBatchSize) - Deciding which inputs to include or exclude
- Returning the final batch payload
interface BatchBuildingResult<TOutput> {
selectedInputs: DefaultBatcherInput[]; // Inputs included in this batch
data: TOutput; // Serialized batch data (format depends on chain)
}
For example:
- EVM adapters might serialize to a hex string
- Midnight adapters might construct circuit arguments
- Custom adapters can use any format their chain accepts
Step 7: Submission (Adapter-Driven)β
The batcher calls the adapter's submitBatch() method, passing:
- The serialized data from Step 6
- An estimated fee (obtained via
estimateBatchFee())
const fee = await adapter.estimateBatchFee(data);
const txHash = await adapter.submitBatch(data, fee);
The adapter handles all blockchain-specific submission logic:
- Constructing the transaction
- Signing it with the configured private key
- Submitting it to the RPC endpoint
- Returning the transaction hash
State transition event batch:submit is emitted at this point.
Step 8: Confirmation (Adapter-Driven)β
After submission, the batcher calls adapter.waitForTransactionReceipt() to wait for the transaction to be included in a block.
The adapter polls the blockchain until:
- The transaction is confirmed (successful receipt)
- An error occurs
- A timeout is reached
Once the receipt is obtained:
- The processed inputs are removed from storage
- State transition event
batch:receiptis emitted
Confirmation Level: "wait-receipt"β
If confirmationLevel is "wait-receipt", the API call returns at this point:
{
"success": true,
"message": "Input processed successfully",
"transactionHash": "0xabc...",
"inputsProcessed": 1
}
Step 9: Effectstream Processing (Optional)β
After blockchain confirmation, the batcher optionally waits for the Effectstream to process the batch. This step:
- Monitors Effectstream Sync events for the specific transaction
- Waits until the Effectstream has parsed and validated the inputs
- Returns the rollup block number where processing occurred
Confirmation Level: "wait-effectstream-processed"β
If confirmationLevel is "wait-effectstream-processed", the API call waits until this step completes:
{
"success": true,
"message": "Input processed and validated by Effectstream",
"transactionHash": "0xabc...",
"rollup": 12345,
"inputsProcessed": 1
}
This provides the strongest guarantee: the input has been confirmed on-chain and processed by your game/application logic.
Step 10: Callback Resolutionβ
Finally, the original API caller receives the response based on their configured confirmationLevel.
All waiting callers whose inputs were included in this batch are resolved simultaneously.
Confirmation Level Fallback Logicβ
If confirmationLevel is not explicitly provided in the API call, the batcher uses this fallback hierarchy:
-
Target-specific configuration: Check if
PaimaBatcherConfig.confirmationLevelis an object with a key matching the target:confirmationLevel: {
evm: "no-wait",
midnight: "wait-receipt"
} -
Global configuration: If
confirmationLevelis a string, use it for all targets:confirmationLevel: "wait-receipt" -
Default: If nothing is configured, default to
"wait-receipt"
Pipeline Visualizationβ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. HTTP POST /send-input β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 2. Target Selection (use input.target or defaultTarget) β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 3. Validation (Adapter-Driven) β
β β’ verifySignature() - Check cryptographic signature β
β β’ validateInput() - Semantic validation β
β β
β β Fails β 400/401 error returned immediately β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 4. Save to BatcherStorage β
β β
β πΉ confirmationLevel: "no-wait" β API returns here β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 5. Polling Loop (every pollingIntervalMs) β
β Check batchingCriteria for each target β
β Ready? β Proceed to build β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 6. adapter.buildBatchData() β
β Serialize inputs β blockchain-specific format β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 7. adapter.submitBatch(data, fee) β
β Submit transaction to blockchain β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 8. adapter.waitForTransactionReceipt() β
β Wait for blockchain confirmation β
β Remove processed inputs from storage β
β β
β πΉ confirmationLevel: "wait-receipt" β API returns here β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 9. Wait for Paima Processing (Optional) β
β Monitor Paima Sync events β
β Wait for rollup block processing β
β β
β πΉ confirmationLevel: "wait-effectstream-processed" β returns hereβ
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 10. Callback Resolution β
β Return final response to API caller β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Key Takeawaysβ
- Validation happens before queuing: Invalid inputs never reach storage
- Storage is crash-safe: Inputs persist through restarts
- Adapters control key operations: Signature verification, validation, building, and submission are all adapter-specific
- Confirmation levels offer flexibility: Choose between immediate return, blockchain confirmation, or full Paima processing
- Per-target configuration: Different blockchain targets can have different criteria and confirmation levels
- Event-driven observability: State transitions emit events throughout the pipeline for monitoring and logging
Next Stepsβ
- Learn about Custom Adapters to support new blockchains
- Explore Batching Criteria for advanced batch timing strategies
- Review Configuration Reference for all available options