Configuring Web3.js in a DApp: transactions, events, and gas optimization

Transactions in the application go missing, users repeat actions and leave. We set up Web3.js integration turnkey: confirmations arrive predictably, duplicate sends are reduced, and support gets clear procedures. We'll discuss the task and come back with an estimate and plan.

Blockchain Development Services

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1375
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1349
  • B2B Advance company logo design
    B2B Advance company logo design
    794
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1087
  • AIDER company logo development
    AIDER company logo development
    1032
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1129

Web3.js Integration: Why Transactions Hang and Get Lost

A typical Web3.js integration failure looks like this: the call went to the node, the interface showed that it was sent, but no confirmation came back.

The user waits, sees no movement, and clicks again — this is how a stuck transaction appears in the application and a duplicate operation on the network.

The reason is usually that signing and sending live separately from state control.

There is no monitoring of the signature queue, no resending of a transaction with the same nonce and a higher gas fee — the first call may go through minutes later, when the second has already been sent after it. The result: double debiting and desynchronized balances in the accounting logic.

Lost events on subscription are the other half of the problem. The filter is raised after sending, the connection to the node breaks on restart or network change, and some logs do not reach the handler.

The application considers the operation incomplete, although on the network it has long since gone through.

Confirmation processing should proceed in stages: accepted into the queue, included in a block, reached the required depth. If you wait for a single event, a chain reorganization produces a false success — the status was recorded, but the operation is not in the main chain.

We build Web3.js integration so that every action is idempotent: a retry with the same key does not create a new operation, and state is restored from the last reliable point. We explain how this is covered in conjunction with the wallet, node, and accounting layer.

What Changes After Setup: Confirmations, Events, Resilience

Application Response: State Based on the Event

After Web3.js integration, the interface stops waiting for the next poll and updates from a subscription to new blocks and logs.

The delay between an operation entering a block and the application's reaction is reduced to fractions of a second, and the load on the node drops: instead of timer-based requests, there is a single event stream.

Reliable Subscription: a Connection Break Does Not Lose Events

A connection break with the node is commonplace; the question is what happens afterward.

Re-subscribing with backfilling of logs over the missed block range and filtering out duplicates by operation hash restores the full picture: the share of lost events goes to zero, and the balance and history in the interface match the network.

Share of Successful Operations: Gas Limit and Signature Queue

A transaction should not hang because of an underestimated gas limit or an occupied signature order.

Estimating with a margin, resending with a higher limit instead of creating a duplicate, and accounting for stuck operations raise the share of successful sends — manual investigations are replaced by a predictable result.

Predictable Confirmation: Depth and Rollback

An explicit confirmation depth and handling of chain reorganization provide an unambiguous status: the operation is either confirmed or canceled, with no intermediate "stuck" state.

The number of failures decreases because of this, and the application's behavior during a block rollback becomes predictable.

Web3.js Integration Options: Wallet, Server, Custom Signing Layer

The three working connection schemes differ not by library, but by who retains the private key and who is responsible for sending operations to the network. This is where control, confirmation speed, and the node load profile diverge.

Option When It Fits Limitations
Signing in the user's wallet (MetaMask) A product for external users, the key remains with the person Every step requires confirmation in the interface; with mass entry we hit the node's request-rate limits, and a stuck operation cannot be resent automatically
Server-side operation signing Internal payouts, registries, token issuance on request The key lives on the server — a direct risk area: a secure secrets vault, rotation, operator limits, and a log of every signature are required
Custom intermediary service Many similar operations, need a queue, resends, and unified gas accounting More expensive in development and support: custom transaction ordering logic, monitoring of stuck ones, manual replacement during network congestion

In practice, a mixed scheme works more often: the user signs what concerns their funds, while service operations go through an intermediary layer with volume restrictions and gas limits. This removes the main risk — a single key with unlimited access.

We choose the connection scheme based on load and control requirements, not on development convenience: the wrong option surfaces at the very first spike in activity, when transactions begin to hang and the bottleneck turns out to be in signing, not in the network.

Workflow: From Call Analysis to Handover to Support

The order of stages is fixed not for reporting: each step relies on the data from the previous one.

Until the actual contract calls are analyzed, the network interaction scheme and event processing are written "according to the documentation" — and it diverges from what actually goes out to the network.

  1. Call audit. We capture the log of node requests for a working period and compare it with the contract methods: which reads are duplicated, where gas estimation is inflated, which transactions go out without result verification.
  2. Network interaction scheme. We record failure points — node access providers and the switching order between them, request rate limits, timeouts, the signature queue, and behavior on resend.
  3. Event processing setup. We switch Web3.js subscriptions to backfill the missed block range: after a connection break, the handler restores the log instead of losing events. Duplicates are filtered by hash and index — the handlers are idempotent.
  4. Gas optimization. We combine safe calls into one transaction, take the estimate before sending, and keep a margin for state changes — the share of transactions that fail on execution decreases.
  5. Testing on a testnet. We run the main and emergency scenarios: loss of connection with the provider, competing transactions, contract failure. After each run, we compare the application and network state.
  6. Solution handover. We provide configurations, the network interaction scheme, key rotation procedure, and analysis of typical incidents so that support can see the cause of a failure from the logs.

What Your Team Retains: Configurations, Schemes, Regulations

