<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BlockVectra</title>
    <description>The latest articles on DEV Community by BlockVectra (@blockvectra).</description>
    <link>https://dev.to/blockvectra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4195569%2F51965796-1833-48e9-bdd2-2ea3ee44be66.png</url>
      <title>DEV Community: BlockVectra</title>
      <link>https://dev.to/blockvectra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/blockvectra"/>
    <language>en</language>
    <item>
      <title>Resumable eth_getLogs scans: fixed chunks, saved checkpoints, safe restarts</title>
      <dc:creator>BlockVectra</dc:creator>
      <pubDate>Wed, 21 Oct 2026 10:25:58 +0000</pubDate>
      <link>https://dev.to/blockvectra/resumable-ethgetlogs-scans-fixed-chunks-saved-checkpoints-safe-restarts-3b3p</link>
      <guid>https://dev.to/blockvectra/resumable-ethgetlogs-scans-fixed-chunks-saved-checkpoints-safe-restarts-3b3p</guid>
      <description>&lt;p&gt;A rate limit, malformed response, or process restart can interrupt a log scan after successful chunks. The useful question then is not "how do I re-run the query?" but "which blocks are already saved, and where does the next request begin?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The first response is not a saved checkpoint
&lt;/h2&gt;

&lt;p&gt;A returned &lt;code&gt;eth_getLogs&lt;/code&gt; result is only candidate data. The client advances to the next block only after two things happen in order: the HTTP status and JSON-RPC envelope pass validation (&lt;code&gt;jsonrpc&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;, no &lt;code&gt;error&lt;/code&gt;, and &lt;code&gt;result&lt;/code&gt; is an array), and a new checkpoint containing that chunk has been committed to disk. A successful empty array is valid. If either validation or the commit fails, the run stops at that chunk. It does not shrink the range, retry, skip ahead, swap checkpoint files, or open a new task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a fixed window
&lt;/h2&gt;

&lt;p&gt;Use Node.js 24 or later. Download the &lt;a href="https://docs.blockvectra.com/examples/getlogs-resumable.mjs?ref=devto-getlogs-checkpoints-1021" rel="noopener noreferrer"&gt;client example&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;Pick a chain, address, and inclusive &lt;code&gt;fromBlock&lt;/code&gt;/&lt;code&gt;toBlock&lt;/code&gt; that you have confirmed and that the node currently serves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Supply BLOCKVECTRA_API_KEY through your secret manager/environment.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;confirmed-chain-slug&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LOG_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;confirmed-address&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;FROM_BLOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;confirmed-start-block&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TO_BLOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;confirmed-end-block&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CHECKPOINT_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./getlogs-checkpoint.json"&lt;/span&gt;
&lt;span class="c"&gt;# optional: export CHUNK_BLOCKS=100&lt;/span&gt;
node getlogs-resumable.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace every placeholder with your own confirmed values. &lt;code&gt;BLOCKVECTRA_API_KEY&lt;/code&gt; is read from your own environment; the client does not create accounts. &lt;code&gt;CHUNK_BLOCKS&lt;/code&gt; is optional and must be a positive decimal integer. Each request spans &lt;code&gt;min(CHUNK_BLOCKS, max_logs_block_range)&lt;/code&gt; blocks, where the limit is read live from &lt;code&gt;GET /v1/chains&lt;/code&gt;; without &lt;code&gt;CHUNK_BLOCKS&lt;/code&gt;, the catalog value is used. Block span, result count, and the 2 MiB response/checkpoint caps are separate limits. The task allows 20 saved chunks, including restored ones. &lt;/p&gt;

&lt;h2&gt;
  
  
  Stop, then continue the same task
&lt;/h2&gt;

&lt;p&gt;Change nothing in the task identity: same chain, address, and complete window. Keep the same checkpoint file and run again. The client re-validates the checkpoint's task binding, contiguous chunk order, and &lt;code&gt;nextBlock&lt;/code&gt;, then requests only blocks not yet saved. You may change &lt;code&gt;CHUNK_BLOCKS&lt;/code&gt; on resume; it only affects how the remaining range is split and never re-fetches saved chunks.&lt;/p&gt;

&lt;p&gt;One process holds the checkpoint at a time through &lt;code&gt;CHECKPOINT_FILE.lock&lt;/code&gt;; if a crash leaves the lock behind, confirm that no process owns it, then remove it manually. Checkpoints are written through a &lt;code&gt;0600&lt;/code&gt; temporary file, &lt;code&gt;fsync&lt;/code&gt;, and an atomic rename. The client does not claim exactly-once behavior across power loss or database commits.&lt;/p&gt;

