Skip to content

fix(rpc): eth_getTransactionByHash returns EVM-standard JSON shape#692

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/rpc-gettxbyhash-format
May 20, 2026
Merged

fix(rpc): eth_getTransactionByHash returns EVM-standard JSON shape#692
github-actions[bot] merged 1 commit into
mainfrom
fix/rpc-gettxbyhash-format

Conversation

@satyakwok
Copy link
Copy Markdown
Collaborator

@satyakwok satyakwok commented May 20, 2026

Pre-fix the handler returned the chain-native shape (block_hash, nested transaction.{amount,chain_id,data:"EVM:..."}) directly, which ethers.js / viem / alloy / Hyperlane CLI all reject with "missing from address". eth_sendRawTransaction and eth_getTransactionReceipt already return EVM-standard responses on the same chain, so cast send works but anything that polls a freshly-broadcast tx for confirmation crashes.

Mirror the receipt handler's conversion logic for getTransactionByHash:

  • expand from/to/value/gas/gasPrice/input/nonce/type/chainId
  • map TOKEN_OP_ADDRESS to to:null for contract creations
  • parse EVM::<input_hex> envelope for gas + input
  • decode stored RLP envelope back into v/r/s via alloy
  • amount (sentri) -> value (wei) via *1e10
  • type 0x2 + maxFeePerGas for EVM txs, 0x0 for native

Reproducer + workaround discussion isolated 2026-05-20 while running Hyperlane warp deploy for the Base Sepolia USDC -> Sentrix Testnet sUSDC route. ProxyAdmin + HypERC20 implementation deployed cleanly via cast (sendRawTx + getReceipt path), then the CLI crashed once it tried provider.getTransaction(hash).

Summary

Risk tier

Check ONE:

  • 🟢 Low — docs, tools/, tests/, CI configs, dependency patch bumps in dev-only crates, comments
  • 🟡 Medium — non-consensus production code (RPC handlers, network plumbing, observability, ops scripts)
  • 🟠 High — consensus-critical crates (sentrix-core, sentrix-trie, sentrix-staking, sentrix-bft), block_executor, apply_block_*, state_root path
  • 🔴 Critical — Voyager activation, fork-height changes, hard-fork rollouts, anything that flips env vars on mainnet

Required by tier

🟢 Low — minimum bar

  • CI green (tests + clippy + audit + gitleaks)

🟡 Medium — adds

  • New public function or behavioural change has at least one corresponding #[test] in same PR
  • Brief description of how this was tested (manual run, integration test, etc.)

🟠 High — adds

  • Regression test that fails on main and passes with this change — paste test name in PR body
  • Designed against documented invariant (link the audit/runbook/design doc)
  • Fresh-brain review by someone other than the author (per the consensus-change review checklist)
  • Single conceptual unit per PR (no bundling — bundling consensus changes burned us on v2.1.12 → 2026-04-25 livelock)

🔴 Critical — adds

  • Testnet rehearsal completed with success criteria + log evidence linked here
  • Bake window observed: minimum 2h on testnet at the same configuration before mainnet
  • Coordinated rollback plan documented in PR body — exact commands operator runs if it fails
  • Operator sign-off at activation moment (not just PR approval — separate moment for the actual flip)

Test plan

Rollback plan

Related

Summary by CodeRabbit

  • Chores

    • Workspace version bumped to 2.2.12
  • Bug Fixes

    • Fixed RPC transaction lookup to return properly formatted Ethereum-compatible transaction objects. Transactions now include normalized addresses, values in wei, gas information, input data, and signature components in the standard format.

Review Change Stack

Pre-fix the handler returned the chain-native shape (block_hash,
nested transaction.{amount,chain_id,data:"EVM:..."}) directly,
which ethers.js / viem / alloy / Hyperlane CLI all reject with
"missing from address". eth_sendRawTransaction and
eth_getTransactionReceipt already return EVM-standard responses
on the same chain, so cast send works but anything that polls a
freshly-broadcast tx for confirmation crashes.

Mirror the receipt handler's conversion logic for getTransactionByHash:
- expand from/to/value/gas/gasPrice/input/nonce/type/chainId
- map TOKEN_OP_ADDRESS to to:null for contract creations
- parse EVM:<gas>:<input_hex> envelope for gas + input
- decode stored RLP envelope back into v/r/s via alloy
- amount (sentri) -> value (wei) via *1e10
- type 0x2 + maxFeePerGas for EVM txs, 0x0 for native

