Batch transactions for dApp: gas savings and atomicity

Batch sending of calls often fails: some operations go through, some are lost, and fees grow with each retry. We design and implement batch transactions turnkey — from the batch scheme to failure handling and rollbacks. Atomicity and predictable network cost instead of a manual series of calls.

Blockchain Development Services

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1376
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1350
  • B2B Advance company logo design
    B2B Advance company logo design
    798
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1088
  • AIDER company logo development
    AIDER company logo development
    1034
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1130

Why batch transactions do not reach the network

Batch transactions look like one call, but the network sees dozens of independent operations in them.

Each gets its own sequence number, and if one gets stuck, the queue stalls: subsequent calls in the batch wait for the number to be released and hang in the mempool as "pending" rather than executed.

Sending a batch of calls with the same number or with a gap breaks even earlier — the node rejects such a set entirely, without examining the reasons.

The second bottleneck is the block gas limit and competition for space in it. A batch designed for one block almost always does not fit into it: other transactions raise the gas price, and some of our calls are pushed to the next block.

If the contract logic is not designed for execution stretched over time, the state that the batch relied on manages to change between steps.

This is where partial execution appears. Three calls out of seven went through — the balance is debited, limits are updated, but the asset transfer did not happen.

Such state desynchronization is more expensive than an error in a single call: the application considers the operation complete, while half of its steps remained unexecuted.

A rejected batch without atomicity leaves the system in an intermediate state that has to be sorted out manually — reconcile logs, add the missing steps, and explain to the user why the debit did not lead to a result.

Sequence number, gas limit, and lack of atomicity hit together: the same batch passes on the test network and falls apart on mainnet. Below we break down how sending a batch of calls is closed at the contract level, rather than hoping for a lucky block.

What batch sending of calls gives in practice

Batch sending of calls, or batch transactions, turns several separate calls to a contract into one transaction with an array of operations inside.

The base transaction cost and signature verification are paid once instead of N — on ten operations of the same type, gas cost per operation drops by 2-3 times, and the gain grows with batch size.

The "first approval, then swap" bundle collapses into a single call, and the contract's intermediate state does not hang between two confirmations.

The second part of the gain is removing the nonce race. When a dozen sends go out in parallel, some are rejected as stale, some hang in the queue and require manual acceleration.

One batch — one nonce, one signature, one confirmation: the class of failures related to competition for the send queue disappears entirely.

Atomicity closes a more expensive class of failures — partial execution.

If at least one operation inside the batch fails validation, the entire batch reverts: there is no state where the first action applied but the second did not and blocked funds remain in the contract until manual review.

For the user, this is one wait instead of the chain "confirmed — did not work — confirmed again."

Network load for the same amount of useful work drops: fewer transactions in the queue, fewer confirmation requests, fewer reasons to hit the moment of peak block price.

The limitations are also fair — the gas limit per block and per transaction itself, so large sets are cut into parts, and the batch composition is run through a read-only simulation before sending so as not to pay for a guaranteed revert.

Batch transaction formats: comparison of approaches

The batch format determines where conditions are checked and who is responsible for the shared context of calls. On Ethereum networks and compatible ones, three schemes are used: intermediary contract, multi-call inside the contract, and sequential series.

The difference is visible during partial failure — it determines whether you lose the entire batch or only one failed step.

Variant When it fits Limitations
Intermediary aggregator Steps touch different contracts, shared permission checks, pause, and limit accounting are needed A trusted contract appears: its logic must be checked separately, and an error in it blocks the entire batch
Multi-call inside the contract All actions belong to one contract and must execute inseparably Changing the logic requires a new contract, and the volume of one call is limited by block execution
Sequential series of calls Steps are independent, and some of them may be skipped There is no atomicity: intermediate states are visible on the network, and a step failure forces the remainder to be repeated separately

What we consider when choosing

The batch size limit rests not on the number of calls, but on execution cost: the longer the batch, the higher the risk of not fitting within block boundaries. We split it into parts by target cost and add retry keys so that a communication failure does not duplicate operations.

For long series, we aggregate checks: they remain one-time, and the number of steps grows without rebuilding the contract. We check compatibility with already deployed contracts before work begins — otherwise the chosen scheme will require migration.

How we implement batch sending: stages of work

Batch sending starts not with code, but with an inventory of how transactions are sent now. Until it is clear which calls depend on the result of the previous one and which can be combined into one batch, any acceleration turns into stuck operations and state divergence.

  1. Audit of sending scenarios. We break down current operations step by step: where a chain of calls is mandatory, where it arose from interface specifics, and what can be combined into one batch without changing the result.
  2. Flow scheme. We fix the path of each operation: who forms the batch, what it is signed with, in what order calls go out, and what counts as successful completion.
  3. Run on the test network. We compare calculation with fact: gas cost per batch versus the sum of individual calls, confirmation time, behavior under congestion.
  4. Limit check. We match batch size against the upper gas limit per block and node limits: a batch that does not fit entirely is not executed at all.
  5. Failure handling. We describe behavior during partial execution — resending unaccepted calls, an operations journal, notification of the responsible person.
  6. Handover of regulations. We provide maintenance documentation: how to change batch composition, which metrics to watch, and at which thresholds to react.

By the end of the work, the team has not a one-time fix, but a described process: batch composition changes according to regulations, and gas savings are visible in metrics, not in a one-time report.

What the client retains after implementation

Code in the repository is not the only result of the work. After implementation, the client's team retains a set of artifacts that can be used to operate and develop the system without us: from batch sending parameters to the procedure for action during a failure.