&lt;p&gt;A final &lt;code&gt;complete&lt;/code&gt; status means the client read every block in that fixed window successfully. It does not prove that upstream data has no missing logs, that archive data covers the range, or that finality and reorgs are handled. Persist idempotently and verify chain hashes on the consumer side.&lt;/p&gt;

&lt;p&gt;The full walkthrough, limits, and recovery rules are in the &lt;a href="https://docs.blockvectra.com/en/guides/getlogs-block-range/?ref=devto-getlogs-checkpoints-1021" rel="noopener noreferrer"&gt;resumable eth_getLogs guide&lt;/a&gt;. You can also download the &lt;a href="https://docs.blockvectra.com/examples/getlogs-resumable.mjs?ref=devto-getlogs-checkpoints-1021" rel="noopener noreferrer"&gt;public client example&lt;/a&gt; and check its &lt;a href="https://docs.blockvectra.com/examples/getlogs-client-cases.md?ref=devto-getlogs-checkpoints-1021" rel="noopener noreferrer"&gt;35 synthetic response fixtures&lt;/a&gt; with a mock transport.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>node</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Gasless transactions: separate the signer, gas payer, and token owner</title>
      <dc:creator>BlockVectra</dc:creator>
      <pubDate>Wed, 21 Oct 2026 01:28:57 +0000</pubDate>
      <link>https://dev.to/blockvectra/gasless-transactions-separate-the-signer-gas-payer-and-token-owner-1elp</link>
      <guid>https://dev.to/blockvectra/gasless-transactions-separate-the-signer-gas-payer-and-token-owner-1elp</guid>
      <description>&lt;p&gt;&lt;em&gt;AI-generated engineering note from BlockVectra.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Gasless" describes an experience, not a protocol. Before writing any test, name four roles. In a demo they are often one address; in production they usually are not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signer&lt;/strong&gt;: the key that authorizes an action off-chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gas payer&lt;/strong&gt;: in the direct EOA-relaying model used for this discussion, the transaction-sending EOA that pays native gas. Other sponsorship architectures can separate these roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token owner&lt;/strong&gt;: the account whose tokens are spent or whose allowance is authorized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract caller&lt;/strong&gt;: the address the target contract sees as &lt;code&gt;msg.sender&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many bugs come from treating two of these as one. A contract that reads the caller as the owner works when the owner sends the transaction and breaks when a relayer does.&lt;/p&gt;

&lt;p&gt;Two token authorization patterns are useful to distinguish. A signed approval, as in &lt;a href="https://eips.ethereum.org/EIPS/eip-2612" rel="noopener noreferrer"&gt;ERC-2612&lt;/a&gt;, only changes an allowance; a transfer must still follow. A signed transfer, as in &lt;a href="https://eips.ethereum.org/EIPS/eip-3009" rel="noopener noreferrer"&gt;ERC-3009&lt;/a&gt;, binds sender, recipient, amount, validity window and nonce into one authorization that a third party can submit. Both need a token or wrapper that implements them, since plain ERC-20 does not. The official ERC-3009 page currently marks it Draft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assert roles, not just outcomes
&lt;/h2&gt;

&lt;p&gt;A passing "transfer succeeded" check hides which account paid for what. Assert each role's balance and allowance separately, before and after, and use distinct token-owner/signer, relayer, and recipient addresses so a mix-up cannot pass by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposed test matrix
&lt;/h2&gt;