We deliver the project not as a working demo, but as a set of artifacts by which your team lives on without us. Everything is in your repository and knowledge base, tied to specific files, not to messenger correspondence. Below is what exactly remains as the output.

  • Connection configuration — a list of primary and backup network nodes, timeouts, the concurrent request limit, environment variables with an explanation of each value, and ready-made examples for the test and production environments.
  • Event processing scheme — which events we listen to, by what criteria we filter duplicates, and how we backfill the missed block range after a service restart.
  • Retry policy — where we retry with increasing delay, where we stop immediately, how many attempts there are, and by what criterion an operation goes to manual investigation.
  • Fee management — price calculation rules, the moment of sending to the network, and the procedure for a stuck operation: speed up, replace, or cancel.
  • Integration documentation — method descriptions, request and response formats, short call examples, and a table of typical errors with a ready-made solution for each.
  • Rollback procedure — the order for stopping subscriptions and background tasks, the composition of the previous working release, the list of irreversible operations, and the text the user sees in that case.
  • Access matrix and monitoring — who gets which roles and keys, how often rotation occurs, the set of metrics with thresholds, and alert recipients.

We verify each item in a live environment before handover: we bring up the project from scratch using this documentation and record discrepancies. Such a run takes a new engineer hours, not a sprint.

Implementation Result: How Resends and Failures Decreased

What It Was Like Before Implementation

A payout service on its own smart contract sent the transaction directly from the interface: the gas price was set as a constant, the hash status after sending was not tracked, and repeated button clicks were not limited in any way.

During network congestion, transactions got stuck in the pool, users tried to send the operation again — duplicates were closed manually, and event logs were pulled from the last block without a confirmation window, so during chain reorganization some records were lost.

What We Changed

The network access layer was rewritten as a custom module on top of Web3.js: a single sending point with an idempotent key, automatic transaction replacement by price, tracking of each hash status until finality, and clear button states in the interface.

The event subscription was supplemented with a confirmation window and restoration of the missed block range after a connection break.

What It Became

The share of operations successful on the first attempt grew from 62% to 97%, and resends decreased almost ninefold. The interface response after a click dropped from several seconds to fractions of a second — the status appears immediately, not after a manual page refresh.

There are no more lost events on reconnection, and the "orphaned transaction" failure class is closed at the handler level.

How Does Working Through a Wallet Differ from Server-Side Signing?

Wallet signing means that the private key never leaves the user's device: the application assembles the contract call, and confirmation comes from the extension or mobile wallet.

Control over the operation remains with the owner — they see the amount, address, and fee before sending and can reject it. The price of such control is an extra step in the scenario and the dependency on the wallet being connected to the right network.

Server-side operation signing is built differently: the key resides in a secure vault on the service side, and the backend itself forms, signs, and sends the transaction.

The user does not confirm every action, so batch payouts and accruals proceed without pauses, and the operator bears responsibility for an erroneous address or an inflated fee.

The difference between connection schemes is visible in the code. With wallet signing, account and network change events arrive, and the application must react to them — otherwise the transaction will go to the wrong chain.

The server scheme does not provide such events, but it requires its own accounting: a sending queue, transaction ordering, and retries on connection loss.

The choice of signing is about who is trusted with the assets and who pays for gas. Where the user is in control, a wallet is needed; where the service operates, limits on the operation amount, a role model, and a log of signed actions are needed.

In Web3.js integration, both schemes coexist in one application: critical operations go to the owner for confirmation, while routine ones are handled by the server.

Common Technical Doubts When Connecting to the Network

Web3.js integration is not only contract method calls, but also an operating mode: what will happen when the node provider starts responding with errors, how to debug scenarios without real funds, and what your team will retain after handover. Below are the doubts most often discussed at the assessment stage.

Timings are a separate layer: a transaction hangs because of an underestimated gas price or a stale sent-transaction counter, and a confirmed block can be canceled by a chain reorganization.

We build correctness verification into the logic: we wait for several confirmations and compare the contract state before writing to the accounting system.

What happens if the main network node stops responding?

The connection goes through a pool of independent providers: liveness checks, automatic switching to a backup, retrying the request with the same identifier — duplicate sends are excluded.

If the entire network is unavailable, the operation is queued and replayed after connectivity is restored.

Can the integration be tested without real funds?

Yes — we bring up a test environment: a local node and a public testnet.

We run scenarios with emulated provider failures and confirmation delays, and we verify the event subscription on a copy of the production configuration, so no discrepancies arise when switching to the main network.

What remains for the team after project handover?

Infrastructure, on-call and incident response regulations, and dashboards with metrics on the share of successful transactions and confirmation time. Our engineers remain in touch in an agreed support mode: incident analysis, dependency updates, and training for your team.

Let's Discuss the Task: We'll Analyze Calls and Propose a Working Scheme

Discussion of the task begins with you sending the current state and requirements — this is enough for us to come back with an assessment and work plan, rather than with clarifying questions for a week.

The more accurate the initial data on failures and application behavior on the network, the fewer hypotheses will have to be checked on your side.

  • Interaction scheme: which contracts and networks the application works with, who signs transactions — the user's external wallet, a server key, or both options, and whether roles and multisig are needed.
  • Initial data on failures: node and application logs, hashes of stuck or rejected transactions, full error texts, and the steps at which the problem reproduces consistently.
  • Sending behavior: how the gas price is currently set, whether there are retries on hanging, and what happens to an already sent transaction during network congestion.
  • Event handling: which events you listen to, whether post-confirmation processing is needed, and how the application survives a change of the latest blocks and a connection gap.
  • Current implementation: access to the repository or code archive, library versions, test node configuration, and test keys, if any.
  • Constraints: the networks you plan to work with, the expected number of transactions per hour, and requirements for the number of confirmations before recording the result.
  • Starting order: who on your side makes decisions on disputed points and by what deadline the result is needed — the sequence of stages in the plan depends on this.

In response, you receive an analysis of failure causes, a component interaction scheme, a list of changes for transactions and events, and a work plan with the sequence of stages. Then we move according to the agreed starting order — without stops for intermediate approvals.