We check each item — we hand it over and go through it together with the engineers who will remain on support.

  • Sending configurations — gas strategies, batch size, limits per batch transaction, retry thresholds, and confirmation wait time. Values are not hardcoded: they are changed through configuration, with an explanation nearby — what to adjust and when load grows.
  • Flow schemes — the request path from call to confirmation: where the batch is assembled, where it is signed, where it waits for inclusion in a block. Failure points and behavior during transaction replacement or cancellation are marked.
  • Key access policies — roles, who signs and through which service, how keys are stored, rotation and access revocation procedure. A separate block — actions during compromise.
  • Documentation — description of contracts and methods, data formats, error codes with interpretation and action for each. Enough for a new engineer to join the work from the text, not from hearsay.
  • Maintenance regulations — what we monitor (share of failed batches, confirmation time, remaining funds on signing addresses), which alerts and thresholds, who receives them, and in what order they respond.
  • Recovery plan — how to roll back to the previous state, how to resend stuck batches, and how to verify this on the test environment before the real launch.

Case: atomic payouts for a gaming service

The client is a gaming service with daily mass accruals: match rewards, retention bonuses, refunds for failures.

Each payout went as a separate operation, and with hundreds of thousands of recipients on peak days, most of the cost went not to the reward itself, but to overhead: base cost of the record, signature, and place in the queue. The more congested the Ethereum network was, the more often payout batches fell apart midway.

This affected stuck operations most painfully, not cost. If one payout failed, the handler stopped, and reconciliation — who had already received the reward and who had not — was done manually from the journal.

Re-running the same set led to double accruals, so before each restart the recipient list was exported and compared with history.

We moved payouts to a batch scheme: one call carries up to several hundred recipients, amounts, and a service batch identifier.

The identifier is fixed before distribution, so re-execution of the same batch is cut off at the input — restart is safe and does not require manual reconciliation.

A failure on any recipient reverts the batch entirely: the state remains whole, rather than "half paid out."

By metrics: cost per accrual dropped by about eight times — the base part is divided across the whole batch, and the share of cost going to overhead fell from ~60% to single-digit percentages.

Stuck operations decreased from dozens per week to isolated cases, and closing the gaming day stopped stretching for hours — batches confirm in minutes.

How does batch sending differ from a series of calls?

Batch sending is one transaction in which a smart contract calls several methods in succession within a single execution. All calls land in one block, and the state either changes entirely or does not change at all.

A series of calls is arranged differently: these are separate transactions, each with its own signature, its own place in the queue, and confirmation wait. Time passes between steps, and the intermediate result is already fixed in a block.

If the third call fails, the first two remain executed — rolling back the state will require a separate compensating transaction, which still needs to be composed correctly.

The execution order in a batch is strictly set by code: calls go one after another without gaps, and it is impossible to interpose between them.

In a series, order rests only on the send queue, and between confirmations another transaction can enter it and change the data you relied on.

Gas cost differs for the same reason: in a batch, the base fee and signature are paid once for all operations; in a series — for each transaction separately. The more steps in the chain, the more noticeable the gap.

The boundary is simple. If steps are logically inseparable and must execute or revert together — these are batch transactions.

If pauses, external data, human result verification, or partial fixation of intermediate state are needed between steps — a series of calls with confirmation wait remains.

Technical doubts and answers on batch calls

A batch transaction is one transaction inside which several actions execute sequentially: transfer, method call, state change. For a dApp, this removes extra confirmations and gas cost per step, but creates four engineering questions that are closed before design.

Can calls to different contracts be collected into one batch?

Yes — the batch is assembled through an aggregator contract that calls other addresses in turn and returns the results. The limitation is atomicity: failure of any call reverts the entire sequence.

Therefore, independent operations, where one may fail by condition, are better split into separate batches.

What happens on resend?

A batch identifier is needed — a content hash or a monotonic counter for the address. If the node did not respond and sending was repeated, the contract sees the already executed identifier and rejects the duplicate. Without a label, retry on timeout causes re-execution and state desynchronization.

Is strict ordering of operations inside a batch preserved?

Calls execute exactly in the order they were added, and this is critical for related steps: access confirmation and then the swap must go in one batch, otherwise the state changes between them and the second step fails. The flip side — the intermediate state is not visible externally.

What limits batch size?

Two limits: the gas limit per transaction and block capacity. The batch is assembled with a margin so that it fits into a block under the current network load.

Before sending, we run it on a local node, and if the cost hits the limit, we cut it into parts along the boundaries of meaningful steps — the order is not broken.

Let's discuss the task: batch calls and atomicity

For the task estimate to be engineering-based rather than approximate, we need an input set on the current sending logic.

The more precisely you describe scenarios and operation volumes, the fewer assumptions we will include in the work plan and the more realistic the timeline forecast will be.

  • Current sending scenarios: which contract calls today go as separate batch transactions, which go one by one, and at which step gas cost stops being predictable.
  • Operation volumes: how many calls are in one batch, how often batches are formed, what peak load is per hour and per day.
  • Ordering requirements: whether there is a dependency between calls, whether execution order matters, and what breaks when rearranged.
  • Admissibility of partial execution: whether the group must pass entirely or whether it is acceptable for some operations to apply and some to revert.
  • Target networks and environments: where the logic runs — on testnet or mainnet, whether identical rules are needed for several networks.
  • Access and roles: who signs batches, whether multisig is present, which keys are used in automation.
  • Limitations: maximum batch size and the threshold below which combining calls loses meaning.

Based on this list, we will return with an estimate and work plan: where to combine calls, where atomicity is needed, how to handle failures, and what to do with already sent batches.