For developers

Batching & transactions

A confidential distribution is several onchain actions. Obscura runs them as one all-or-nothing signature using EIP-5792 and EIP-7702, and falls back to separate transactions when a wallet cannot batch.

Why batching matters

A confidential disperse is not a single call. Before tokens move, the sender may need to register disperse sub-wallets, approve the disperse contract as an ERC-7984 operator, and only then run the payout. Done the old way that is three wallet prompts, in order, with a real risk of stopping half way and leaving a confusing partial setup.

Batching turns that into one signature that either fully succeeds or fully reverts. Two EIPs make it possible.

EIP-5792: Wallet Call API

EIP-5792 standardizes how a dapp hands a wallet a batch of calls and tracks them. It defines four JSON-RPC methods:

MethodPurpose
wallet_getCapabilitiesFeature detection: does this account and chain support atomic batching?
wallet_sendCallsSubmit an array of calls (to, data, value) as one bundle.
wallet_getCallsStatusPoll a bundle by its id for status and receipts.
wallet_showCallsStatusAsk the wallet to show its own status UI for a bundle.

The key signal is the atomic capability, which has three states:

  • supported - the wallet executes the calls atomically and contiguously.
  • ready - the wallet can upgrade to atomic execution, pending user approval.
  • unsupported - no atomicity guarantee; fall back to separate transactions.

A dapp sets atomicRequired when it needs all-or-nothing execution rather than best-effort. Obscura reaches these through wagmi's useCapabilities and useSendCalls.

EIP-7702: smart EOAs

EIP-5792 defines the interface, but a plain externally owned account (EOA) historically could not batch. EIP-7702, shipped in the Pectra upgrade (May 2025), closes that gap. It adds a new transaction type (0x04, "set code") that lets an EOA adopt smart contract code by writing a delegation designator (0xef0100 || address) into its own account, authorized by a signed tuple [chain_id, address, nonce, y_parity, r, s].

Its motivation names three UX wins directly:

  • Batching - many operations in one atomic transaction.
  • Sponsorship - one account pays gas for another.
  • Privilege de-escalation - scoped session keys weaker than the main key.

In practice, a 7702-upgraded EOA is what makes an ordinary wallet report atomic: supported or ready. Smart-contract accounts (ERC-4337) already batch, so both paths converge on the same one-signature experience.

A 7702 delegation persists on the account until it is changed, and delegated code has full access to the account, so wallets only point delegations at audited implementations. As a dapp, you never sign the authorization yourself; the wallet handles it when the user opts into smart features.

How Obscura batches

Obscura builds the whole disperse as one call list, checks capabilities, then picks a path.

Detect capabilities
// Detect atomic-batch support for this account + chain (EIP-5792)const { data: caps } = useCapabilities({ account, chainId });const canBatch =  caps?.atomic?.status === "supported" ||  caps?.atomic?.status === "ready";
Send one batch, or fall back
// Build every step of the disperse as one list of callsconst calls = [  // register / approve your disperse sub-wallets (once per token)  // setOperator(disperseSingleton, FAR_FUTURE_DEADLINE)  // disperseConfidentialTokens(token, recipients, handles, subtotals, proof)]; if (canBatch) {  // One signature, all-or-nothing (EIP-5792 wallet_sendCalls)  const { id } = await sendCallsAsync({ calls, forceAtomic: true });  await waitForCallsStatus(config, { id });} else {  // Fallback: one transaction per call, in order  for (const call of calls) {    const hash = await sendTransactionAsync(call);    await waitForTransactionReceipt({ hash });  }}
1

Assemble the calls

Setup (register or approveUserWalletsForToken), the operator approval (setOperator), and the disperse call, only including the steps that are still needed.
2

Check atomic support

useCapabilities reports the atomic status for the connected account on Sepolia.
3

Send atomically

When supported, sendCalls submits the bundle with forceAtomic, so setup and payout are all-or-nothing and can never half-apply.

One signature, all-or-nothing

With a batching wallet, a full disperse (setup, approval, and payout) is a single prompt that either lands completely or reverts completely. No stranded approvals, no partial setup.

The sequential fallback

If the wallet cannot batch (atomic: unsupported, and not a smart account), Obscura runs the exact same call list as separate transactions, in order, waiting for each receipt before the next. The end state is identical; the difference is one prompt per step instead of one for the whole flow.

The same fallback also runs when a wallet that reported atomic: supported or ready still rejects wallet_sendCalls at submit time. Obscura treats that failure the same as an unsupported capability and retries the exact same calls sequentially, rather than surfacing the error.

Which wallets can batch?

  • 7702-upgraded EOAs (a normal wallet that has opted into smart features).
  • Smart-contract accounts (ERC-4337).
  • Everything else takes the sequential path automatically; no configuration needed.

Best-UX checklist

  • Always feature-detect with wallet_getCapabilities; never assume batching.
  • Set atomicRequired when a partial result would be worse than a full revert.
  • Keep a sequential fallback so non-batching wallets still work.
  • Only include the steps that are still needed, so a second run does not re-submit a done approval.
  • Track the bundle with wallet_getCallsStatus before treating it as complete.

References: EIP-5792, EIP-7702, and the EIP-5792 developer reference.