&lt;p&gt;This is a suggested plan, not a record of executed results.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Assert&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Zero-native user&lt;/td&gt;
&lt;td&gt;Owner holds tokens and no native coin; relayer pays gas&lt;/td&gt;
&lt;td&gt;Call succeeds; owner token balance drops by the exact amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-token sponsor&lt;/td&gt;
&lt;td&gt;Relayer holds gas funds but zero tokens&lt;/td&gt;
&lt;td&gt;Relayer token balance is zero before and after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actor binding&lt;/td&gt;
&lt;td&gt;Token-owner/signer, relayer, and recipient are distinct&lt;/td&gt;
&lt;td&gt;A signed transfer debits the authorized owner and credits the authorized recipient; a permit updates the authorized owner-to-spender allowance without transferring tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization lifecycle&lt;/td&gt;
&lt;td&gt;Valid, expired, then replayed signature&lt;/td&gt;
&lt;td&gt;Valid works once; the others revert and leave state unchanged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vault accounting&lt;/td&gt;
&lt;td&gt;Tokens credited directly to a vault&lt;/td&gt;
&lt;td&gt;Deposit counters match what the design says they should mean&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  One public case
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/bytesbrains/16ane/blob/1856b9f3307e530b55fff4eefa3f9bc903feeffe/test/AneAgentVaultSignedActions.t.sol#L226-L270" rel="noopener noreferrer"&gt;16ane repository test, pinned to one commit&lt;/a&gt;, has a relayer holding zero mock tANE submit a signed ERC-3009 transfer. The owner's balance falls from 10000 to 8400, the vault holds 1600, and owner principal stays 0, an accepted accounting gap because the deposit method never ran. A second test has the owner sign a permit while the relayer calls the vault's fundWithPermit wrapper. The second test expects an insufficient-allowance revert, because that wrapper treats the caller as the permit owner. That limits the wrapper, not ERC-2612 relaying in general. The tests do not assert that the owner holds zero native coin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying for real
&lt;/h2&gt;

&lt;p&gt;Read &lt;code&gt;balanceOf&lt;/code&gt; and &lt;code&gt;allowance&lt;/code&gt; for the caller and the owner separately, and compare receipts and state deltas, one branch at a time. A simulation or a log status is not end-to-end proof. Also scope "atomic" carefully: a reverted transaction can still cost the sponsor gas, so atomicity here means application state is unchanged, not that fees come back.&lt;/p&gt;

&lt;p&gt;If you are wiring agents to onchain actions, the &lt;a href="https://docs.blockvectra.com/en/guides/ai-agents/?ref=devto-gasless-roles-1021" rel="noopener noreferrer"&gt;BlockVectra agent integration documentation&lt;/a&gt; covers the integration side.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Block heights are not a clock: RPC polling on fast EVM chains</title>
      <dc:creator>BlockVectra</dc:creator>
      <pubDate>Tue, 20 Oct 2026 21:40:28 +0000</pubDate>
      <link>https://dev.to/blockvectra/block-heights-are-not-a-clock-rpc-polling-on-fast-evm-chains-496e</link>
      <guid>https://dev.to/blockvectra/block-heights-are-not-a-clock-rpc-polling-on-fast-evm-chains-496e</guid>
      <description>&lt;p&gt;A block count is not a duration. The same block count does not preserve the same elapsed time across chains: a 20-block cooldown that equals roughly 40 seconds at a 2,000 ms nominal block time equals about 2 seconds where nominal block time is 100 ms. Code that stores retry, backoff, or cooldown windows only in blocks therefore changes behavior when block times differ. The &lt;a href="https://github.com/morpho-org/morpho-bots/issues/4" rel="noopener noreferrer"&gt;morpho-org/morpho-bots issue #4&lt;/a&gt; reports block-denominated backoff and settled cooldown values on a fast chain with roughly 103 ms blocks; that report is the motivation here, not an independent verification.&lt;/p&gt;

&lt;p&gt;The table is a hypothetical conversion for a 40-second intent, not a measurement or a promise about any chain's real block rate.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Nominal block time&lt;/th&gt;
&lt;th&gt;Blocks = ceil(40,000 ms / block time)&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2,000 ms&lt;/td&gt;
&lt;td&gt;ceil(20.0)&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 ms&lt;/td&gt;
&lt;td&gt;ceil(400.0)&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103 ms&lt;/td&gt;
&lt;td&gt;ceil(388.35)&lt;/td&gt;
&lt;td&gt;389&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ceiling means rounding up: 389 blocks at 103 ms represent 40,067 ms, not exactly 40,000 ms. At 2,000 ms and 100 ms the division happens to be exact.&lt;/p&gt;

&lt;p&gt;Three practical checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Poll against an elapsed clock.&lt;/strong&gt; Keep retry decisions in milliseconds from a monotonic elapsed-time source, then convert only for display or where a block boundary is genuinely required. This does not replace chain-state checks and does not by itself address reorgs, nonces, or finality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate backoff from head and cooldown responsibilities.&lt;/strong&gt; Backoff answers "when should this attempt run again?" Head lag and settled cooldown answer "what chain state do we trust?" Do not let one block counter own both questions; changing a timer cannot repair a stale read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exercise fast, slow, paused, and jumpy clocks.&lt;/strong&gt; Test with nominal block times below and above the default, with head progression halted, and with irregular jumps. Keep an explicit manual environment override so an operator can pin behavior during an incident instead of editing defaults.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even with those checks, block conversion remains an approximation. A chain pause, a change in block time, or bursty production makes a block-denominated duration an estimate, not a real-time guarantee. Rounding up reduces undershoot but cannot enforce a deadline.&lt;/p&gt;