Reproducer + workaround discussion isolated 2026-05-20 while running
Hyperlane warp deploy for the Base Sepolia USDC -> Sentrix Testnet
sUSDC route. ProxyAdmin + HypERC20 implementation deployed cleanly
via cast (sendRawTx + getReceipt path), then the CLI crashed once
it tried provider.getTransaction(hash).
@github-actions github-actions Bot enabled auto-merge (squash) May 20, 2026 03:46
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

This PR bumps the Cargo workspace version to 2.2.12 and refactors the eth_getTransactionByHash RPC method to return standards-compliant EIP-1474 Ethereum transaction objects. The implementation converts chain-native transaction fields into the standard shape by normalizing addresses with 0x prefixes, deriving wei-denominated value, parsing EVM envelope metadata for gas and input, computing transaction index via block scan, and extracting v/r/s signature components from stored RLP. A helper function decodes RLP-encoded signatures into their constituent parts.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning PR description lacks required template sections (Summary, Test plan) and does not follow the standard format, though it provides substantial technical context. Add a concise Summary section (1-3 sentences) and complete the Test plan section with specific test cases executed. Ensure all risk-tier required checklist items are addressed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: making eth_getTransactionByHash return EVM-standard JSON format instead of chain-native shape.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rpc-gettxbyhash-format

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/sentrix-rpc/src/jsonrpc/eth.rs`:
- Around line 228-232: The code currently maps missing inclusion fields into
genesis-looking defaults (block_index -> 0, block_hash -> "" etc.); change the
parsing in the transaction-building code (where variables block_index,
block_hash, transaction_index are read from tx_data) to produce null/None when
the fields are absent or not present instead of
unwrap_or(0)/unwrap_or_default(). Specifically, for block_index and
transaction_index return an Option/None (or serde_json::Value::Null) when
tx_data["block_index"] / tx_data["transaction_index"] is missing, and for
block_hash return None/null when tx_data["block_hash"] is missing or empty;
apply the same change at the other occurrences you noted (the similar parsing
blocks around lines mentioned) so pending transactions serialize with
blockNumber, blockHash, and transactionIndex set to null until mined.
- Around line 300-317: The code currently hard-codes every EVM transaction as
EIP-1559 (tx_type "0x2", INITIAL_BASE_FEE, zero priority fee) and uses a single
extract_vrs_from_rlp path for signatures; instead decode the original TxEnvelope
variant and populate fields from that envelope: call the transaction decoder
that returns a TxEnvelope (or equivalent) for tx_obj["signature"]/raw RLP, match
on variants (Legacy, EIP2930, EIP1559, EIP4844), set tx_type appropriately
("0x0","0x1","0x2", etc.), fill maxFeePerGas/maxPriorityFeePerGas or gasPrice
from the decoded envelope rather than INITIAL_BASE_FEE, and extract v/r/s from
the decoded signature bytes (use extract_vrs_from_rlp only where appropriate for
legacy/2930 signed envelopes); update both the block response construction
around tx_type/gas fields and the later signature handling (the other affected
block) to use the decoded TxEnvelope data.
- Around line 235-240: The code that builds the sender string from
tx_obj["from_address"] (variables from_raw and from) must normalize "COINBASE"
(case-insensitive) and empty values to the canonical zero EVM address instead of
emitting "0xCOINBASE" or "0x"; update the logic that computes from (and the
analogous occurrence around the other instance noted) to check if from_raw is
empty or equals "COINBASE" (ignoring case) and in those cases set from to
"0x0000000000000000000000000000000000000000", otherwise preserve the existing
0x-prefix handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8ad83114-4750-4724-9acf-abe3fa72dde7

📥 Commits

Reviewing files that changed from the base of the PR and between d8f76ac and 210572d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (2)
  • Cargo.toml
  • crates/sentrix-rpc/src/jsonrpc/eth.rs

Comment on lines +228 to +232
let block_index = tx_data["block_index"].as_u64().unwrap_or(0);
let block_hash = tx_data["block_hash"]
.as_str()
.map(|h| if h.starts_with("0x") { h.to_string() } else { format!("0x{h}") })
.unwrap_or_default();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return pending location fields as null, not genesis-looking defaults.

This path still turns missing inclusion data into blockNumber = "0x0", blockHash = "", and transactionIndex = "0x0". Given the PR repro is provider.getTransaction(hash) immediately after broadcast, pending transactions will still be mis-shaped here. EIP-1474 clients expect those three fields to be null until the tx is mined.

Proposed fix
-    let block_index = tx_data["block_index"].as_u64().unwrap_or(0);
-    let block_hash = tx_data["block_hash"]
+    let block_index = tx_data["block_index"].as_u64();
+    let block_hash = tx_data["block_hash"]
         .as_str()
         .map(|h| if h.starts_with("0x") { h.to_string() } else { format!("0x{h}") })
-        .unwrap_or_default();
+        .map(Value::String)
+        .unwrap_or(Value::Null);
...
-    let tx_index = bc
-        .get_block_any(block_index)
+    let tx_index = block_index.and_then(|block_index| {
+        bc.get_block_any(block_index)
         .and_then(|b| b.transactions.iter().position(|t| t.txid == txid))
         .map(|i| to_hex(i as u64))
-        .unwrap_or_else(|| "0x0".to_string());
+    });
...
-        "blockHash": block_hash,
-        "blockNumber": to_hex(block_index),
-        "transactionIndex": tx_index,
+        "blockHash": block_hash,
+        "blockNumber": block_index.map(to_hex),
+        "transactionIndex": tx_index,

Also applies to: 285-289, 321-323

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sentrix-rpc/src/jsonrpc/eth.rs` around lines 228 - 232, The code
currently maps missing inclusion fields into genesis-looking defaults
(block_index -> 0, block_hash -> "" etc.); change the parsing in the
transaction-building code (where variables block_index, block_hash,
transaction_index are read from tx_data) to produce null/None when the fields
are absent or not present instead of unwrap_or(0)/unwrap_or_default().
Specifically, for block_index and transaction_index return an Option/None (or
serde_json::Value::Null) when tx_data["block_index"] /
tx_data["transaction_index"] is missing, and for block_hash return None/null
when tx_data["block_hash"] is missing or empty; apply the same change at the
other occurrences you noted (the similar parsing blocks around lines mentioned)
so pending transactions serialize with blockNumber, blockHash, and
transactionIndex set to null until mined.