&lt;p&gt;For provider-selection context, see the &lt;a href="https://docs.blockvectra.com/en/guides/choose-rpc-provider/?ref=devto-fast-chain-clock-1021" rel="noopener noreferrer"&gt;BlockVectra RPC provider guide&lt;/a&gt;. This is a BlockVectra engineering note and an AI-assisted draft. A different provider does not correct a unit error in application timers.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JSON-RPC batch responses: match by id, not array position</title>
      <dc:creator>BlockVectra</dc:creator>
      <pubDate>Tue, 20 Oct 2026 19:48:26 +0000</pubDate>
      <link>https://dev.to/blockvectra/json-rpc-batch-responses-match-by-id-not-array-position-595d</link>
      <guid>https://dev.to/blockvectra/json-rpc-batch-responses-match-by-id-not-array-position-595d</guid>
      <description>&lt;p&gt;By BlockVectra, with AI-assisted drafting.&lt;/p&gt;

&lt;p&gt;A JSON-RPC batch puts several calls in one request. The reply array is not a positional mirror of the request array. Treating &lt;code&gt;payload[i]&lt;/code&gt; as the answer to &lt;code&gt;requests[i]&lt;/code&gt; can bind every result to the wrong call while the transport still reports success. Here is a three-response mismatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_blockNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_getBalance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x0000000000000000000000000000000000000000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eth_chainId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;32000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;busy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// 7 9&lt;/span&gt;
&lt;span class="c1"&gt;// 8 7&lt;/span&gt;
&lt;span class="c1"&gt;// 9 8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every request is paired with another request's id. Request 7 is now "answered" by id 9's chain id, request 8 by id 7's error, and request 9 by id 8's balance. If the caller only checks that three responses arrived, the batch looks complete. A downstream parser may then apply an &lt;code&gt;eth_chainId&lt;/code&gt; result where an &lt;code&gt;eth_blockNumber&lt;/code&gt; result was expected. These are offline synthetic fixtures, not real calls, results, or balances.&lt;/p&gt;

&lt;p&gt;The specification permits this: response objects "MAY be returned in any order within the Array." The stable join key is the &lt;code&gt;id&lt;/code&gt; carried by both the request and its response. A response's position carries no contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP 200 is not batch success
&lt;/h2&gt;

&lt;p&gt;A batch can return HTTP 200 while containing an item-level error. In the example above, the middle entry is an error for id 7; id 9 still has a result. A status-code check sees only transport success. Code that scans for the first non-error entry, or that assumes every array entry is a result, can silently skip the failed call.&lt;/p&gt;

&lt;p&gt;The same rule applies to completeness. A missing response, a duplicate response id, or a response id that was never sent means the client cannot confirm a complete batch. A malformed whole batch may be returned as one error object instead of an array. That object cannot be paired with any request, so it should be reported as uncorrelated rather than attached to the first request or copied to all of them.&lt;/p&gt;

&lt;p&gt;Each response must carry exactly one of &lt;code&gt;result&lt;/code&gt; or &lt;code&gt;error&lt;/code&gt;, and an error must be a non-null object with an integer &lt;code&gt;code&lt;/code&gt; and a string &lt;code&gt;message&lt;/code&gt;. A &lt;code&gt;result&lt;/code&gt; is allowed to be &lt;code&gt;false&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, or &lt;code&gt;null&lt;/code&gt;; checking truthiness is not a valid presence test.&lt;/p&gt;

&lt;h2&gt;
  
  
  A strict correlator
&lt;/h2&gt;

&lt;p&gt;The complete implementation follows. It is dependency-free and can be copied as one module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasOwn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasOwnProperty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPositiveSafeInt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSafeInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`correlateBatch: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;assertErrorShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`request &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; returned a non-object error`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`request &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; returned a malformed error`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Correlate a JSON-RPC 2.0 batch response array with its requests by id.
 * This checks envelope, id, and error shape only, not result semantics.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;correlateBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;requests must be a non-empty array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;every request must be an object with its own id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isPositiveSafeInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;request ids must be unique positive safe integers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uncorrelated: response payload must be a non-empty array&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;every response row must be a non-null object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jsonrpc&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;every response row must declare jsonrpc 2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;every response row must carry its own id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`unknown response id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`duplicate response id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasResult&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`request &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; must carry exactly one of result or error`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;assertErrorShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`missing response id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It first validates the expected ids. It then rejects non-object rows, a wrong &lt;code&gt;jsonrpc&lt;/code&gt; version, a missing row id, an unknown id, a duplicate id, and a result/error pair that is not exactly one present. Finally, it checks that every expected id was seen. The returned &lt;code&gt;Map&lt;/code&gt; keeps the original response object for each id, so a result or error is not rewritten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consume successes, fail on errors
&lt;/h2&gt;

&lt;p&gt;The snippet is application pseudocode. &lt;code&gt;validateForMethod&lt;/code&gt; and &lt;code&gt;consume&lt;/code&gt; are application-provided; only the correlator above is a complete, copyable module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;correlateBatch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./batch-response.mjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;byId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;correlateBatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;byId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOwn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`RPC &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;validateForMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Callers should iterate over the requests they sent, look up each response by id, and handle the two branches explicitly. Error entries remain errors. This helper does not turn an RPC error into a successful value, retry it, or advance a checkpoint. The &lt;code&gt;map&lt;/code&gt; step validates the whole batch before any &lt;code&gt;consume&lt;/code&gt; call, so later errors block earlier consumptions. This is application ordering, not a database transaction or all-or-nothing guarantee; persistence and checkpoint advancement stay with the application. A job that needs retries should make that decision outside the correlator and should only advance a checkpoint after every required call has a validated success.&lt;/p&gt;

&lt;p&gt;Do not skip the method-specific step. A syntactically valid &lt;code&gt;result&lt;/code&gt; can still be the wrong shape for &lt;code&gt;eth_getLogs&lt;/code&gt;, &lt;code&gt;eth_getBalance&lt;/code&gt;, or any other method. Envelope validation is not result validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope and limits
&lt;/h2&gt;

&lt;p&gt;This example is deliberately stricter than the full protocol. It expects non-empty read-only requests whose ids are unique positive safe integers. It does not cover notifications, null ids, string ids, or fractional ids. Those are constraints chosen for this example, not claims that JSON-RPC forbids other id forms. The function checks envelope, id, and error shape only. It does not validate method result semantics, chain or finality, checkpoints, or the whole protocol.&lt;/p&gt;

&lt;p&gt;Batch size and concurrency are different budgets. Batch size is how many calls go into one request; concurrency is how many requests are in flight. Lowering one does not automatically lower the other. Joining responses by id fixes correlation; it does not remove a provider's batch limit or concurrency limit.&lt;/p&gt;

&lt;p&gt;If you are querying logs over a block range, the BlockVectra getLogs guide is a natural next step: &lt;a href="https://docs.blockvectra.com/en/guides/getlogs-block-range/?ref=devto-jsonrpc-batch-ids-1021" rel="noopener noreferrer"&gt;Get logs over a block range&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By BlockVectra, with AI-assisted drafting. The code was run locally with Node's built-in test runner; independent review is not claimed.&lt;/p&gt;

&lt;p&gt;Runnable MIT companion and 14 offline checks: &lt;a href="https://gist.github.com/gjwang/bf08843780c46ffabc4bb628db11a394" rel="noopener noreferrer"&gt;code and tests&lt;/a&gt;. Download both &lt;code&gt;.mjs&lt;/code&gt; files and run &lt;code&gt;node --test batch-response.test.mjs&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>Wallet balance APIs: null decimals, pagination, and snapshot time</title>
      <dc:creator>BlockVectra</dc:creator>
      <pubDate>Tue, 20 Oct 2026 07:19:14 +0000</pubDate>
      <link>https://dev.to/blockvectra/wallet-balance-apis-null-decimals-pagination-and-snapshot-time-35jk</link>
      <guid>https://dev.to/blockvectra/wallet-balance-apis-null-decimals-pagination-and-snapshot-time-35jk</guid>
      <description>&lt;p&gt;&lt;em&gt;Prepared by the BlockVectra AI assistant. This is a contract-based integration note with an offline JavaScript reproduction; it does not report a live wallet integration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A wallet can receive HTTP 200 and still display the wrong portfolio. Two particularly quiet mistakes are treating unknown token decimals as zero and feeding a paginated response object into a parser that expects an array.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unknown decimals are different from zero
&lt;/h2&gt;

&lt;p&gt;In BlockVectra’s documented address-balance schema, &lt;code&gt;balance&lt;/code&gt; is a decimal integer &lt;strong&gt;string&lt;/strong&gt;. &lt;code&gt;decimals&lt;/code&gt; is an integer from 0 through 255, or &lt;code&gt;null&lt;/code&gt;; &lt;code&gt;symbol&lt;/code&gt; can also be &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Zero decimals is a known scale. Null means the scale is unknown. JavaScript coercion erases that distinction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:assert/strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;9007199254740993&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;9007199254740992&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;9007199254740993&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;9007199254740993&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a synthetic row excerpt, not a complete API response. The assertions reproduce coercion and integer precision loss locally; they make no network requests. Preserve the raw balance string. Use an exact integer/decimal representation for calculations, and leave the scaled amount unknown when decimals is null. Do not invent a symbol or infer a zero balance from missing metadata. BigInt helps with integer arithmetic; convert it back to a string before JSON serialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  An envelope is not a token array
&lt;/h2&gt;

&lt;p&gt;The documented response to &lt;code&gt;GET /v1/data/{chain}/addresses/{address}/balances&lt;/code&gt; has &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;meta&lt;/code&gt;, and an optional &lt;code&gt;next_cursor&lt;/code&gt;. Only &lt;code&gt;data&lt;/code&gt; is the token array. A bare-array parser that silently returns &lt;code&gt;[]&lt;/code&gt; for any object can turn an integration error into a convincing empty wallet.&lt;/p&gt;

&lt;p&gt;Handle HTTP and response-shape failures separately from a valid empty &lt;code&gt;data&lt;/code&gt; array. Follow the opaque &lt;code&gt;next_cursor&lt;/code&gt; unchanged until it is absent. Preserve failures on later pages too: a partial first page is not a complete portfolio. Keep &lt;code&gt;null&lt;/code&gt; distinct from absence where the contract makes that distinction; the final page omits &lt;code&gt;next_cursor&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Indexed head is not snapshot time
&lt;/h2&gt;

&lt;p&gt;For balances, &lt;code&gt;meta.as_of_block&lt;/code&gt; describes the current indexed head. It does not give the balance snapshot’s height or update time. Read &lt;code&gt;meta.refreshed_at&lt;/code&gt; for the snapshot update time; null means stale or unknown update time. A coverage label is not a freshness guarantee.&lt;/p&gt;

&lt;p&gt;Before choosing this endpoint for a chain, check that chain’s &lt;code&gt;data_features&lt;/code&gt; and &lt;code&gt;data_status&lt;/code&gt; in &lt;code&gt;GET /v1/status&lt;/code&gt;. Unsupported coverage returns 422 &lt;code&gt;no_coverage&lt;/code&gt;. This article does not establish current coverage for any particular chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adapter cases worth retaining
&lt;/h2&gt;

&lt;p&gt;Test an explicitly empty successful page, an HTTP/HTML error, an unexpected response shape, several cursor pages, a failed later page, a balance above the safe integer range, null decimals versus zero decimals, and null snapshot time. These are suggested integration cases, not completed end-to-end tests.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.blockvectra.com/en/guides/wallet-assets/?ref=devto-wallet-balance-adapter" rel="noopener noreferrer"&gt;wallet assets guide&lt;/a&gt; covers the endpoint and snapshot semantics. The &lt;a href="https://docs.blockvectra.com/openapi/data.yaml?ref=devto-wallet-balance-adapter" rel="noopener noreferrer"&gt;public Data API schema&lt;/a&gt; defines &lt;code&gt;UInt256String&lt;/code&gt;, &lt;code&gt;AddressBalance&lt;/code&gt;, &lt;code&gt;AddressBalanceListEnvelope&lt;/code&gt;, and &lt;code&gt;Meta&lt;/code&gt;. The contract snapshot used here was saved on 2026-10-20; verify the current contract before implementing an adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reusable, offline-tested consumer
&lt;/h2&gt;

&lt;p&gt;An optional &lt;a href="https://github.com/blockvectra/agent-quickstart/pull/9" rel="noopener noreferrer"&gt;TypeScript balance-page consumer and regression tests&lt;/a&gt; is now merged in BlockVectra Agent Quickstart. It preserves raw integer strings and nullable metadata, follows opaque cursors, and rejects later-page failures or conflicting known snapshot times. If any page has an unknown snapshot time, the aggregate timestamp stays unknown. The PR and main CI each passed 85 offline tests and typecheck on Node 24.21.0. This is a backend helper with a caller-supplied page reader, and has not been run as a live wallet integration.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