Comment on lines +235 to +240
let from_raw = tx_obj["from_address"].as_str().unwrap_or("").to_string();
let from = if from_raw.starts_with("0x") {
from_raw.clone()
} else {
format!("0x{from_raw}")
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize coinbase/empty senders to the zero address here too.

eth_getTransactionReceipt already guards from == "COINBASE" / empty because the chain-native value is not a valid EVM address. This helper currently emits 0xCOINBASE or 0x, which will break the same parsers this PR is trying to satisfy for reward/system transactions.

Proposed fix
-    let from_raw = tx_obj["from_address"].as_str().unwrap_or("").to_string();
-    let from = if from_raw.starts_with("0x") {
-        from_raw.clone()
-    } else {
-        format!("0x{from_raw}")
-    };
+    let from_raw = tx_obj["from_address"].as_str().unwrap_or("");
+    let from = if from_raw.is_empty() || from_raw == "COINBASE" {
+        ZERO_HASH_HEX[..42].to_string()
+    } else if from_raw.starts_with("0x") {
+        from_raw.to_string()
+    } else {
+        format!("0x{from_raw}")
+    };

Also applies to: 324-324

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sentrix-rpc/src/jsonrpc/eth.rs` around lines 235 - 240, The code that
builds the sender string from tx_obj["from_address"] (variables from_raw and
from) must normalize "COINBASE" (case-insensitive) and empty values to the
canonical zero EVM address instead of emitting "0xCOINBASE" or "0x"; update the
logic that computes from (and the analogous occurrence around the other instance
noted) to check if from_raw is empty or equals "COINBASE" (ignoring case) and in
those cases set from to "0x0000000000000000000000000000000000000000", otherwise
preserve the existing 0x-prefix handling.

Comment on lines +300 to +317
// Same EIP-1559 type + effectiveGasPrice rationale as receipts. EVM txs
// go through the base_fee pipeline, native txs do not.
let (tx_type, gas_price) = if is_evm {
("0x2", to_hex(sentrix_evm::gas::INITIAL_BASE_FEE))
} else {
("0x0", "0x0".to_string())
};

// For EVM txs, decode the original RLP envelope stored in `signature`
// (see eth_send_raw_transaction). That gives us the canonical v/r/s
// ethers needs to round-trip the tx. For native txs we don't have
// ECDSA-on-EVM-envelope semantics; return zeros so the field is present.
let (v_hex, r_hex, s_hex) = if is_evm {
extract_vrs_from_rlp(tx_obj["signature"].as_str().unwrap_or(""))
.unwrap_or_else(|| ("0x0".to_string(), "0x0".to_string(), "0x0".to_string()))
} else {
("0x0".to_string(), "0x0".to_string(), "0x0".to_string())
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Decode the actual envelope kind before filling type/fee/signature fields.

eth_sendRawTransaction accepts legacy, 2930, 1559, and 4844 envelopes, but this response hard-codes every EVM tx to type = 0x2, maxFeePerGas = INITIAL_BASE_FEE, maxPriorityFeePerGas = 0, and v = 0/1. That makes non-1559 transactions materially wrong, and legacy v loses the signed recovery id. Please branch on the decoded TxEnvelope variant and populate the response from the original envelope instead of from is_evm.

Also applies to: 345-357

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sentrix-rpc/src/jsonrpc/eth.rs` around lines 300 - 317, The code
currently hard-codes every EVM transaction as EIP-1559 (tx_type "0x2",
INITIAL_BASE_FEE, zero priority fee) and uses a single extract_vrs_from_rlp path
for signatures; instead decode the original TxEnvelope variant and populate
fields from that envelope: call the transaction decoder that returns a
TxEnvelope (or equivalent) for tx_obj["signature"]/raw RLP, match on variants
(Legacy, EIP2930, EIP1559, EIP4844), set tx_type appropriately
("0x0","0x1","0x2", etc.), fill maxFeePerGas/maxPriorityFeePerGas or gasPrice
from the decoded envelope rather than INITIAL_BASE_FEE, and extract v/r/s from
the decoded signature bytes (use extract_vrs_from_rlp only where appropriate for
legacy/2930 signed envelopes); update both the block response construction
around tx_type/gas fields and the later signature handling (the other affected
block) to use the decoded TxEnvelope data.

@github-actions github-actions Bot merged commit 3b25889 into main May 20, 2026
13 checks passed
github-actions Bot pushed a commit that referenced this pull request May 20, 2026
…onByHash (#695)

Two follow-ups on the EVM-shape conversion shipped in #692, both flagged
by review after merge:

1. Pending / not-yet-included txs now return null for blockHash,
   blockNumber, and transactionIndex instead of genesis-looking defaults
   (block 0, empty string, "0x0"). EIP-1474 clients rely on these three
   being null to distinguish a mined tx at genesis from one still in the
   mempool. Pre-fix, ethers.js would happily treat any pending tx as
   "mined at block 0" and start polling for confirmations against the
   genesis block.

2. Coinbase / system-emitted txs ("COINBASE" sentinel sender, or empty)
   now serialize from as the zero address. Receipts already do this; the
   tx-by-hash path was emitting "0xCOINBASE" or "0x" verbatim, which
   crashes the same address parsers the original fix tried to unblock.

Skipped the third review point — branching tx_type/maxFeePerGas/v on
the original TxEnvelope variant — because every EVM tx on Sentrix
currently flows through the EIP-1559 base-fee pipeline (cast/forge/
Hyperlane all send type 0x2), so the hard-coded "0x2" matches actual
execution semantics. Worth revisiting if/when legacy or 2930 envelopes
start landing.
github-actions Bot pushed a commit that referenced this pull request May 20, 2026
…onByHash (#702)

Two follow-ups on the EVM-shape conversion shipped in #692, both flagged
by review after merge:

1. Pending / not-yet-included txs now return null for blockHash,
   blockNumber, and transactionIndex instead of genesis-looking defaults
   (block 0, empty string, "0x0"). EIP-1474 clients rely on these three
   being null to distinguish a mined tx at genesis from one still in the
   mempool. Pre-fix, ethers.js would happily treat any pending tx as
   "mined at block 0" and start polling for confirmations against the
   genesis block.

2. Coinbase / system-emitted txs ("COINBASE" sentinel sender, or empty)
   now serialize from as the zero address. Receipts already do this; the
   tx-by-hash path was emitting "0xCOINBASE" or "0x" verbatim, which
   crashes the same address parsers the original fix tried to unblock.

Skipped the third review point — branching tx_type/maxFeePerGas/v on
the original TxEnvelope variant — because every EVM tx on Sentrix
currently flows through the EIP-1559 base-fee pipeline (cast/forge/
Hyperlane all send type 0x2), so the hard-coded "0x2" matches actual
execution semantics. Worth revisiting if/when legacy or 2930 envelopes
start landing.
github-actions Bot pushed a commit that referenced this pull request May 21, 2026
…ros (#703)

Audit M-6 follow-up against #692. `extract_vrs_from_rlp` returns
`Option<(String,String,String)>`; the caller used `unwrap_or_else`
to substitute `("0x0","0x0","0x0")` on decode failure. All-zero r/s
parses as a valid ECDSA point pair and ecrecover returns a
deterministic junk address — a caller doing best-effort sender check
via the signature could trust that junk.

Surface v/r/s as `null` instead. EIP-1474 clients already treat null
sig fields as "not recoverable" (matches the pending-tx convention),
so ethers / viem behave correctly without changes. Log a `warn!` when
this path fires — successful decode is the steady state for any tx
that came in via `eth_sendRawTransaction`, so a failure here means
the stored signature field is corrupted or the wire format drifted.

For native (non-EVM) txs the same null fields apply; there is no
Ethereum-shaped signature to surface, and shape-consistency between
the two paths simplifies downstream parsers.

No behavior change for healthy EVM txs (which is everything that
landed via the standard sender-recovered path).
satyakwok added a commit that referenced this pull request May 21, 2026
2.2.14 went live on mainnet 2026-05-21 17:02 UTC carrying PRs
#692/#696/#702/#703/#704. Document the five fixes + flag that
the eth_call + logsBloom fixes on this branch are scheduled
for 2.2.15.
github-actions Bot pushed a commit that referenced this pull request May 21, 2026
* fix(rpc): eth_getTransactionCount + eth_call + logsBloom length

Three discrete chain-RPC fixes bundled because they share one cause
(strict-but-non-spec behavior breaking off-the-shelf EVM agents). All
discovered together standing up the Hyperlane relayer for the Base
Sepolia ↔ Sentrix Testnet bridge (audit fix H-4).

1) eth_getTransactionCount accepts any block tag.
   Relayer + ethers + viem all pin nonce queries to a recent past
   block. A stale nonce is self-correcting (chain rejects wrong-nonce
   tx, caller retries) so this method serves current nonce regardless
   of block tag.

2) eth_call accepts any block tag.
   Same pattern: Hyperlane queries Mailbox.delivered(msgId) and
   recipientIsm(addr) at past blocks. Returns current-state. The strict
   gate stays on eth_getBalance / eth_getCode / eth_getStorageAt where
   wrong = wrong protocol decision.

3) Empty logsBloom is now actually 256 bytes (Ethereum spec).
   The EMPTY_LOGS_BLOOM const was 304 bytes (608 hex chars) because of
   an off-by-one in the original hand-typed string. The doc comment
   said 256 but the literal was 304. ethers' fee-oracle middleware
   strict-parses Block.logsBloom — Hyperlane gas estimation
   SerdeJson'd on this with "invalid length 608, expected 256 bytes"
   before any process() tx could be submitted.

Bumps workspace 2.2.13 -> 2.2.14.

* docs(changelog): add 2.2.14 entry — five RPC compat fixes

2.2.14 went live on mainnet 2026-05-21 17:02 UTC carrying PRs
#692/#696/#702/#703/#704. Document the five fixes + flag that
the eth_call + logsBloom fixes on this branch are scheduled
for 2.2.15.
github-actions Bot pushed a commit that referenced this pull request May 22, 2026
…bytes (#707)

* fix(rpc): eth_getTransactionCount + eth_call + logsBloom length

Three discrete chain-RPC fixes bundled because they share one cause
(strict-but-non-spec behavior breaking off-the-shelf EVM agents). All
discovered together standing up the Hyperlane relayer for the Base
Sepolia ↔ Sentrix Testnet bridge (audit fix H-4).

1) eth_getTransactionCount accepts any block tag.
   Relayer + ethers + viem all pin nonce queries to a recent past
   block. A stale nonce is self-correcting (chain rejects wrong-nonce
   tx, caller retries) so this method serves current nonce regardless
   of block tag.

2) eth_call accepts any block tag.
   Same pattern: Hyperlane queries Mailbox.delivered(msgId) and
   recipientIsm(addr) at past blocks. Returns current-state. The strict
   gate stays on eth_getBalance / eth_getCode / eth_getStorageAt where
   wrong = wrong protocol decision.

3) Empty logsBloom is now actually 256 bytes (Ethereum spec).
   The EMPTY_LOGS_BLOOM const was 304 bytes (608 hex chars) because of
   an off-by-one in the original hand-typed string. The doc comment
   said 256 but the literal was 304. ethers' fee-oracle middleware
   strict-parses Block.logsBloom — Hyperlane gas estimation
   SerdeJson'd on this with "invalid length 608, expected 256 bytes"
   before any process() tx could be submitted.

Bumps workspace 2.2.13 -> 2.2.14.

* docs(changelog): add 2.2.14 entry — five RPC compat fixes

2.2.14 went live on mainnet 2026-05-21 17:02 UTC carrying PRs
#692/#696/#702/#703/#704. Document the five fixes + flag that
the eth_call + logsBloom fixes on this branch are scheduled
for 2.2.15.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant