From be2b5fa66ddd02690ce87935dac7a763886a6334 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 24 Sep 2026 12:58:21 +0300 Subject: [PATCH 1/5] feat: move to hegota-testnet (chain 8141) and EIP-8250 keyed nonces --- .github/ISSUE_TEMPLATE/03-feature.yml | 2 +- CONTRIBUTING.md | 47 +- README.md | 30 +- docs/DESIGN.md | 558 ++++++------------ docs/OPEN-ITEMS.md | 44 -- package.json | 3 +- scripts/capture-fixtures.ts | 131 ++-- scripts/send-eth-eoa.ts | 26 +- src/accounts/toEoaFrameAccount.ts | 28 +- src/accounts/types.ts | 7 +- src/divergence.ts | 4 +- src/envelope.ts | 59 +- src/gas.ts | 30 +- src/index.ts | 10 + src/nonce.ts | 60 ++ src/prepareFrameTransaction.ts | 29 +- src/rpc.ts | 8 +- src/types.ts | 14 +- test/accounts.test.ts | 42 +- test/chain.oracle.test.ts | 63 ++ test/envelope.decode.test.ts | 55 +- test/envelope.encode.test.ts | 20 +- test/envelope.validate.test.ts | 68 ++- ...84412ec2c327faf6d612fe34f1b6dd7f9daf8.json | 124 ++++ ...121ccfb1f46135f107cf2b0be15530bbbc895.json | 190 ++++++ ...72e219df996eb774babb22138beae889ff019.json | 150 ----- ...952ca510bb30634d3e8c0ebc5abc9dadc12a3.json | 98 +++ ...211ca9a27945eeaae6c51ad0d8d0d501b45ac.json | 211 +++++++ ...c6ee9a59c648985dbba9497dfea454cccaa4a.json | 203 ------- ...5b96f41fbe309e1facd193e06f2e659e1e490.json | 97 +++ ...e262a786d614d20cb80edea1528ce7be19200.json | 90 +++ ...edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4.json | 124 ++++ ...5f42491e14c31978f019263a68ab7eb2d19c0.json | 117 ++++ ...454b8d41af5ee654f7ff609b09dd75ea55d03.json | 97 +++ ...dc6506656b3ac0c374c85a34f3b9e6954fc12.json | 124 ++++ ...e086f95bd6dadd12679cea1c57b9c51c856be.json | 155 ----- ...b62fad2962b589e51ce90eb6462440b6d2a66.json | 176 ------ ...7bef590df3878e94198a2b64fff58592ac8d5.json | 212 +++++++ ...afc743251f8cac0b5b1904722bc5c15c8cc8f.json | 117 ++++ ...c0a6589d82d3ba229dcc93ab6f6cb11925fb1.json | 125 ---- ...9245fce355d3c84eb44938e2b7ed18bc17dc6.json | 139 +++++ ...7a95b018c43807f1bc6381514bedc4546b971.json | 184 ++++++ test/fixtures/golden.ts | 33 +- test/gas.test.ts | 49 +- test/live/simulate.test.ts | 6 +- test/nonce.test.ts | 27 + test/prepareFrameTransaction.test.ts | 46 +- test/signatures.test.ts | 2 +- test/viem.test.ts | 4 +- 49 files changed, 2794 insertions(+), 1444 deletions(-) delete mode 100644 docs/OPEN-ITEMS.md create mode 100644 src/nonce.ts create mode 100644 test/chain.oracle.test.ts create mode 100644 test/fixtures/chain/0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8.json create mode 100644 test/fixtures/chain/0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895.json delete mode 100644 test/fixtures/chain/0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019.json create mode 100644 test/fixtures/chain/0x26ed3e31ef89ea6f6713dafb34e952ca510bb30634d3e8c0ebc5abc9dadc12a3.json create mode 100644 test/fixtures/chain/0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac.json delete mode 100644 test/fixtures/chain/0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a.json create mode 100644 test/fixtures/chain/0x624ad97d70719a3d712610280de5b96f41fbe309e1facd193e06f2e659e1e490.json create mode 100644 test/fixtures/chain/0x68f20f09eda8ac96c999e14db7be262a786d614d20cb80edea1528ce7be19200.json create mode 100644 test/fixtures/chain/0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4.json create mode 100644 test/fixtures/chain/0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0.json create mode 100644 test/fixtures/chain/0x80ca74022f3cddddd54ff57bd0e454b8d41af5ee654f7ff609b09dd75ea55d03.json create mode 100644 test/fixtures/chain/0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12.json delete mode 100644 test/fixtures/chain/0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be.json delete mode 100644 test/fixtures/chain/0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66.json create mode 100644 test/fixtures/chain/0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5.json create mode 100644 test/fixtures/chain/0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f.json delete mode 100644 test/fixtures/chain/0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1.json create mode 100644 test/fixtures/chain/0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6.json create mode 100644 test/fixtures/chain/0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971.json create mode 100644 test/nonce.test.ts diff --git a/.github/ISSUE_TEMPLATE/03-feature.yml b/.github/ISSUE_TEMPLATE/03-feature.yml index 8be36be..11463da 100644 --- a/.github/ISSUE_TEMPLATE/03-feature.yml +++ b/.github/ISSUE_TEMPLATE/03-feature.yml @@ -5,7 +5,7 @@ body: - type: markdown attributes: value: | - Check `docs/OPEN-ITEMS.md` first. Known gaps are already recorded there, and a comment on the matching item is more useful than a new issue. + Check the open issues first; a comment on a matching one is more useful than a new issue. - type: textarea id: need attributes: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fd0f3a0..d511300 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,10 +9,12 @@ exist only to redirect assistants here. `frametx-kit` reads, builds, hashes, signs, prices, simulates and broadcasts EIP-8141 frame transactions as the hegota-testnet chain (chain ID `8141`) accepts them. That envelope is a composition of three draft EIPs that none of them specifies on its own: -EIP-8141 frames, EIP-8250 keyed nonces, EIP-8272 recent-root references. +EIP-8141 frames, EIP-8250 keyed nonces, EIP-8272 recent-root references (which now travel +in a VERIFY frame rather than an envelope field). -It is a deliberate second implementation. The migration is pinned against -[ethrex commit `d587cf9`](https://github.com/lambdaclass/ethrex/tree/d587cf9ff0996315381c4b2784a4d7d499decc0f), +It is a deliberate second implementation. It is pinned against +[ethrex commit `bdfc5d8`](https://github.com/lambdaclass/ethrex/tree/bdfc5d8f2e7f653e620a9901db915f89a87a3d0d), +the `hegota-testnet` build that `rpc1.privacy.ethrex.xyz` serves, and this library exists partly to disagree with it usefully. Every `.rs`, `.py` and `docs/*.md` path cited in this repository's docs and comments — `transaction.rs`, `frame_tx_wire_tests.rs`, `frametx.py` and the rest — is a path in that repository, not in @@ -46,14 +48,13 @@ interchangeable. ```bash bun run test # hermetic, no network -bun run test:live # checks rpc1.frames.ethrex.xyz identity +bun run test:live # checks rpc1.privacy.ethrex.xyz identity bun run typecheck bun run build # tsup -> dist/, dual ESM + CJS with both declaration flavours ``` CI runs `typecheck`, `test`, `build`, and then `@arethetypeswrong/cli --pack .` against -the built package. All four must pass. The public frames RPC currently has no -frame-aware simulation method, so there is no live simulation gate. +the built package. All four must pass. That last check is there because a broken `exports` map or a mismatched `.d.cts` breaks every consumer while leaving the test suite entirely green — the tests import from `src/`, @@ -93,6 +94,11 @@ path is `assertValidFrameTx(tx)` then `encodeFrameTx(tx)`. ## Traps specific to this wire format +- **There is no scalar nonce.** The envelope's second and third fields are `nonceKeys` + (an RLP list, even for the common `[0]`) and `nonceSeq`. Key `0` is only valid alone. + `rlp(nonce_keys) || rlp(nonce_seq)` is billed as calldata, so dropping it misprices + every transaction by at least 48 gas — `test/chain.oracle.test.ts` catches that against + the node's `maxCost`. - **Signature layout is `v || r || s`**, `v` at byte 0 as a bare recovery id (0 or 1), never 27/28. That is the reverse of viem's layout in both directions. A 27/28-encoded signature makes the transaction invalid at consensus, not merely unrelayable. @@ -125,23 +131,23 @@ Historical state is pruned. `ethrex_simulateFrameTransaction` works only against composes `envelope`, `signatures`, `rpc` and `gas` and holds no wire logic of its own. `divergence` depends on `gas`. Nothing imports upward. -## Verification +## Tests -Three oracles, and none of them is self-referential: +Wire-format and gas tests are pinned to something other than the code's own output: -1. The golden byte vector and sig-hash, transcribed from ethrex's Rust tests. -2. Re-encoding the node's decoded JSON must reproduce each transaction's hash. -3. Against live receipts: recovered signer equals the *resolved* signer (often not - `sender` on this chain), our `'chain'` `maxCost` equals the node's, and - `gasUsed = intrinsic + Σ frame execution + Σ frame state`. +- `test/fixtures/golden.ts` — the golden vector and sig-hash from ethrex's + `frame_tx_wire_tests.rs`. +- `test/fixtures/chain/` — real transactions captured from the chain, with their receipts + and the node's simulate answer. `test/chain.oracle.test.ts` re-encodes each one and checks + the hash, the recovered signer, `maxCost` against the node's, and receipt gas. To add one, + run `bunx tsx scripts/capture-fixtures.ts 0x`. Pick transactions without storage + refunds: receipts don't itemize them, so their gas can't be reconstructed. +- `test/gas.test.ts` — expected figures derived by hand, with the derivation in comments. +A round-trip test cannot catch a mistake mirrored in the encoder and the decoder, such as +the `v || r || s` byte order, so pin any new layout to bytes or to captured data as well. Run the commands and read the output before concluding that something works. -The `v || r || s` byte order deserves a note, because a round-trip test cannot pin it: a -mirrored error in the encoder and the decoder survives one. It is pinned instead by -hand-constructed byte-pattern assertions and by oracle 3 recovering signers from real -ethrex-produced signatures. Hold any new layout you add to the same standard. - ## Commits, PR titles and releases This repository releases automatically once it has been released once. Merging to `main` @@ -167,7 +173,7 @@ That means **your commit messages set the version**. Use Pull request titles are validated by CI against that list, with an optional scope from `envelope`, `sighash`, `signatures`, `gas`, `divergence`, `rlp`, `rpc`, `viem`, `fixtures`, `docs`, `deps`, `ci`, `repo` — for example -`fix(envelope): encode the scalar frame nonce`. Subjects start lowercase. +`fix(envelope): reject a zero nonce key beside other keys`. Subjects start lowercase. Do not hand-edit `version` in `package.json` or touch `CHANGELOG.md`; the release commit owns both. A wire-format or gas change is almost always at least a `fix:`, because @@ -181,14 +187,11 @@ someone downstream is encoding bytes with this. published figure, a golden vector, or captured chain data, not to the code's own output. - If you touched anything in **Rules that are not style preferences**, say why in the PR description. Those changes are not refused, but they are argued. -- Files under `test/fixtures/chain` are archived captures from the superseded envelope. - Do not use them as current wire-format oracles. ## Where the rest of the docs are - `docs/DESIGN.md` — the binding design spec. It is the authority on intended behaviour; where the code and the spec disagree, that is a bug in one of them, so say which. -- `docs/OPEN-ITEMS.md` — known gaps and unfinished work. - `README.md` — user-facing API. - `SECURITY.md` — what counts as a vulnerability here, and why a report goes private. - `CODE_OF_CONDUCT.md` — expected conduct in issues and pull requests. diff --git a/README.md b/README.md index 8648e43..8291774 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/JustaLab-co/frametx-kit/badge)](https://scorecard.dev/viewer/?uri=github.com/JustaLab-co/frametx-kit) -TypeScript for the current **EIP-8141 frame transaction** envelope implemented by -the Ethrex frames devnet (chain ID `81410`). +TypeScript for the **EIP-8141 frame transaction** envelope, with EIP-8250 keyed nonces, +as the Ethrex hegota-testnet (chain ID `8141`) accepts it. Decode, build, hash, sign, price, dry-run and broadcast. @@ -26,7 +26,7 @@ import { createPublicClient, http } from 'viem' import { frameActions } from '@jaw.id/frametx-kit/viem' const client = createPublicClient({ - transport: http('https://rpc1.frames.ethrex.xyz'), + transport: http('https://rpc1.privacy.ethrex.xyz'), }).extend(frameActions) const tx = await client.getFrameTransaction({ hash }) @@ -82,11 +82,23 @@ const hash = await client.sendFrameTransaction({ transaction: signed }) const receipt = await client.waitForFrameTransactionReceipt({ hash }) ``` -Passing every check here does not guarantee admission. On this chain the VERIFY prefix must -call `APPROVE`, which a plain EOA sender cannot do, so a broadcastable transaction needs a -sender contract and a funded key — both are yours, not this library's. Dry-run with +Passing every check here does not guarantee admission. The VERIFY prefix must `APPROVE` +payment, and the payer must hold the transaction's `maxCost`, or the frame reverts. A +code-less EOA gets that from the protocol's default code, which `toEoaFrameAccount` targets; +anything else needs its own validating contract. Dry-run with `client.simulateFrameTransaction({ raw })` before spending. +## Nonces are keyed + +A transaction carries `nonceKeys` and one `nonceSeq` (EIP-8250), not a scalar `nonce`. +`[0n]` is the ordinary account nonce and is what `prepareFrameTransaction` uses by +default. Any other key lives in the `NONCE_MANAGER` predeploy; `getFrameNonceSeq` reads +either kind, and `prepareFrameTransaction(account, calls, limits, { nonceKeys: [1n, 2n] })` +selects several, provided they currently sit at the same sequence. + +The first use of a non-zero key costs `KEYED_NONCE_FIRST_USE_STATE_GAS` (97,920) of state +gas, charged against the payment-approving frame's `limits.state`. Budget for it. + ## Two things that will bite you **Frame receipt status is three-valued** — `'failure'`, `'success'`, `'skipped'`. A @@ -99,7 +111,8 @@ can put back on the wire byte-for-byte. ## Gas -`frameTxGas` prices the current EIP-8141 transaction shape. The historical +`frameTxGas` prices the EIP-8141 transaction shape, including EIP-8250's nonce +calldata. The historical `'chain'`, `'pins'`, and `'head'` rule-set names remain accepted as compatibility aliases and currently produce identical results. @@ -142,9 +155,8 @@ are the coverage gaps that need a captured transaction rather than a design deci ## Docs - [`CONTRIBUTING.md`](CONTRIBUTING.md) — architecture, invariants, wire-format traps, how - the verification oracles work + the tests work - [`docs/DESIGN.md`](docs/DESIGN.md) — the binding design spec -- [`docs/OPEN-ITEMS.md`](docs/OPEN-ITEMS.md) — known gaps and unfinished work - [`SECURITY.md`](SECURITY.md) — what counts as a vulnerability here, and how to report it - [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) — expected conduct in issues and pull requests diff --git a/docs/DESIGN.md b/docs/DESIGN.md index a9f595d..9ce8c43 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -1,404 +1,230 @@ # frametx-kit design -**Status:** implemented against the EIP-8141 frame transaction layout used by -Ethrex `v23.0.0-HEAD-d587cf9ff0996315381c4b2784a4d7d499decc0f`. +The binding spec for what the library does. Where the code and this document disagree, +one of them has a bug; say which. -## Scope +**Target:** the EIP-8141 frame transaction with EIP-8250 keyed nonces, as served by the +Ethrex hegota-testnet — chain ID `8141`, `https://rpc1.privacy.ethrex.xyz`, Ethrex +`v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d`. Citations of +`transaction.rs`, `frame_tx_wire_tests.rs` and other `.rs` / `.py` paths refer to that +commit of [`lambdaclass/ethrex`](https://github.com/lambdaclass/ethrex/tree/bdfc5d8f2e7f653e620a9901db915f89a87a3d0d). -The package provides pure TypeScript tools to encode, decode, hash, sign, validate, -price, and format EIP-8141 frame transactions. It also provides a small account -abstraction for constructing account-specific validation and signatures. +## 1. Scope -Transaction submission remains a normal `eth_sendRawTransaction` call. The optional -Ethrex simulation wrapper is retained, but not every frames-devnet RPC exposes that -method. +A TypeScript client for frame transactions: build, validate, encode, decode, hash, sign, +price, simulate, broadcast, and read them back with their frame receipts. It ships a small +account abstraction (`toFrameAccount`, `toEoaFrameAccount`), `prepareFrameTransaction`, +and a viem extension (`client.extend(frameActions)`). It is not a fork of viem core. -## Canonical envelope +Not supported: -A transaction is encoded as `0x06 || rlp(payload)`, where `payload` has exactly seven -fields: +- **Recent-root verifier frames** (EIP-8272). The chain carries recent-root references in a + VERIFY frame targeting `0x…8272`; the library does not build that frame. A transaction + that contains one still decodes, encodes and prices as ordinary frames. +- **Blob sidecars.** `blobVersionedHashes` is carried through the envelope, but a + blob-carrying transaction must be broadcast in the EIP-7594 wrapper, which the library + does not produce. +- **UTXO frames** (mode 5, EIP-8312), inert on this chain. +- **P256 and ARBITRARY signing.** Both are validated, neither is produced by `signFrameTx`. -The same blindness applies to the wire format. From `test/tests/common/frame_tx_wire_tests.rs:3-8`: - -> Nothing in this suite pinned the frame-transaction encoding before: every frame-tx test either -> round-trips (so a changed layout stays symmetric and invisible) or asserts behaviour. Changing -> the envelope therefore passed 1367 tests without a murmur, while every joiner's transaction -> builder would have broken. - -This library is that joiner's transaction builder, written to the published numbers rather than -to ethrex's constants. - -**Primary goal is understanding, verifiably.** Every milestone terminates in a check that fails -loudly when comprehension is wrong: an exact byte string, a field-by-field diff against the -node, a recovered address, a gas figure from a real receipt. - -### Non-goal - -This is not a fork of, or a PR against, viem core. It is a standalone package with a viem -*extension* surface (`client.extend()`), which is how tooling for unsettled EIPs normally -ships. If the EIPs stabilize and viem wants frames in core, this becomes the reference an -eventual PR borrows from. - -## 2. Scope - -### In scope - -Decoding, encoding, `sig_hash`, signing, the gas model, and `ethrex_simulateFrameTransaction`. - -`ethrex_simulateFrameTransaction` takes a **raw hex string** as its first parameter -(`crates/networking/rpc/ethrex.rs:120`) and replays "EIP-8141 static constraints and signature -authentication" (`ethrex.rs:44`). Simulation therefore requires a working encoder and real -signatures — the encoder is on the critical path for the dry run, not just for sending. - -Signing uses a throwaway key that holds no funds. Nothing in either test suite can alter -chain state. - -### Also in scope (added 2026-09-09) - -`eth_sendRawTransaction`, as `sendRawFrameTransaction` in `rpc` and -`frameActions(client).sendFrameTransaction` in `viem`, plus a frame-aware receipt wait. The -node takes the same `0x06 || rlp(body)` bytes it takes for simulation, and the hash it returns -is checked against `keccak256` of those bytes — the mirror of the read-side pin. - -Admission still requires a sender whose VERIFY prefix calls `APPROVE`. A throwaway EOA cannot -do that, so no test in this repository broadcasts: the hermetic suite stubs the node, and the -`FRAMES_LIVE` suite remains read-only plus simulate. A live broadcast gate needs a deployed -sender contract and a funded key; it is recorded in `OPEN-ITEMS.md`, not built. - -### Out of scope - -Key management beyond a throwaway signer. Blob data beyond carrying `blobVersionedHashes` -through the envelope. UTXO frames — mode 5, EIP-8312, inert on this chain because -`utxoFramesTime` is unset. Anything FOCIL or inclusion-list, which is consensus-side and not -transaction-building. Any UI; the inspector is a separate later project that consumes this one. - -## 3. The envelope - -What the chain accepts (`scripts/hegota-testnet/frametx.py:11-17`): +## 2. The envelope +```text +raw = 0x06 || rlp([chain_id, nonce_keys, nonce_seq, sender, frames, signatures, + fees, blob_versioned_hashes]) +fees = [max_priority_fee_per_gas, max_fee_per_gas, max_fee_per_blob_gas] +frame = [mode, flags, target, limits, value, data] +limits = [execution, state] +signature = [scheme, signer, msg, signature] ``` -raw = 0x06 || rlp([chain_id, nonce_keys, nonce_seq, sender, frames, signatures, - fees, blob_hashes, recent_root_references]) -fees = rlp([max_priority_fee, max_fee, max_blob_fee]) -frame = rlp([mode, flags, target_or_empty, limits, value, data]) -limits = rlp([execution, state]) -signature = rlp([scheme, signer, msg, signature_bytes]) -``` +Integers are canonical minimal big-endian RLP: zero is the empty string, never `0x00`. +`limits` is always two elements and `fees` always three, zeros included. An empty +`target` means `tx.sender`; an empty `signer` is resolved per §3. -`nonce` is a scalar u64 account nonce. The older experimental `nonce_keys`, -`nonce_seq`, and `recent_root_references` fields are intentionally unsupported. +Field widths are those ethrex decodes at: `chain_id` and `nonce_seq` are uint64; the nonce +keys, all three fees and `value` are uint256; addresses are exactly 20 bytes. A wider value +would encode to well-formed RLP that the node cannot decode. -A frame is: +**Nonces (EIP-8250).** `nonce_keys` holds 1 to 16 strictly increasing keys; `nonce_seq` +must be below `2**64 - 1` and equal the current sequence of every selected key. Key `0` is +the sender's account nonce and is valid only as the sole key. Any other key is a slot of +the `NONCE_MANAGER` predeploy at `0x0000000000000000000000000000000000008250`: ```text -[mode, flags, target, [execution_gas, state_gas], value, data] +slot(sender, key) = keccak256(left_pad_32(sender) || uint256_be(key)) ``` -A signature is: +An unused key reads zero. There is no RPC for keyed nonces; they are read with +`eth_getStorageAt`. -```text -[scheme, signer, msg, signature] -``` +**Frames.** Modes are 0 DEFAULT, 1 VERIFY, 2 SENDER. `flags` bits 0-1 are the APPROVE scope +(payment `0x1`, execution `0x2`), bit 2 marks an atomic batch, bits 3-7 are reserved and +must be zero. -All integer fields use canonical minimal big-endian RLP. Zero is the empty RLP string, -not the byte `0x00`. +**Structural rules** (`validateFrameTx`, transcribed from `validate_static_constraints`, +`transaction.rs:2667`, checked in the same order): sender non-zero; the nonce rules above; +1 to 64 frames; at most 6 blob hashes, each 32 bytes with the `0x01` KZG version byte, and +`max_fee_per_blob_gas` zero without blobs; signature entries well-formed (§3); only SENDER +frames carry value; `APPROVE_EXECUTION` only with an empty target or `tx.sender`; per-frame +and cumulative limits within `2**63 - 1`; at most one expiry frame (VERIFY to `0x…8141`, +flags 0, 8-byte data, `limits.state` 0); atomic batches never on a VERIFY frame or the +last frame, never followed by a VERIFY frame, and no frame in a batch approving a scope. +Rules that need chain state — nonce values, balances, the validation prefix — are the +node's, and `simulateFrameTransaction` replays them. -## Signature hash +## 3. Signatures -The canonical signature hash is `keccak256(0x06 || rlp(payload))` after replacing the -raw `signature` bytes of every signature whose `msg` is empty with `0x`. Frames and all -other fields remain unchanged. Because the signature commits to the envelope, any wire -format change must update encoding, decoding, hashing, signing, and fixtures together. +**Signature hash.** `keccak256(0x06 || rlp(body))` with the `signature` bytes of every entry +whose `msg` is empty replaced by `0x`. Every other field, frames included, is committed as +encoded. An entry with an explicit 32-byte, non-zero `msg` signs that digest instead and +its bytes stay in the hash. -SECP256K1 signatures use `v || r || s`, with `v` normalized to a bare recovery id `0` -or `1`. P256 and ARBITRARY entries are validated structurally but are not generated by -`signFrameTx`. +**Schemes.** -## Frame accounts +| Scheme | Layout | Canonical form | +|---|---|---| +| 0 ARBITRARY | any bytes | `signer` must be empty | +| 1 SECP256K1 | `v \|\| r \|\| s`, 65 bytes | `v` a bare recovery id `0` or `1`, `0 < r < n`, `0 < s <= n/2` | +| 2 P256 | `r \|\| s \|\| qx \|\| qy`, 128 bytes | `0 < r < n`, `0 < s <= n/2` | -`FrameAccountImplementation` follows viem's implementation/resolved-account split. -It supplies: +The SECP256K1 layout is the reverse of Ethereum's usual `r || s || v`, and a 27/28 `v` +invalidates the transaction at consensus. `signFrameTx` normalizes whatever a signer +returns into this form. -- `getAddress()` -- `getNonce({ blockTag? })` -- `getValidationData({ calls, chainId, nonce })` -- `signFrameTransaction(transaction)` -- an execution strategy +**Signer resolution.** An empty `signer` resolves to `tx.sender` for SECP256K1 and P256. +`signFrameTx` signs every empty-`msg` SECP256K1 entry whose resolved signer is the signing +account, and refuses a signer that cannot sign a raw digest (EIP-191 `signMessage`, JSON-RPC +accounts) rather than produce a signature that recovers to nothing. -`toEoaFrameAccount` implements protocol default-code validation for a code-less EOA. -It reads the scalar nonce with `eth_getTransactionCount`, returns empty validation -calldata, and installs one empty-message SECP256K1 signature entry. +## 4. Gas -## Gas +EIP-8141 constants, written as published figures in `gas.ts` and never derived: -The mandatory gas terms are: +| Constant | Value | +|---|---| +| `FRAME_TX_INTRINSIC_COST` | 12,000 | +| `FRAME_TX_PER_FRAME_COST` | 475 | +| `FRAME_TX_VALUE_COST` | 6,000 | +| Signature verification | ARBITRARY 100, SECP256K1 2,800, P256 6,700 | +| `STANDARD_TOKEN_COST` / `TOTAL_COST_FLOOR_PER_TOKEN` | 4 / 16 | +| `GAS_PER_BLOB` | 131,072 | ```text -12000 -+ 475 * len(frames) -+ signature verification cost -+ 6000 for each value transfer to an explicit non-sender target +mandatory = 12000 + 475 * len(frames) + Σ signature cost + + 6000 per frame moving value to an explicit target other than the sender +billed bytes = each frame's data, each signature's signer, msg and signature, + and nonce_calldata = rlp(nonce_keys) || rlp(nonce_seq) +data cost = 4 per zero byte + 16 per non-zero byte, over the billed bytes +intrinsic = mandatory + data cost +standard = intrinsic + Σ limits.execution + Σ limits.state +floor total = mandatory + 16 * 4 * len(billed bytes) +max_gas = max(standard, floor total + Σ limits.state) +max_cost = max_gas * max_fee_per_gas + len(blobs) * 131072 * blob_base_fee ``` -Signature verification costs are ARBITRARY `100`, SECP256K1 `2800`, and P256 `6700`. -Calldata cost and the calldata floor cover frame `data` and each signature's `signer`, -`msg`, and `signature` bytes. Envelope RLP, nonce, and other scalar fields are not -billed data. State gas is added on top of the calldata-floor branch. - -The historical `'chain'`, `'pins'`, and `'head'` parameters remain accepted by gas -helpers as compatibility aliases; they currently use the same rules. +State gas is added on top of the calldata floor, never absorbed by it. The same holds for +what a receipt reports: +`gasUsed = max(intrinsic + Σ frame gasUsed, floor total) + Σ frame stateGasUsed`, less any +storage refund, which frame receipts do not itemize. + +**Nonce state gas.** Consuming a nonce charges state gas at execution time against the +payment-approving frame's `limits.state`: `KEYED_NONCE_FIRST_USE_STATE_GAS` (97,920) for each +non-zero key whose slot is still zero, and `NEW_ACCOUNT_STATE_GAS` (183,600) for key `[0]` +from a sender that does not exist yet. These are budgeting figures for choosing +`limits.state`, not terms of `frameTxGas`. + +The `'chain'`, `'pins'` and `'head'` rule-set arguments are compatibility aliases and price +identically. + +## 5. Accounts and preparation + +A `FrameAccountImplementation` supplies `getAddress()`, `getNonce({ key?, blockTag? })` (the +current sequence of one nonce key), `getValidationData({ calls, chainId, nonceKeys, +nonceSeq })`, `signFrameTransaction(tx)` and an execution strategy; `toFrameAccount` +resolves it into a `FrameAccount`. + +`toEoaFrameAccount` targets a code-less EOA through the protocol's default code: key `0` +from `eth_getTransactionCount`, other keys from `NONCE_MANAGER` via `eth_getStorageAt`, empty +validation data, and one empty-`msg`, empty-`signer` SECP256K1 entry. + +`prepareFrameTransaction(account, calls, limits, { nonceKeys?, blockTag? })` builds one +VERIFY frame with scope `0x3` followed by one SENDER frame per call, reads fees from the node +(`2 * baseFee + priorityFee`, or `eth_gasPrice` without a base fee), and defaults to +`nonceKeys: [0n]` at `blockTag: 'pending'`. Selected keys must currently sit at the same +sequence, since one `nonceSeq` is matched against all of them. The caller supplies every +frame's limits. + +## 6. RPC surface + +**Transactions.** `eth_getTransactionByHash` returns `type` (`'0x6'`), `chainId`, +`nonceKeys` (a list of quantities), `nonceSeq`, `sender`, `frames` (`mode`, `flags`, `to`, +`gasLimit`, `stateGasLimit`, `value`, `data`), `signatures`, the three fees and +`blobVersionedHashes`. The frame target is `to`, not `target`. + +The node serves no raw bytes: there is no `eth_getRawTransactionByHash`, and the public +endpoint refuses `debug_getRawTransaction`. `getFrameTransaction` therefore re-encodes the +JSON and refuses a result whose re-encoding does not hash to the transaction hash, which +is `keccak256` of exactly the canonical bytes. + +**Receipts.** `eth_getTransactionReceipt` adds `payer` and `frameReceipts[]` with `status`, +`gasUsed`, `stateGasUsed` and `logs`. Frame status is three-valued — `0` failure, `1` +success, `2` skipped — and a skipped frame never executed; it is not a revert. + +**Broadcast.** `eth_sendRawTransaction` takes `0x06 || rlp(body)`; the returned hash is +checked against `keccak256` of the bytes sent, and the request is not retried, so an +accepted broadcast is never re-posted. Admission needs a VERIFY prefix that approves +payment from a payer holding `max_cost`. + +**Simulation.** `ethrex_simulateFrameTransaction(raw, 'latest')` returns `valid`, +`prefixShape` (`SelfVerify`, `DeploySelfVerify`, `OnlyVerifyPay`, `DeployOnlyVerifyPay`), +`payer`, `maxCost`, `violation`, `gasUsed`, `frames[]` (`gasUsed`, `succeeded`), +`executionStatus` and `executionError`. Each field is checked, not cast. `maxCost` is +reported even on structural rejection. `valid: true` is necessary but not sufficient for +admission, and only `latest` works: historical state is pruned. -## Module boundaries - -| Module | Responsibility | -|---|---| -| `types` | Public frame transaction and account-independent types | -| `rlp` | Canonical scalar and offset-aware RLP helpers | -| `envelope` | Encode, decode, and structural validation | -| `sighash` | Canonical signature hash | -| `signatures` | Signature validation, recovery, and signing | -| `accounts` | Generic frame accounts and the direct EOA adapter | -| `gas` | Pure gas and maximum-cost accounting | -| `rpc` | Frame transaction/receipt JSON parsing and optional simulation wrapper | -| `viem` | viem client extension | - -`gas` deliberately does not depend on `envelope`; this keeps accounting independently -testable. `envelope` does not depend on `signatures`; the reverse direction is allowed. - -- **`value_cost`** — the chain charges `TX_VALUE_COST` for every frame with `value > 0`; the - pins charge it only when the frame has a target that is not `tx.sender`. A targetless or - self-targeted value frame is overcharged 6,000 on the live chain. The branch code at - `transaction.rs:2524-2531` already implements the pinned rule, so branch behaviour is - `'pins'` and deployed behaviour is `'chain'`. -- **`SIGPARAM(0x03)`** — the chain returns `len(signature)` for every scheme; the pins permit it - for ARBITRARY entries only and require an exceptional halt otherwise. This one is not a gas - divergence and affects validation-prefix replay rather than the gas model, so it is recorded - but not modelled. - -The hermetic suite pins: - -- Ethrex v23's golden frame transaction bytes -- Ethrex v23's canonical signature hash -- strict canonical-RLP rejection and byte offsets -- signature normalization and recovery -- scalar nonce retrieval for EOAs -- gas-accounting terms -- RPC transaction and receipt formatting - -## 5. Modules - -Ten source files in dependency order. Nothing depends on anything above it. +## 7. Errors and strictness -| Module | Responsibility | Depends on | -|---|---|---| -| `types` | The `FrameTransaction` shape and the `RuleSet` union. | viem types only | -| `errors` | Typed error classes with stable `name`s. | — | -| `rlp` | `rlpUint`, `parseRlpUint`, `byteLength`: minimal-scalar rules viem does not enforce; `walkRlp`, an offset-tracking RLP reader that rejects the non-canonical encodings `fromRlp` accepts. | `errors` | -| `envelope` | `encodeFrameTx`, `decodeFrameTx`, `validateFrameTx`. Pure, no IO. | `rlp`, `errors`, `types`, viem `toRlp` | -| `sighash` | The elision rule plus keccak256. | `envelope` | -| `signatures` | Canonical rules, signer recovery, empty-signer resolution, signing (private key or external account), `assertValidFrameTx`. | `sighash`, `envelope` | -| `gas` | The whole of §4, parameterized by rule set. Pure, no IO. | `rlp`, `errors`, `types` only | -| `divergence` | `compareRuleSets` and the head EIP-8250 state-gas figure. | `gas` | -| `rpc` | Typed `ethrex_simulateFrameTransaction` and `eth_sendRawTransaction` (hash-pinned); frame-aware transaction and receipt formatters. | `errors`, `types` only | -| `viem` | `client.extend(frameActions)`. Thin — no wire logic of its own. | `envelope`, `signatures`, `rpc`, `gas` | -| `fixtures` | The golden vector plus captured real transactions, as JSON. | — | - -`gas` deliberately does not depend on `envelope` or `rpc`, and `rpc` does not depend on -`envelope`. The gas model is the thing being learned, so it stays a pure function testable -offline against a table of cases, and independently wrong or independently right of the -encoder. The small duplication this causes (a `sameAddress` helper, the framing of two -calldata blobs) is deliberate; `CONTRIBUTING.md` lists it among the rules that are not style -preferences. - -Crypto is borrowed, not written: viem supplies RLP, keccak256 and secp256k1. The envelope is -hand-rolled because it is the object of study; ECDSA is not. - -### The node's JSON surface - -`eth_getTransactionByHash` returns a decoded frame transaction with fields `type`, `chainId`, -`nonceKeys`, `nonceSeq`, `sender`, `frames`, `signatures`, `maxPriorityFeePerGas`, -`maxFeePerGas`, `maxFeePerBlobGas`, `blobVersionedHashes`, `recentRootReferences` -(`transaction.rs:4109-4155`). - -`eth_getTransactionReceipt` adds `payer` and `frameReceipts[]`, each entry carrying `status`, -`gasUsed`, `stateGasUsed` and `logs` (`crates/networking/rpc/types/receipt.rs:33-47`). -**`status` is three-valued: 0 failure, 1 success, 2 skipped** (atomic-batch failure). Skipped -frames never executed and their gas was refunded, so collapsing 2 into "reverted" is wrong — -this is precisely the defect in viem PR 4486 (§10). - -`ethrex_simulateFrameTransaction` returns `valid`, `prefixShape`, `payer`, `maxCost`, -`violation`, `gasUsed`, `frames[]` (`gasUsed`, `succeeded`), `executionStatus` and -`executionError` (`crates/networking/rpc/ethrex.rs:41-95`). `prefixShape` is one of -`SelfVerify`, `DeploySelfVerify`, `OnlyVerifyPay`, `DeployOnlyVerifyPay`. Its `valid` is -necessary but not sufficient for admission: the gates shared with every other transaction type, -and the per-sender pending-frame rule, are not replayed. `maxCost` is reported on every path, -including structural rejection, because it is a pure function of the fields. Only `latest` is -usable as the block parameter: the public node has pruned historical state. - -**No raw transaction bytes are served.** ethrex has no `eth_getRawTransactionByHash` -(`Method not found`), and `debug_getRawTransaction` — present in the binary, and returning the -canonical `0x06…` bytes — is refused by `rpc1.privacy.ethrex.xyz` (`not available on this -endpoint`) despite `docs/hegota-testnet-spec.md:74` listing `debug` as exposed. The raw bytes of -a live transaction can only be *reconstructed* from its JSON. This is not a weakness once -noticed: the transaction hash is `keccak256(0x06 || rlp(body))`, so a reconstruction that -reproduces the hash is the bytes, and a reconstruction that does not is a finding. - -## 6. Verification — three oracles - -All three get wired. Each catches what the others miss. - -**Oracle 1 — the golden vector.** Offline and exact. From -`test/tests/common/frame_tx_wire_tests.rs:67-69`: +Every error is a typed class with a stable `name`, following viem's conventions: +`FrameEncodeError`, `FrameDecodeError` and `FrameRlpError`. -``` -GOLDEN_RLP = f8b301c1800794000000000000000000000000000000000000abcdeccc010380c48252088080821122de0280940000000000000000000000000000000000001234c4829c40808080f85cf85a0194000000000000000000000000000000000000abcd80b8410101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101cc843b9aca008506fc23ac0080c0c0 -GOLDEN_SIG_HASH = 0xd4df51143828c0338882dbd10c3308f3569972fe1928a7b5040ee18057920510 -``` +**Encode is not validation.** `encodeFrameTx` encodes whatever it is given, because +`frameTxSigHash` is defined over a re-encoding. The strict path is `assertValidFrameTx(tx)` +(the §2 rules plus §3 canonicality) and then `encodeFrameTx(tx)`; +`frameActions(client).sendFrameTransaction` walks it for you. -The transaction it encodes (`frame_tx_wire_tests.rs:22-60`): `chain_id` 1, `nonce_keys` `[0]`, -`nonce_seq` 7, `sender` `0x…abcd`, two frames — a targetless VERIFY with `flags` 3, execution -limit `0x5208`, no state limit, data `0x1122`; then a SENDER frame targeting `0x…1234`, -execution limit `0x9c40`, empty data — one SECP256K1 signature with signer `0x…abcd`, empty -`msg`, 65 bytes of `0x01`, `max_priority_fee_per_gas` `0x3b9aca00`, `max_fee_per_gas` -`0x6fc23ac00`, no blobs, no recent-root references. - -Catches absolute byte layout. Hermetic; runs in CI. - -**Oracle 2 — the transaction hash over a re-encoding of the node's JSON.** Parse the node's -decoded JSON for a live transaction, re-encode it, and assert -`keccak256(encodeFrameTx(parseRpcFrameTransaction(json))) === json.hash`. The hash is keccak256 -of the canonical bytes, so this pins the absolute layout of a real transaction as tightly as a -raw-bytes diff would — and more tightly than a field-by-field diff of two decoders, which cannot -see a mis-nesting both make. Then, decoder side, `decodeFrameTx(bytes)` must equal the parsed -JSON. Needs no golden vector and no raw-bytes RPC. Verified on 2026-09-06 against three live -transactions, one carrying two keyed nonces and a recent-root reference. - -Round-trip (`decode(encode(x)) === x`) is *also* implemented but never trusted alone: it is -symmetric-blind, which is exactly how an envelope change passed 1367 ethrex tests. - -**Oracle 3 — semantic cross-checks against the chain.** For every captured transaction: - -- Recovered signer equals the *resolved* signer — `signer`, or `sender` when `signer` is empty - — for SECP256K1 entries with an empty `msg`. On this chain the two usually differ: the - shielded pool's spends have the pool contract as `sender` and an EOA as `signer`. -- Computed `max_cost` under `'chain'` matches `maxCost` from - `ethrex_simulateFrameTransaction` over our re-encoding — the node's figure computed from - our bytes, and a pure function of the fields, so it is hermetic once captured. -- The receipt reconciles exactly: `gasUsed = frame_tx_intrinsic_gas + Σ frameReceipts[].gasUsed - + Σ frameReceipts[].stateGasUsed`. Observed with a zero delta on every live transaction - checked. -- The node gets past signature authentication over our re-encoding: `violation` from - `ethrex_simulateFrameTransaction` is never "frame signature list does not authenticate the - sender". That is our `sig_hash` agreeing with the node's over a real signature. - -Oracle 3 is where the rule sets pay off. A `'pins'` model that disagrees with a live receipt by -exactly 6,000 on a targetless value frame has independently reproduced divergence-ledger row -3.8 rather than read about it. A disagreement that is *not* 6,000 and *not* explainable is a -finding worth reporting. +**Decode is lenient about shapes, strict about bytes.** `decodeFrameTx` accepts any +structure the chain accepted, but walks the body with `walkRlp` rather than viem's +`fromRlp`, so it rejects what ethrex rejects at RLP decode: a long-form single-byte scalar, +a non-minimal length or leading zero, a non-byte-aligned string, nesting deeper than 1024. +Malformed RLP produces a typed error carrying the byte offset at which parsing failed, +counting the `0x06` type byte as byte 0. A re-encoding is also compared against the input +as a backstop. Strict decode is `decodeFrameTx(raw)` followed by `assertValidFrameTx(tx)`. -## 7. Errors and strictness +## 8. Modules -Encoding and inspecting want opposite defaults. - -**Encode is strict.** Reserved flag bits must be zero; signatures must be canonical; mode -restricted to 0, 1, 2; structural limits enforced; `value` non-zero only on SENDER frames. -Violations throw a typed error before any bytes are produced. - -**Decode is lenient.** It must decode anything the chain accepted, including shapes that look -wrong, and surface them as findings rather than throwing — a surveyor that dies on one odd -transaction cannot survey. Strict decode is not a flag: it is `decodeFrameTx(raw)` followed by -`assertValidFrameTx(tx)`, so the strict rules live in one place instead of two. Malformed RLP -produces a typed error carrying **the byte offset at which parsing failed**: `decodeFrameTx` -walks the body with `walkRlp` (in `rlp.ts`), a small offset-tracking RLP reader, rather than -viem's `fromRlp`, which exposes no offset. The offset is into the full transaction, counting -the `0x06` type byte as byte 0, so it indexes straight into the hex the caller passed. -`walkRlp` also rejects **non-canonical RLP** — a long-form encoding of a single-byte scalar, -say — at the offending byte, because ethrex rejects those bytes at RLP decode while `fromRlp` -silently canonicalizes them; that is a well-formedness rule about the bytes, not a structural -rule about the transaction, so it does not make decode any less lenient about the shapes the -chain actually accepted. It also keeps the two guards `fromRlp` applied and a bare -`hexToBytes` does not: a non-byte-aligned string is rejected rather than nibble-padded, and -nesting is capped at 1024 so pathological input throws a typed error instead of exhausting -the stack. A re-encoding is still compared against the input as a cheap independent -backstop; that comparison carries no offset, since a mismatch could be anywhere. - -Every error is a typed class with a stable `name`, following viem's error conventions so the -extension surface composes with viem's own error handling. - -## 8. Testing and repo mechanics - -TypeScript in strict mode; Vitest, which is viem's own runner; the package exports a root entry -point and a `./viem` entry point (`@jaw.id/frametx-kit/viem`) for the extension surface. - -Two suites. The **default suite is hermetic**, running entirely off checked-in fixtures, so CI -never depends on a testnet staying up. The **`test:live` suite** is opt-in behind an env var -and hits `https://rpc1.privacy.ethrex.xyz`. - -Fixtures are captured by a script so they can be refreshed. They will need refreshing: both -divergences are "fixed on the branch, on the chain at the next relaunch", and a re-genesis -wipes the chain — this one is already the third genesis, the previous chain having ended at -block 313,106. Fixtures record the `web3_clientVersion` and genesis hash they were captured -against, so a stale fixture set is detectable rather than silently wrong. - -Development is test-first throughout: each milestone's oracle is written before the code that -satisfies it. - -## 9. Milestones - -Each terminates in a check that fails loudly when understanding is wrong. - -1. **Decode.** Raw hex to structured object. Gate: decode `GOLDEN_RLP` to the documented - transaction. (Oracle 2 over live transactions needs the encoder and the RPC parser, so it - lands with milestones 2 and 5's RPC formatters, not here.) -2. **Encode and `sig_hash`.** Gate: reproduce `GOLDEN_RLP` and `GOLDEN_SIG_HASH` byte-for-byte - from a hand-built transaction. Passing this means the envelope is understood. -3. **Signatures.** Canonical rules, recovery, P256 normalization. Gate: recovered signer equals - `sender` for every captured SECP256K1 transaction. -4. **Gas model with rule sets.** Gate: `'chain'` agrees with live receipts and with simulated - `maxCost`; `'pins'` disagrees only where the ledger says it should, by the amount it says. -5. **Simulate.** Typed `ethrex_simulateFrameTransaction`. Gate: a hand-built, throwaway-signed - SelfVerify transaction (a VERIFY frame with scope `0x3` — a SENDER frame opens no recognized - prefix) returns `prefixShape: 'SelfVerify'` and a `maxCost` equal to ours. It cannot return - `valid: true`: the shape is derived after signature authentication, so a recognized shape - proves decoding and authentication, but a throwaway EOA has no code to call `APPROVE`, and - the node answers `valid: false, violation: "validation prefix frame reverted"`. A `valid: - true` gate needs a deployed sender contract, which is an open item rather than a milestone. -6. **viem extension.** Gate: `client.extend(frameActions)` reads a frame transaction and its - three-valued frame receipts through ordinary viem ergonomics. -7. **Broadcast.** `sendFrameTransaction` and `waitForFrameTransactionReceipt`. Gate: hermetic - only — the bytes posted equal `encodeFrameTx` of the input, a hash that is not - `keccak256` of those bytes is refused, and a skipped frame survives the receipt wait as - `'skipped'`. The live gate (a `valid: true` simulation followed by an accepted broadcast) - needs a deployed sender contract and is an open item. - -## 10. Candidate contributions - -Byproducts, each independently useful and none required by the milestones. Recorded here so -they are not lost; whether to file them is the author's call. - -1. **viem PR 4486 — frame receipt status collapse.** `transactionReceipt.ts` maps - `status: fr.status === '0x1' ? 'success' : 'reverted'`, silently reporting every skipped - frame as reverted. Status is three-valued (`receipt.rs:35`; semantics merged upstream as - EIPs PR-12061). The same formatter also drops `stateGasUsed`. -2. **`docs/eip-8141.md:78` — stale wire layout.** Documents the pre-composition envelope; a - builder written from it produces bytes the chain rejects. -3. **`docs/eip-8141.md:362` — stale intrinsic and incomplete formulas.** Says 15000; the - published figure and the code are 12000. The gas formulas there omit `state_gas_limit` and - `recent_root_reference_intrinsic_gas`. -4. **Whatever Oracle 3 turns up** that the divergence ledger does not already explain. -5. **`docs/hegota-testnet-spec.md:74` — `debug` listed as served, but refused.** The endpoint - answers `debug_getRawTransaction` with `-32601 "not available on this endpoint"`. Either the - page or the proxy is wrong, and a joiner building on raw-bytes retrieval from that page is - misled. The same report can ask for `eth_getRawTransactionByHash`, which every other client - serves. - -## 11. Open questions - -None blocking. Two to settle during implementation, both low-stakes and reversible: - -- Whether to depend on viem directly or on `ox`, the lower-level library viem builds on. viem - is assumed here because the extension surface requires it anyway; `ox` would only reduce the - core module's dependency weight, and the decision can be deferred until the core is green. -- Whether the `'head'` EIP-8272 envelope change ever needs a serializer. `toHeadShape` covers - pricing without one; bytes are only justified once that draft stops moving. +| Module | Responsibility | Depends on | +|---|---|---| +| `types`, `errors` | The transaction shape; typed errors | — | +| `rlp` | Minimal scalars, `walkRlp` | `errors` | +| `envelope` | `encodeFrameTx`, `decodeFrameTx`, `validateFrameTx` | `rlp`, `errors`, `types` | +| `sighash` | `frameTxSigHash` | `envelope` | +| `signatures` | Canonicality, recovery, signer resolution, `signFrameTx`, `assertValidFrameTx` | `sighash`, `envelope` | +| `gas` | `frameTxGas`, `frameTxMaxCost`, `nonceCalldata` | `rlp`, `types` | +| `divergence` | `compareRuleSets` | `gas` | +| `nonce` | `NONCE_MANAGER`, `keyedNonceSlot`, `getFrameNonceSeq` | `errors` | +| `rpc` | Transaction and receipt parsing, simulate, broadcast | `errors`, `types` | +| `accounts`, `prepareFrameTransaction`, `signFrameTransaction` | Frame accounts and transaction preparation | `envelope`, `signatures`, `nonce` | +| `viem` | `frameActions`, no wire logic of its own | `envelope`, `signatures`, `rpc`, `gas` | + +Nothing imports upward. `gas` does not import `envelope`, so the gas model stays +independently testable; `envelope` does not import `signatures`. + +## 9. Testing + +The default suite is hermetic. It pins the golden vector and sig-hash transcribed from +`frame_tx_wire_tests.rs:69-70`, the published gas figures, and a set of captured chain +transactions checked end to end (hash, signer, `maxCost`, receipt gas). The `test:live` +suite, gated on `FRAMES_LIVE=1`, is read-only. Broadcasting is checked manually with +`scripts/send-eth-eoa.ts`. `CONTRIBUTING.md` covers running, refreshing and extending the +tests. diff --git a/docs/OPEN-ITEMS.md b/docs/OPEN-ITEMS.md deleted file mode 100644 index 4a97fdf..0000000 --- a/docs/OPEN-ITEMS.md +++ /dev/null @@ -1,44 +0,0 @@ -# frametx-kit open items - -Known gaps and unfinished work, current as of ethrex `hegota-testnet` @ `19c065fa8`. -The invariants and wire-format traps are in `../CONTRIBUTING.md`; `DESIGN.md` is the -binding spec. - -Nothing here is blocking. In rough priority order: - -- **Broadcast has no live gate.** `sendFrameTransaction` is verified against a stubbed node - only. A live test would need a sender contract deployed on hegota-testnet whose VERIFY - prefix calls `APPROVE`, a key funded from `faucet.privacy.ethrex.xyz`, and a second opt-in - env var distinct from `FRAMES_LIVE`, because that suite is documented as unable to alter - chain state. Until then the strongest evidence that our bytes are admissible is - `ethrex_simulateFrameTransaction` returning a recognized `prefixShape`, which the existing - live suite already checks. -- **The head reference price is a floor, not a budget.** `toHeadShape` rewrites a - reference-carrying transaction into its EIP-8272 head equivalent, so the divergence - survey prices one instead of refusing it. The synthetic "recent root verifier frame" - claims zero execution and state. `limits.state == 0` is normative (upstream - `eip-8272.md` @ `824cbc0b0`, §Recent root verifier frame); `limits.execution` is the one - figure the spec does not pin — it falls out of the `STATICCALL` and one `SLOAD` per tuple - — so every limit-derived term of the head price is a lower bound. `frameTxGas(tx, 'head')` - still refuses a reference-carrying transaction for that reason: comparing against a - floor is sound, budgeting against one is not. The tuple packing - (`source_id: bytes32 || slot: uint64_be || root: bytes32`, 72 bytes, - `RECENT_ROOT_TUPLE_BYTES`) is confirmed against that spec — order and widths both. -- **Fixture coverage gaps that are facts about the chain, not omissions:** no captured - transaction uses signature scheme 2 (P256) or a targetless value-carrying frame — the - latter is the only shape where `'chain'` and `'pins'` diverge, so the divergence survey's - positive branch is exercised only offline. Scheme 0 (ARBITRARY) and frame mode 0 *are* - covered, both by - `test/fixtures/chain/0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be.json` - (block 42,241): two mode-0 frames and a single 24,820-byte ARBITRARY signature. - Re-run `bunx tsx scripts/capture-fixtures.ts` when the chain produces a transaction - covering one of the remaining gaps. -- **Fixtures expire.** They record the client version and genesis hash they were taken - against, and the oracle suite asserts the genesis hash, so a re-genesis fails loudly - rather than silently. This chain is already on its third genesis. -- **Two upstream documentation bugs worth reporting.** ethrex's - `docs/eip-8141.md` still documents the pre-composition wire layout and an intrinsic of - 15000 (the published figure and the code are both 12000), and - ethrex's `docs/hegota-testnet-spec.md` lists `debug` among the namespaces served by - `rpc1.privacy.ethrex.xyz` while the endpoint actually refuses `debug_getRawTransaction`. - Anyone implementing from either document would be misled. diff --git a/package.json b/package.json index 84c3bc7..3408d45 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,8 @@ "ethereum", "eip-8141", "frame-transaction", - "frames-devnet", + "eip-8250", + "hegota-testnet", "ethrex", "viem", "rlp" diff --git a/scripts/capture-fixtures.ts b/scripts/capture-fixtures.ts index b403060..bc484c1 100644 --- a/scripts/capture-fixtures.ts +++ b/scripts/capture-fixtures.ts @@ -1,8 +1,5 @@ /** - * Legacy fixture capture script for the retired chain-8141 envelope. - * - * Do not use this to produce current wire-format fixtures. The frames-devnet RPC - * does not expose the simulation method this historical capture flow requires. + * Capture live hegota-testnet frame transactions as test fixtures. * * The public endpoint serves no raw transaction bytes — ethrex has no * `eth_getRawTransactionByHash`, and `debug_getRawTransaction` is refused by @@ -12,14 +9,14 @@ * raw bytes are re-derived by the tests and pinned by the transaction hash, * which is keccak256 of exactly those bytes. * - * Records the client version and genesis hash each capture was taken against, so - * a stale fixture set after a re-genesis is detectable rather than silently - * wrong. This chain is already on its third genesis; both known divergences are - * fixed on the branch and land on the chain at the next relaunch. + * Each capture also records the client version and genesis hash it was taken + * against, as provenance. * - * Usage: bunx tsx scripts/capture-fixtures.ts [from-block] [to-block] - * Defaults to the last 500 blocks. Frame transactions are sparse; on the third - * genesis, blocks 2782, 2787 and 2792 are known to carry one each. + * Usage: + * bunx tsx scripts/capture-fixtures.ts [from-block] [to-block] + * Defaults to the last 500 blocks. Frame transactions are sparse. + * bunx tsx scripts/capture-fixtures.ts 0x [0x ...] + * Capture the named transactions. */ import { mkdirSync, writeFileSync } from 'node:fs' import { type Hex, keccak256 } from 'viem' @@ -56,62 +53,82 @@ async function main() { const genesis = await rpc<{ hash: Hex }>('eth_getBlockByNumber', ['0x0', false]) const head = BigInt(await rpc('eth_blockNumber', [])) - const to = process.argv[3] ? BigInt(process.argv[3]) : head - const from = process.argv[2] ? BigInt(process.argv[2]) : to > 500n ? to - 500n : 0n - mkdirSync(OUT_DIR, { recursive: true }) const captured: string[] = [] - for (let n = from; n <= to; n++) { - const block = await rpc<{ transactions: HydratedTx[] }>('eth_getBlockByNumber', [ - `0x${n.toString(16)}`, - true, - ]) - for (const tx of block.transactions) { - if (tx.type !== '0x6') continue // `{:#x}` of 6 is one hex digit, not two + const args = process.argv.slice(2) + const byHash = args.length > 0 && args.every((a) => /^0x[0-9a-fA-F]{64}$/.test(a)) + const to = !byHash && args[1] ? BigInt(args[1]) : head + const from = !byHash && args[0] ? BigInt(args[0]) : to > 500n ? to - 500n : 0n + + async function* frameTransactions(): AsyncGenerator<{ n: bigint; tx: HydratedTx }> { + if (byHash) { + for (const hash of args) { + const tx = await rpc<(HydratedTx & { blockNumber: Hex }) | null>( + 'eth_getTransactionByHash', + [hash], + ) + if (tx === null) throw new Error(`${hash}: not found`) + yield { n: BigInt(tx.blockNumber), tx } + } + return + } + for (let n = from; n <= to; n++) { + const block = await rpc<{ transactions: HydratedTx[] }>('eth_getBlockByNumber', [ + `0x${n.toString(16)}`, + true, + ]) + for (const tx of block.transactions) yield { n, tx } + } + } + + for await (const { n, tx } of frameTransactions()) { + if (tx.type !== '0x6') continue // `{:#x}` of 6 is one hex digit, not two - const receipt = await rpc('eth_getTransactionReceipt', [tx.hash]) + const receipt = await rpc('eth_getTransactionReceipt', [tx.hash]) - // Our re-encoding. Checked against the hash here only to WARN: a mismatch is - // a finding for Oracle 2 in test/oracles.test.ts, which fails loudly, so - // the fixture is still written. `simulate` over wrong bytes is then noise. - const raw = encodeFrameTx(parseRpcFrameTransaction(tx)) - if (keccak256(raw) !== tx.hash) - console.warn(`block ${n} ${tx.hash}: re-encoding does not reproduce the hash`) + // Our re-encoding. Checked against the hash here only to WARN: a mismatch is + // a finding for Oracle 2 in test/oracles.test.ts, which fails loudly, so + // the fixture is still written. `simulate` over wrong bytes is then noise. + const raw = encodeFrameTx(parseRpcFrameTransaction(tx)) + if (keccak256(raw) !== tx.hash) + console.warn(`block ${n} ${tx.hash}: re-encoding does not reproduce the hash`) - // The node's verdict on our bytes. `maxCost` is a pure function of the - // fields (hermetic oracle); `violation` records how far the node got — - // anything past "does not authenticate the sender" means our sig_hash - // over a real signature matched. Only `latest` is available: state is pruned. - const simulate = await rpc('ethrex_simulateFrameTransaction', [raw, 'latest']) + // The node's verdict on our bytes. `maxCost` is a pure function of the + // fields (hermetic oracle); `violation` records how far the node got — + // anything past "does not authenticate the sender" means our sig_hash + // over a real signature matched. Only `latest` is available: state is pruned. + const simulate = await rpc('ethrex_simulateFrameTransaction', [raw, 'latest']) - writeFileSync( - new URL(`${tx.hash}.json`, OUT_DIR), - `${JSON.stringify( - { - meta: { - clientVersion, - genesisHash: genesis.hash, - capturedAt: new Date().toISOString(), - block: n.toString(), - rawSource: 'reconstructed from JSON; pinned by hash in test/oracles.test.ts', - }, - tx, - receipt, - simulate, + writeFileSync( + new URL(`${tx.hash}.json`, OUT_DIR), + `${JSON.stringify( + { + meta: { + clientVersion, + genesisHash: genesis.hash, + capturedAt: new Date().toISOString(), + block: n.toString(), + rawSource: 'reconstructed from JSON; pinned by hash in test/oracles.test.ts', }, - null, - 2, - )}\n`, - ) - captured.push(tx.hash) - console.log(`block ${n}: ${tx.hash}`) - } + tx, + receipt, + simulate, + }, + null, + 2, + )}\n`, + ) + captured.push(tx.hash) + console.log(`block ${n}: ${tx.hash}`) } - console.log(`captured ${captured.length} frame transactions from blocks ${from}..${to}`) - if (captured.length === 0) - console.warn('no type-0x06 transactions found — widen the range, or try `2780 2800`') + console.log( + byHash + ? `captured ${captured.length} frame transactions by hash` + : `captured ${captured.length} frame transactions from blocks ${from}..${to}`, + ) + if (captured.length === 0) console.warn('no type-0x06 transactions found — widen the range') } main().catch((error) => { diff --git a/scripts/send-eth-eoa.ts b/scripts/send-eth-eoa.ts index 7504919..7332c95 100644 --- a/scripts/send-eth-eoa.ts +++ b/scripts/send-eth-eoa.ts @@ -6,8 +6,11 @@ * * Optional: * AMOUNT_ETH=0.001 - * RPC_URL=https://rpc1.frames.ethrex.xyz - * DRY_RUN=1 + * RPC_URL=https://rpc1.privacy.ethrex.xyz + * DRY_RUN=1 simulate only; do not broadcast + * + * The transaction is always simulated with `ethrex_simulateFrameTransaction` + * first, and is not broadcast unless the node reports it valid. */ import { createPublicClient, @@ -28,18 +31,19 @@ import { frameTxMaxCost, parseRpcFrameReceipt, prepareFrameTransaction, + simulateFrameTransaction, signFrameTransaction, toEoaFrameAccount, type FrameRpcClient, type RpcFrameReceiptJson, } from '../src/index.js' -const CHAIN_ID = 81410n -const DEFAULT_RPC_URL = 'https://rpc1.frames.ethrex.xyz' +const CHAIN_ID = 8141n +const DEFAULT_RPC_URL = 'https://rpc1.privacy.ethrex.xyz' const RECEIPT_TIMEOUT_MS = 120_000 -const framesDevnet = defineChain({ +const hegotaTestnet = defineChain({ id: Number(CHAIN_ID), - name: 'Ethrex Frames Devnet', + name: 'Ethrex Hegota Testnet', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: [DEFAULT_RPC_URL] } }, }) @@ -86,7 +90,7 @@ async function main(): Promise { const dryRun = process.env.DRY_RUN === '1' const client = createPublicClient({ - chain: framesDevnet, + chain: hegotaTestnet, transport: http(rpcUrl), }) const account = await toEoaFrameAccount({ client, owner }) @@ -112,11 +116,17 @@ async function main(): Promise { console.log(`sender: ${account.address}`) console.log(`recipient: ${recipient}`) console.log(`amount: ${formatEther(amount)} ETH`) - console.log(`nonce: ${signed.nonce}`) + console.log(`nonce keys: [${signed.nonceKeys.join(', ')}] at seq ${signed.nonceSeq}`) console.log(`max gas: ${gas.maxGas}`) console.log(`max gas cost: ${formatEther(maxGasCost)} ETH`) console.log(`local hash: ${transactionHash}`) + const simulation = await simulateFrameTransaction(client, raw) + console.log(`simulated: valid=${simulation.valid} prefix=${simulation.prefixShape}`) + console.log(` gas used: ${simulation.gasUsed}`) + console.log(` max cost: ${simulation.maxCost} (local ${maxGasCost})`) + if (!simulation.valid) throw new Error(`simulation rejected the transaction: ${simulation.violation}`) + if (dryRun) { console.log('DRY_RUN=1, so the valid transaction was not broadcast') return diff --git a/src/accounts/toEoaFrameAccount.ts b/src/accounts/toEoaFrameAccount.ts index 4499a3e..98b7a19 100644 --- a/src/accounts/toEoaFrameAccount.ts +++ b/src/accounts/toEoaFrameAccount.ts @@ -12,13 +12,10 @@ import { } from 'viem' import { signFrameTx } from '../signatures.js' import { FrameEncodeError } from '../errors.js' +import { getFrameNonceSeq } from '../nonce.js' import type { FrameTransaction } from '../types.js' import { toFrameAccount } from './toFrameAccount.js' -import type { - FrameAccount, - FrameAccountImplementation, - GetFrameNonceParameters, -} from './types.js' +import type { FrameAccount, FrameAccountImplementation } from './types.js' export type EoaFrameOwner = Account & { address: Address @@ -51,25 +48,6 @@ export type ToEoaFrameAccountReturnType< extend extends object = object, > = FrameAccount> -async function getEoaFrameNonce< - chain extends Chain | undefined, - owner extends EoaFrameOwner, ->( - client: Client< - Transport, - chain, - JsonRpcAccount | LocalAccount | undefined - >, - sender: Address, - parameters: GetFrameNonceParameters, -): Promise { - const nonce = await client.request({ - method: 'eth_getTransactionCount', - params: [sender, parameters.blockTag ?? 'pending'], - }) - return BigInt(nonce) -} - /** Create a direct-execution frame account backed by an ordinary EOA. */ export async function toEoaFrameAccount< chain extends Chain | undefined, @@ -89,7 +67,7 @@ export async function toEoaFrameAccount< return owner.address }, async getNonce(nonceParameters = {}) { - return getEoaFrameNonce(client, owner.address, nonceParameters) + return getFrameNonceSeq(client, { address: owner.address, ...nonceParameters }) }, async getValidationData() { return '0x' diff --git a/src/accounts/types.ts b/src/accounts/types.ts index 33be036..c45c4cd 100644 --- a/src/accounts/types.ts +++ b/src/accounts/types.ts @@ -18,13 +18,16 @@ export type FrameCall = { } export type GetFrameNonceParameters = { + /** EIP-8250 nonce key. Defaults to `0n`, the account nonce. */ + key?: bigint | undefined blockTag?: BlockTag | undefined } export type GetValidationDataParameters = { calls: readonly FrameCall[] chainId: bigint - nonce: bigint + nonceKeys: readonly bigint[] + nonceSeq: bigint } export type DirectFrameExecution = { @@ -51,7 +54,7 @@ export type FrameAccountImplementation< extend?: extend | undefined /** Resolve the sender address represented by this account. */ getAddress: () => Promise
- /** Resolve the sender's scalar account nonce. */ + /** Resolve the current EIP-8250 sequence of one of the sender's nonce keys. */ getNonce: (parameters?: GetFrameNonceParameters) => Promise /** Produce calldata for the account's EIP-8141 validation frame. */ getValidationData: (parameters: GetValidationDataParameters) => Promise diff --git a/src/divergence.ts b/src/divergence.ts index 68d3612..1d935bd 100644 --- a/src/divergence.ts +++ b/src/divergence.ts @@ -17,8 +17,8 @@ export type GasDivergence = { * on live chain data that the divergence ledger does not already explain is a * finding worth reporting. * - * The rule-set names are compatibility aliases now that the current envelope - * has removed the keyed-nonce and recent-root extensions. + * The rule-set names are compatibility aliases: hegota-testnet prices every + * frame transaction under one set of rules, keyed nonces included. */ export function compareRuleSets( tx: FrameTransaction, diff --git a/src/envelope.ts b/src/envelope.ts index 0fd4886..ba89530 100644 --- a/src/envelope.ts +++ b/src/envelope.ts @@ -35,7 +35,8 @@ export function encodeFrameTxBody(tx: FrameTransaction): Hex { return toRlp( [ rlpUint(tx.chainId), - rlpUint(tx.nonce), + tx.nonceKeys.map(rlpUint), + rlpUint(tx.nonceSeq), tx.sender, tx.frames.map(encodeFrame), tx.signatures.map(encodeSignature), @@ -143,14 +144,15 @@ function decodeFields( ): FrameTransaction { return { chainId: asUint(fields[0]!, 'chainId'), - nonce: asUint(fields[1]!, 'nonce'), + nonceKeys: asList(fields[1]!, 'nonceKeys').map((k) => asUint(k, 'nonceKeys[]')), + nonceSeq: asUint(fields[2]!, 'nonceSeq'), sender, - frames: asList(fields[3]!, 'frames').map(decodeFrame), - signatures: asList(fields[4]!, 'signatures').map(decodeSignature), + frames: asList(fields[4]!, 'frames').map(decodeFrame), + signatures: asList(fields[5]!, 'signatures').map(decodeSignature), maxPriorityFeePerGas: asUint(fees[0]!, 'maxPriorityFeePerGas'), maxFeePerGas: asUint(fees[1]!, 'maxFeePerGas'), maxFeePerBlobGas: asUint(fees[2]!, 'maxFeePerBlobGas'), - blobVersionedHashes: asList(fields[6]!, 'blobVersionedHashes').map((h) => + blobVersionedHashes: asList(fields[7]!, 'blobVersionedHashes').map((h) => asHex(h, 'blobVersionedHashes[]'), ), } @@ -193,22 +195,22 @@ export function decodeFrameTx(raw: Hex): FrameTransaction { } const fields = asList(tree, 'envelope') - if (fields.length !== 7) + if (fields.length !== 8) throw new FrameDecodeError( - `envelope must have 7 fields, got ${fields.length}`, + `envelope must have 8 fields, got ${fields.length}`, tree.start, ) - const fees = asList(fields[5]!, 'fees') + const fees = asList(fields[6]!, 'fees') if (fees.length !== 3) throw new FrameDecodeError( `fees must have 3 fields, got ${fees.length}`, - fields[5]!.start, + fields[6]!.start, ) - const sender = asAddressOrNull(fields[2]!, 'sender') + const sender = asAddressOrNull(fields[3]!, 'sender') if (sender === null) - throw new FrameDecodeError('sender may not be empty', fields[2]!.start) + throw new FrameDecodeError('sender may not be empty', fields[3]!.start) const tx = decodeFields(fields, fees, sender) @@ -226,6 +228,8 @@ export function decodeFrameTx(raw: Hex): FrameTransaction { } export const MAX_FRAMES = 64 +/** EIP-8250 `MAX_NONCE_KEYS`. */ +export const MAX_NONCE_KEYS = 16 /** EIP-7594 per-transaction blob limit; frame transactions inherit it unchanged. */ export const MAX_BLOBS_PER_TX = 6 /** EIP-8141 expiry-verifier predeploy. A VERIFY frame targeting it is an expiry frame. */ @@ -301,9 +305,10 @@ function isExpiryVerifier(frame: Frame): boolean { } function assertFieldWidths(tx: FrameTransaction): void { - // These are the widths ethrex's decoder reads the fields at. + // These are the widths ethrex's decoder reads the fields at. The nonce keys + // are U256 and are checked with the rest of the nonce rules in `assertNonce`. assertUint(tx.chainId, 64, 'chainId') - assertUint(tx.nonce, 64, 'nonce') + assertUint(tx.nonceSeq, 64, 'nonceSeq') assertUint(tx.maxPriorityFeePerGas, 256, 'maxPriorityFeePerGas') assertUint(tx.maxFeePerGas, 256, 'maxFeePerGas') assertUint(tx.maxFeePerBlobGas, 256, 'maxFeePerBlobGas') @@ -315,7 +320,7 @@ function assertSender(tx: FrameTransaction): void { throw new FrameEncodeError('sender must not be the zero address') } -/** Checked before the nonce rules, matching the order in the Rust original. */ +/** Checked after the nonce rules, matching the order in the Rust original. */ function assertFrameCount(tx: FrameTransaction): void { if (tx.frames.length === 0) throw new FrameEncodeError('a frame transaction needs at least one frame') @@ -323,9 +328,25 @@ function assertFrameCount(tx: FrameTransaction): void { throw new FrameEncodeError(`at most ${MAX_FRAMES} frames, got ${tx.frames.length}`) } +/** + * EIP-8250 keyed-nonce rules, from `validate_static_constraints` at ethrex + * `bdfc5d8`. Key `0` is the account nonce and cannot be mixed with non-zero + * keys: a transaction either increments the account nonce or writes + * `NONCE_MANAGER` slots, never both. + */ function assertNonce(tx: FrameTransaction): void { - if (tx.nonce >= U64_MAX) - throw new FrameEncodeError('nonce must be below 2**64 - 1') + if (tx.nonceKeys.length < 1 || tx.nonceKeys.length > MAX_NONCE_KEYS) + throw new FrameEncodeError( + `nonceKeys must hold between 1 and ${MAX_NONCE_KEYS} entries, got ${tx.nonceKeys.length}`, + ) + for (const [i, key] of tx.nonceKeys.entries()) assertUint(key, 256, `nonceKeys[${i}]`) + for (let i = 1; i < tx.nonceKeys.length; i++) + if (tx.nonceKeys[i - 1]! >= tx.nonceKeys[i]!) + throw new FrameEncodeError('nonceKeys must be strictly increasing') + if (tx.nonceKeys.length > 1 && tx.nonceKeys[0] === 0n) + throw new FrameEncodeError('nonce key 0 is only valid as the sole nonce key') + if (tx.nonceSeq >= U64_MAX) + throw new FrameEncodeError('nonceSeq must be below 2**64 - 1') } function assertBlobs(tx: FrameTransaction): void { @@ -380,7 +401,7 @@ function assertFrames(tx: FrameTransaction): void { throw new FrameEncodeError(`frame ${i}: only SENDER frames may carry value`) // ethrex bounds both dimensions at i64::MAX, stricter than the EIP's 2**64-1, - // so its i64 state-gas accounting cannot overflow (transaction.rs:2924-2950). + // so its i64 state-gas accounting cannot overflow (transaction.rs:2814-2845 at `bdfc5d8`). assertNonNegative(frame.limits.execution, `frame ${i}: limits.execution`) assertNonNegative(frame.limits.state, `frame ${i}: limits.state`) if (frame.limits.execution > I64_MAX) @@ -434,7 +455,7 @@ function assertFrames(tx: FrameTransaction): void { /** * The encode-side structural rules, transcribed from - * `FrameTransaction::validate_static_constraints` (transaction.rs:2662-3010). + * `FrameTransaction::validate_static_constraints` (transaction.rs:2667 at `bdfc5d8`). * * Strict on purpose: these are consensus rules, so producing a transaction that * violates one is producing an invalid transaction, not merely an unrelayable @@ -451,8 +472,8 @@ function assertFrames(tx: FrameTransaction): void { export function validateFrameTx(tx: FrameTransaction): void { assertFieldWidths(tx) assertSender(tx) - assertFrameCount(tx) assertNonce(tx) + assertFrameCount(tx) assertBlobs(tx) assertSignatures(tx) assertFrames(tx) diff --git a/src/gas.ts b/src/gas.ts index 998ccd2..0b8baf9 100644 --- a/src/gas.ts +++ b/src/gas.ts @@ -1,5 +1,5 @@ -import { type Address, type Hex } from 'viem' -import { byteLength } from './rlp.js' +import { type Address, type Hex, concatHex, toRlp } from 'viem' +import { byteLength, rlpUint } from './rlp.js' import type { FrameTransaction, RuleSet, SigScheme } from './types.js' // EIP-8141 Constants table. Written as published figures, never derived: ethrex's @@ -12,6 +12,16 @@ export const STANDARD_TOKEN_COST = 4n export const TOTAL_COST_FLOOR_PER_TOKEN = 16n export const GAS_PER_BLOB = 131_072n +// EIP-8037 state gas that EIP-8250 nonce consumption charges against the +// payment-approving frame's `limits.state`. These are execution-time charges, so +// they are budgeting figures for callers sizing `limits.state`, not terms of +// `frameTxGas`. Both are `state bytes * cost_per_state_byte`, with the cost +// pinned at 1530 on hegota-testnet (ethrex `bdfc5d8`, `gas_cost.rs:194`). +/** A non-zero nonce key whose `NONCE_MANAGER` slot is still zero: 64 * 1530. */ +export const KEYED_NONCE_FIRST_USE_STATE_GAS = 97_920n +/** Key `[0]` from a sender that does not exist yet: 120 * 1530. */ +export const NEW_ACCOUNT_STATE_GAS = 183_600n + export const SIG_VERIFY_COST: Record = { 0: 100n, 1: 2_800n, @@ -35,15 +45,27 @@ export type FrameGas = { maxGas: bigint } +/** + * EIP-8250 `nonce_calldata`: `rlp(nonce_keys) || rlp(nonce_seq)`. The bytes the + * keyed nonce adds to the payload, priced like frame and signature data. + */ +export function nonceCalldata(tx: FrameTransaction): Hex { + return concatHex([ + toRlp(tx.nonceKeys.map(rlpUint), 'hex'), + toRlp(rlpUint(tx.nonceSeq), 'hex'), + ]) +} + /** * The fields the calldata cost is charged over: each frame's `data`, each - * signature's `signer`, `msg` and `signature`. The envelope's RLP framing and - * its scalar fields are not billed. + * signature's `signer`, `msg` and `signature`, and the nonce calldata. The + * envelope's RLP framing and its other scalar fields are not billed. */ function billedFields(tx: FrameTransaction): Hex[] { return [ ...tx.frames.map((f) => f.data), ...tx.signatures.flatMap((s) => [s.signer ?? '0x', s.msg, s.signature] as Hex[]), + nonceCalldata(tx), ] } diff --git a/src/index.ts b/src/index.ts index aa013af..b440397 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,12 @@ export { type ToEoaFrameAccountReturnType, toEoaFrameAccount, } from './accounts/toEoaFrameAccount.js' +export { + type GetFrameNonceSeqParameters, + NONCE_MANAGER, + getFrameNonceSeq, + keyedNonceSlot, +} from './nonce.js' export { type PrepareFrameLimits, type PrepareFrameTransactionOptions, @@ -33,6 +39,7 @@ export { decodeFrameTx, validateFrameTx, MAX_FRAMES, + MAX_NONCE_KEYS, MAX_BLOBS_PER_TX, EXPIRY_VERIFIER, } from './envelope.js' @@ -52,12 +59,15 @@ export { FRAME_TX_PER_FRAME_COST, FRAME_TX_VALUE_COST, GAS_PER_BLOB, + KEYED_NONCE_FIRST_USE_STATE_GAS, + NEW_ACCOUNT_STATE_GAS, SIG_VERIFY_COST, STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN, type FrameGas, frameTxGas, frameTxMaxCost, + nonceCalldata, } from './gas.js' export { type GasDivergence, diff --git a/src/nonce.ts b/src/nonce.ts new file mode 100644 index 0000000..8f266c5 --- /dev/null +++ b/src/nonce.ts @@ -0,0 +1,60 @@ +import { + type Address, + type BlockTag, + type EIP1193RequestFn, + type Hex, + concatHex, + keccak256, + numberToHex, + pad, +} from 'viem' +import { FrameEncodeError } from './errors.js' + +/** EIP-8250 `NONCE_MANAGER` predeploy. Holds the sequence of every non-zero nonce key. */ +export const NONCE_MANAGER: Address = '0x0000000000000000000000000000000000008250' + +const U256_MAX = 2n ** 256n - 1n + +/** + * The `NONCE_MANAGER` storage slot of `(sender, key)`: + * `keccak256(left_pad_32(sender) || uint256_to_bytes32(key))` + * (ethrex `docs/eip-8250.md:34`, `VM::keyed_nonce_slot`). + */ +export function keyedNonceSlot(sender: Address, key: bigint): Hex { + if (key <= 0n || key > U256_MAX) + throw new FrameEncodeError(`a keyed nonce slot needs a key in 1..2**256 - 1, got ${key}`) + return keccak256(concatHex([pad(sender, { size: 32 }), numberToHex(key, { size: 32 })])) +} + +export type GetFrameNonceSeqParameters = { + address: Address + /** Nonce key to read. Defaults to `0n`, the account nonce. */ + key?: bigint | undefined + blockTag?: BlockTag | undefined +} + +/** + * The current EIP-8250 sequence of one nonce key: what `nonceSeq` must equal for + * a transaction selecting that key. + * + * Key `0` is the account nonce, read with `eth_getTransactionCount`. Any other + * key is a `NONCE_MANAGER` storage slot, read with `eth_getStorageAt`; the node + * has no RPC for keyed nonces, and an unused key reads zero. + */ +export async function getFrameNonceSeq( + client: { request: EIP1193RequestFn }, + { address, key = 0n, blockTag = 'pending' }: GetFrameNonceSeqParameters, +): Promise { + if (key === 0n) { + const count = (await client.request({ + method: 'eth_getTransactionCount', + params: [address, blockTag], + })) as Hex + return BigInt(count) + } + const value = (await client.request({ + method: 'eth_getStorageAt', + params: [NONCE_MANAGER, keyedNonceSlot(address, key), blockTag], + })) as Hex + return BigInt(value) +} diff --git a/src/prepareFrameTransaction.ts b/src/prepareFrameTransaction.ts index 4cbe3f9..6ddbe7d 100644 --- a/src/prepareFrameTransaction.ts +++ b/src/prepareFrameTransaction.ts @@ -14,6 +14,12 @@ export type PrepareFrameLimits = { export type PrepareFrameTransactionOptions = { /** State used for nonce and fee reads. */ blockTag?: BlockTag | undefined + /** + * EIP-8250 nonce keys to consume. Defaults to `[0n]`, the account nonce. With + * several keys, every key must currently sit at the same sequence, because one + * `nonceSeq` is matched against all of them. + */ + nonceKeys?: readonly bigint[] | undefined } type RpcBlock = { @@ -89,16 +95,31 @@ export async function prepareFrameTransaction< assertLimits(limits, `frameLimits.calls[${index}]`) const blockTag = options.blockTag ?? 'pending' - const [nonce, fees] = await Promise.all([ - account.getNonce({ blockTag }), + const nonceKeys = [...(options.nonceKeys ?? [0n])] + if (nonceKeys.length === 0) + throw new FrameEncodeError('nonceKeys must hold at least one key') + const [sequences, fees] = await Promise.all([ + Promise.all(nonceKeys.map((key) => account.getNonce({ key, blockTag }))), getFees(account, blockTag), ]) + const nonceSeq = sequences[0]! + for (const [index, sequence] of sequences.entries()) + if (sequence !== nonceSeq) + throw new FrameEncodeError( + `nonce keys are at different sequences: key ${nonceKeys[0]} is at ${nonceSeq}, key ${nonceKeys[index]} is at ${sequence}; one nonceSeq cannot select both`, + ) const chainId = BigInt(account.client.chain.id) - const validationData = await account.getValidationData({ calls, chainId, nonce }) + const validationData = await account.getValidationData({ + calls, + chainId, + nonceKeys, + nonceSeq, + }) return { chainId, - nonce, + nonceKeys, + nonceSeq, sender: account.address, frames: [ { diff --git a/src/rpc.ts b/src/rpc.ts index 7674a68..ce96bdb 100644 --- a/src/rpc.ts +++ b/src/rpc.ts @@ -14,7 +14,8 @@ export type RpcFrameTransaction = { /** Present on `eth_getTransactionByHash` and hydrated blocks; absent on hand-written fixtures. */ hash?: Hex chainId: Hex - nonce: Hex + nonceKeys: readonly Hex[] + nonceSeq: Hex sender: Address frames: readonly { mode: Hex @@ -126,7 +127,8 @@ export function parseRpcFrameTransaction(json: RpcFrameTransaction): FrameTransa return { chainId: BigInt(json.chainId), - nonce: BigInt(json.nonce), + nonceKeys: json.nonceKeys.map((k) => BigInt(k)), + nonceSeq: BigInt(json.nonceSeq), sender: getAddress(json.sender), frames, signatures, @@ -346,7 +348,7 @@ export function parseSimulateResult( prefixShape: asUnion(result.prefixShape, PREFIX_SHAPES, 'prefixShape'), payer: asOptionalAddress(result.payer, 'payer'), // Reported on every path, structural rejection included, because it is a - // pure function of the fields (DESIGN.md §5, "The node's JSON surface"). + // pure function of the fields (DESIGN.md §6, "Simulation"). maxCost: asHexScalar(result.maxCost, 'maxCost'), violation: asOptionalString(result.violation, 'violation'), gasUsed: asOptionalHexScalar(result.gasUsed, 'gasUsed'), diff --git a/src/types.ts b/src/types.ts index c9626cd..dd19f8a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -37,8 +37,14 @@ export type FrameSignature = { export type FrameTransaction = { chainId: bigint - /** Sender account nonce, encoded as a scalar uint64. */ - nonce: bigint + /** + * EIP-8250 nonce keys: 1 to 16, strictly increasing, each a uint256. `[0n]` is + * the sender's ordinary account nonce; key `0` is valid only as the sole key. + * Non-zero keys are tracked in the `NONCE_MANAGER` predeploy. + */ + nonceKeys: bigint[] + /** EIP-8250 sequence number, a uint64 matched against every selected key. */ + nonceSeq: bigint sender: Address frames: Frame[] signatures: FrameSignature[] @@ -50,7 +56,7 @@ export type FrameTransaction = { /** * Which rule set to price under. - * Kept for API compatibility with the kit's gas helpers. The current frame - * transaction envelope has no keyed-nonce or recent-root extensions. + * Kept for API compatibility with the kit's gas helpers. hegota-testnet prices + * every frame transaction under one set of rules, so all three names are aliases. */ export type RuleSet = 'chain' | 'pins' | 'head' diff --git a/test/accounts.test.ts b/test/accounts.test.ts index cec6557..6f4709e 100644 --- a/test/accounts.test.ts +++ b/test/accounts.test.ts @@ -26,7 +26,7 @@ describe('toFrameAccount', () => { extend: { implementationName: 'test-account' as const }, async getAddress() { return address }, async getNonce() { return 1n }, - async getValidationData({ nonce }) { return nonce === 1n ? '0xabcd' : '0x' }, + async getValidationData({ nonceSeq }) { return nonceSeq === 1n ? '0xabcd' : '0x' }, async signFrameTransaction(transaction) { return transaction }, }) @@ -34,7 +34,9 @@ describe('toFrameAccount', () => { expect(account.type).toBe('frame') expect(account.implementationName).toBe('test-account') expect(await account.getNonce()).toBe(1n) - expect(await account.getValidationData({ calls: [], chainId: 1n, nonce: 1n })).toBe('0xabcd') + expect( + await account.getValidationData({ calls: [], chainId: 1n, nonceKeys: [0n], nonceSeq: 1n }), + ).toBe('0xabcd') expect(await account.signFrameTransaction(GOLDEN_TX as FrameTransaction)).toBe(GOLDEN_TX) }) @@ -72,13 +74,15 @@ describe('toEoaFrameAccount', () => { const account = await toEoaFrameAccount({ client, owner }) const unsigned = { ...GOLDEN_TX, sender: owner.address, signatures: [] } - expect(await account.getValidationData({ calls: [], chainId: 1n, nonce: 0n })).toBe('0x') + expect( + await account.getValidationData({ calls: [], chainId: 1n, nonceKeys: [0n], nonceSeq: 0n }), + ).toBe('0x') const signed = await account.signFrameTransaction(unsigned) expect(signed.signatures[0]).toMatchObject({ scheme: 1, signer: null, msg: '0x' }) expect(await recoverFrameSigner(signed, 0)).toBe(owner.address) }) - test('reads the pending scalar account nonce', async () => { + test('reads key 0 from the pending account nonce', async () => { const owner = privateKeyToAccount(OWNER_KEY) const requests: { method: string; params?: unknown }[] = [] const client = createClient({ @@ -99,6 +103,36 @@ describe('toEoaFrameAccount', () => { ]) }) + test('reads a non-zero key from its NONCE_MANAGER slot', async () => { + const owner = privateKeyToAccount(OWNER_KEY) + const requests: { method: string; params?: unknown }[] = [] + const client = createClient({ + account: owner, + chain: mainnet, + transport: custom({ + async request(request) { + requests.push(request) + return `0x${'00'.repeat(31)}03` + }, + }), + }) + const account = await toEoaFrameAccount({ client, owner }) + + expect(await account.getNonce({ key: 5n, blockTag: 'latest' })).toBe(3n) + // Slot computed independently: `cast keccak` over + // left_pad_32(0x19E7…ff2A) || uint256(5). + expect(requests).toEqual([ + { + method: 'eth_getStorageAt', + params: [ + '0x0000000000000000000000000000000000008250', + '0x91532bb988e1829b215838c387168ecc92dc12a6ace9895d81f60d6eebf505d4', + 'latest', + ], + }, + ]) + }) + test('rejects a transaction whose sender is not the EOA', async () => { const owner = privateKeyToAccount(OWNER_KEY) const client = createClient({ diff --git a/test/chain.oracle.test.ts b/test/chain.oracle.test.ts new file mode 100644 index 0000000..c410fbf --- /dev/null +++ b/test/chain.oracle.test.ts @@ -0,0 +1,63 @@ +import { keccak256 } from 'viem' +import { describe, expect, test } from 'vitest' +import { decodeFrameTx, encodeFrameTx } from '../src/envelope.js' +import { frameTxGas, frameTxMaxCost } from '../src/gas.js' +import { parseRpcFrameReceipt, parseRpcFrameTransaction } from '../src/rpc.js' +import { recoverFrameSigner, resolveSigner } from '../src/signatures.js' +import { loadChainFixtures } from './helpers/fixtures.js' + +// Each fixture carries its own hash, receipt and simulate answer, so these checks +// need no network and keep holding after the chain re-genesises. +const fixtures = loadChainFixtures() +const cases = fixtures.map((f) => [f.name, f] as const) + +describe('captured chain fixtures', () => { + test('exist', () => expect(fixtures.length).toBeGreaterThan(0)) +}) + +// The node's decoded JSON, re-encoded, must hash to the node's hash. +describe.each(cases)('wire format: %s', (_name, fixture) => { + const tx = parseRpcFrameTransaction(fixture.tx) + const raw = encodeFrameTx(tx) + + test('re-encoding reproduces the transaction hash', () => { + expect(keccak256(raw)).toBe(fixture.tx.hash) + }) + + test('the reproduced bytes decode back to the parsed transaction', () => { + expect(decodeFrameTx(raw)).toEqual(tx) + }) +}) + +// Real signatures, real receipts, and the node's own maxCost. +describe.each(cases)('signatures and gas: %s', (_name, fixture) => { + const tx = parseRpcFrameTransaction(fixture.tx) + + test('every SECP256K1 signature recovers to its resolved signer', async () => { + for (const [i, sig] of tx.signatures.entries()) + if (sig.scheme === 1) expect(await recoverFrameSigner(tx, i)).toBe(resolveSigner(tx, i)) + }) + + test('the node authenticates our sig_hash over the real signature', () => { + expect(fixture.simulate.violation ?? '').not.toMatch(/does not authenticate/) + }) + + test("our maxCost equals the node's", () => { + // Blob-free on this chain so far; the blob term needs the base fee at capture. + expect(tx.blobVersionedHashes).toEqual([]) + expect(frameTxMaxCost(tx, 0n, 'chain')).toBe(BigInt(fixture.simulate.maxCost)) + }) + + // The calldata floor binds the regular dimension only; state gas goes on top. + // Storage refunds are not itemized in frame receipts, so fixtures are chosen + // from refund-free transactions and this stays an equality. + test('gasUsed = max(intrinsic + sum of frame execution, floor) + sum of frame state', () => { + const gas = frameTxGas(tx, 'chain') + const { frameReceipts } = parseRpcFrameReceipt(fixture.receipt) + const execution = frameReceipts.reduce((acc, f) => acc + f.gasUsed, 0n) + const state = frameReceipts.reduce((acc, f) => acc + f.stateGasUsed, 0n) + const regular = gas.intrinsicGas + execution + const floored = regular > gas.calldataFloorTotal ? regular : gas.calldataFloorTotal + expect(floored + state).toBe(BigInt(fixture.receipt.gasUsed)) + }) +}) diff --git a/test/envelope.decode.test.ts b/test/envelope.decode.test.ts index f12d83a..65703d0 100644 --- a/test/envelope.decode.test.ts +++ b/test/envelope.decode.test.ts @@ -1,3 +1,4 @@ +import { concatHex, toRlp } from 'viem' import { describe, expect, test } from 'vitest' import { decodeFrameTx, encodeFrameTx } from '../src/envelope.js' import { FrameDecodeError } from '../src/errors.js' @@ -23,6 +24,24 @@ describe('decodeFrameTx', () => { test('rejects a body with the wrong field count', () => { expect(() => decodeFrameTx('0x06c20180')).toThrow(FrameDecodeError) }) + + test('rejects the retired seven-field scalar-nonce envelope', () => { + // The chain-81410 layout: `nonce` a scalar where `nonceKeys` belongs, and one + // field short. ethrex refuses it at `nonce_keys`; so does this decoder. + const sevenField = concatHex([ + '0x06', + toRlp(['0x01', '0x07', GOLDEN_TX.sender, [], [], ['0x', '0x', '0x'], []]), + ]) + expect(() => decodeFrameTx(sevenField)).toThrow(/8 fields, got 7/) + }) + + test('rejects nonceKeys encoded as a scalar', () => { + const scalarKeys = GOLDEN_RLP.replace('01c18007', '018007').replace( + '0x06f8b2', + '0x06f8b1', + ) as `0x${string}` + expect(() => decodeFrameTx(scalarKeys)).toThrow(/nonceKeys must be an RLP list/) + }) }) /** @@ -55,22 +74,22 @@ describe('decodeFrameTx: FrameDecodeError.offset', () => { }) test('a non-canonical scalar points at the offending byte', () => { - // nonce 7 written long-form as `81 07`: chainId is at byte 3, so the bad - // `81` byte sits at offset 4. - const longFormNonce = GOLDEN_RLP.replace('0107', '018107').replace( + // nonceSeq 7 written long-form as `81 07`: chainId is byte 3 and the + // one-key list `c1 80` bytes 4-5, so the bad `81` byte sits at offset 6. + const longFormNonce = GOLDEN_RLP.replace('01c18007', '01c1808107').replace( + '0x06f8b2', '0x06f8b3', - '0x06f8b4', ) as `0x${string}` - expect(offsetOf(longFormNonce)).toBe(4) + expect(offsetOf(longFormNonce)).toBe(6) }) test('a non-minimal scalar field points at the field', () => { - // nonce 7 written as the two-byte string `82 00 07`. - const leadingZero = GOLDEN_RLP.replace('0107', '01820007').replace( - '0x06f8b3', - '0x06f8b5', + // nonceSeq 7 written as the two-byte string `82 00 07`. + const leadingZero = GOLDEN_RLP.replace('01c18007', '01c180820007').replace( + '0x06f8b2', + '0x06f8b4', ) as `0x${string}` - expect(offsetOf(leadingZero)).toBe(4) + expect(offsetOf(leadingZero)).toBe(6) }) }) @@ -95,15 +114,15 @@ describe('decodeFrameTx: malformed bodies stay typed', () => { }) describe('decodeFrameTx: non-canonical RLP', () => { - // `nonce` is 7, canonically the single byte 0x07. Written long-form as + // `nonceSeq` is 7, canonically the single byte 0x07. Written long-form as // 0x81 0x07 it is one byte longer, so the outer list header grows from - // 0xf8 0xb3 to 0xf8 0xb4. viem's `fromRlp` would accept the long form and + // 0xf8 0xb2 to 0xf8 0xb3. viem's `fromRlp` would accept the long form and // return 7; ethrex's decoder rejects it ("the 0x81 0x01 form is now // rejected"), and so does `walkRlp` — decoding it here would otherwise hand // back a transaction whose bytes the chain will not take. - const LONG_FORM_NONCE = GOLDEN_RLP.replace('0107', '018107').replace( + const LONG_FORM_NONCE = GOLDEN_RLP.replace('01c18007', '01c1808107').replace( + '0x06f8b2', '0x06f8b3', - '0x06f8b4', ) test('the mutation is a real one', () => { @@ -121,14 +140,14 @@ describe('decodeFrameTx: non-canonical RLP', () => { }) test('rejects a non-minimal scalar (leading zero byte) as a decode error', () => { - // nonce 7 written as the two-byte string 0x00 0x07: `82 00 07` replaces + // nonceSeq 7 written as the two-byte string 0x00 0x07: `82 00 07` replaces // `07`, and the outer list grows by two. `walkRlp` returns the bytes // verbatim (a 2-byte string is well-formed RLP), `parseRlpUint` rejects the // leading zero, and the caller must see that as a FrameDecodeError like // every other malformed-body case, not the internal FrameRlpError. - const leadingZero = GOLDEN_RLP.replace('0107', '01820007').replace( - '0x06f8b3', - '0x06f8b5', + const leadingZero = GOLDEN_RLP.replace('01c18007', '01c180820007').replace( + '0x06f8b2', + '0x06f8b4', ) as `0x${string}` expect(leadingZero.length).toBe(GOLDEN_RLP.length + 4) expect(() => decodeFrameTx(leadingZero)).toThrow(FrameDecodeError) diff --git a/test/envelope.encode.test.ts b/test/envelope.encode.test.ts index a85b90b..a81ad78 100644 --- a/test/envelope.encode.test.ts +++ b/test/envelope.encode.test.ts @@ -7,23 +7,31 @@ describe('encodeFrameTx', () => { expect(encodeFrameTx(GOLDEN_TX)).toBe(GOLDEN_RLP) }) - test('encodes nonce zero as an empty RLP string, not 0x00', () => { - const encoded = encodeFrameTx({ ...GOLDEN_TX, nonce: 0n }) + test('encodes nonceSeq zero as an empty RLP string, not 0x00', () => { + const encoded = encodeFrameTx({ ...GOLDEN_TX, nonceSeq: 0n }) expect(encoded).not.toBe(GOLDEN_RLP) - expect(encoded).toContain('018094') + // chainId 1, nonceKeys [0] as `c1 80`, nonceSeq 0 as `80`, then the sender. + expect(encoded.slice(0, 18)).toBe('0x06f8b201c1808094') + }) + + test('encodes nonceKeys as a list of minimal uint256 scalars', () => { + const encoded = encodeFrameTx({ ...GOLDEN_TX, nonceKeys: [1n, 2n ** 255n] }) + // `e2` lists 34 bytes: `01`, then `a0` and the 32-byte key; `07` is nonceSeq. + expect(encoded).toContain(`01e201a080${'00'.repeat(31)}07`) }) test('a targetless frame encodes an empty target, not 20 zero bytes', () => { const encoded = encodeFrameTx(GOLDEN_TX) - expect(encoded).toContain('cf010380c7825208831e8480') + expect(encoded).toContain('cc010380c48252088080') }) test('encodeFrameTxBody is the wire encoding minus the 0x06 type byte', () => { expect(encodeFrameTx(GOLDEN_TX)).toBe(`0x06${encodeFrameTxBody(GOLDEN_TX).slice(2)}`) }) - test('limits always encode as a two-element list', () => { - expect(encodeFrameTx(GOLDEN_TX)).toContain('c7825208831e8480') + test('limits always encode as a two-element list, even with zero state', () => { + // c4 (825208)(80): the golden VERIFY frame's state limit is zero. + expect(encodeFrameTx(GOLDEN_TX)).toContain('c482520880') }) test('the fees list always has three entries, even when maxFeePerBlobGas is zero', () => { diff --git a/test/envelope.validate.test.ts b/test/envelope.validate.test.ts index cdf7ea9..c9641ad 100644 --- a/test/envelope.validate.test.ts +++ b/test/envelope.validate.test.ts @@ -60,8 +60,14 @@ describe('validateFrameTx', () => { expect(() => validateFrameTx(tx)).toThrow(/reserved/) }) - test('rejects nonce at 2**64 - 1', () => { - expect(() => validateFrameTx({ ...GOLDEN_TX, nonce: 2n ** 64n - 1n })).toThrow(/nonce/) + test('rejects nonceSeq at 2**64 - 1', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceSeq: 2n ** 64n - 1n })).toThrow( + /nonceSeq must be below/, + ) + }) + + test('accepts nonceSeq at 2**64 - 2', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceSeq: 2n ** 64n - 2n })).not.toThrow() }) test('rejects more than 64 frames', () => { @@ -157,7 +163,8 @@ describe('validateFrameTx: single-byte and 32-byte wire fields', () => { /** * ethrex's `FrameTransaction` decodes each numeric field at a fixed width — - * `chain_id` and `nonce` as u64; all three fees and `Frame::value` as U256. + * `chain_id` and `nonce_seq` as u64; the nonce keys, all three fees and + * `Frame::value` as U256. * `static_left_pad` returns `InvalidLength` * for anything wider. A wider value encodes to well-formed RLP here and then * cannot be decoded by the node at all, so it has to be caught on this side. @@ -277,3 +284,58 @@ describe('validateFrameTx: malformed hex fields throw FrameEncodeError', () => { expect(() => validateFrameTx(tx)).toThrow(/blob hash 0/) }) }) + +/** EIP-8250 keyed-nonce rules, from `validate_static_constraints` at ethrex `bdfc5d8`. */ +describe('validateFrameTx: nonce keys', () => { + test('accepts the account nonce alone', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [0n] })).not.toThrow() + }) + + test('accepts strictly increasing non-zero keys', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [1n, 5n, 2n ** 256n - 1n] })).not.toThrow() + }) + + test('accepts sixteen keys', () => { + const nonceKeys = Array.from({ length: 16 }, (_, i) => BigInt(i + 1)) + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys })).not.toThrow() + }) + + test('rejects an empty key list', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [] })).toThrow(/between 1 and 16/) + }) + + test('rejects seventeen keys', () => { + const nonceKeys = Array.from({ length: 17 }, (_, i) => BigInt(i + 1)) + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys })).toThrow(/between 1 and 16/) + }) + + test('rejects a repeated key', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [3n, 3n] })).toThrow( + /strictly increasing/, + ) + }) + + test('rejects decreasing keys', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [4n, 3n] })).toThrow( + /strictly increasing/, + ) + }) + + test('rejects key 0 alongside another key', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [0n, 1n] })).toThrow( + /key 0 is only valid as the sole nonce key/, + ) + }) + + test('rejects a key wider than 256 bits', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [2n ** 256n] })).toThrow( + /nonceKeys\[0\] must fit in 256 bits/, + ) + }) + + test('checks the nonce before the frame count, as ethrex does', () => { + expect(() => validateFrameTx({ ...GOLDEN_TX, nonceKeys: [], frames: [] })).toThrow( + /nonceKeys/, + ) + }) +}) diff --git a/test/fixtures/chain/0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8.json b/test/fixtures/chain/0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8.json new file mode 100644 index 0000000..d47615b --- /dev/null +++ b/test/fixtures/chain/0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8.json @@ -0,0 +1,124 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:57.848Z", + "block": "66", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xf499469776a32dbc10f7a1dec68cf812a8a83fe7cad50d2f97f775ea0fc4f59f", + "blockNumber": "0x42", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x2", + "stateGasLimit": "0x2cd30", + "to": "0xc0de000000000000000000000000000000000064", + "value": "0x64" + } + ], + "from": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "hash": "0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x77403b44", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x0" + ], + "nonceSeq": "0x64", + "sender": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x0011b4b6326501eee78e4779f719b0be0f4d0955b7a960a9ad02e8ae6e836ab72077ceabb02496012a029bbab3d94914a32d347d0cd825d3b1ff1e87fd2a43e7a1", + "signer": "0x189abc0be7003dd1238b7a404325b440c70e88e4" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xf499469776a32dbc10f7a1dec68cf812a8a83fe7cad50d2f97f775ea0fc4f59f", + "blockNumber": "0x42", + "contractAddress": null, + "cumulativeGasUsed": "0x33826", + "effectiveGasPrice": "0x3b9d1e97", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0xbb8", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000189abc0be7003dd1238b7a404325b440c70e88e4", + "0x000000000000000000000000c0de000000000000000000000000000000000064" + ] + } + ], + "stateGasUsed": "0x2cd30", + "status": "0x1" + } + ], + "from": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "gasUsed": "0x33826", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0xf499469776a32dbc10f7a1dec68cf812a8a83fe7cad50d2f97f775ea0fc4f59f", + "blockNumber": "0x42", + "blockTimestamp": "0x6aa86d11", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000189abc0be7003dd1238b7a404325b440c70e88e4", + "0x000000000000000000000000c0de000000000000000000000000000000000064" + ], + "transactionHash": "0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000002000000010000000000008000000000000000000000000000000100000000000000000200000002000000000000000000000000000000000000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000008000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "payer": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "status": "0x1", + "to": null, + "transactionHash": "0x1071aec56d95978b49436ac1f6584412ec2c327faf6d612fe34f1b6dd7f9daf8", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x240563fedee78", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 10819, got 100" + } +} diff --git a/test/fixtures/chain/0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895.json b/test/fixtures/chain/0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895.json new file mode 100644 index 0000000..da60dae --- /dev/null +++ b/test/fixtures/chain/0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895.json @@ -0,0 +1,190 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:57.389Z", + "block": "127677", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x5931fa3a83b90fecf74db1634daafd6eddf954ccfe71ff0c6d6b49e01f0e73d7", + "blockNumber": "0x1f2bd", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x0ff82c871fc813d9ffd8a18c692133970109afec", + "value": "0x0" + }, + { + "data": "0x26123548249b9fde04144dcabb29e6ba74d9d85e849428590138434daddaf6aa63f0751b", + "flags": "0x4", + "gasLimit": "0x1e8480", + "mode": "0x2", + "stateGasLimit": "0x86470", + "to": "0xac01c30f28b32dd31d3c2854012e673e74f6b100", + "value": "0x1d780286444da000" + }, + { + "data": "0xd03870b30000000000000000000000000000000000000000000000000000000000000000", + "flags": "0x0", + "gasLimit": "0x1e8480", + "mode": "0x2", + "stateGasLimit": "0x86470", + "to": "0xac01c30f28b32dd31d3c2854012e673e74f6b100", + "value": "0x0" + } + ], + "from": "0x0ff82c871fc813d9ffd8a18c692133970109afec", + "hash": "0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x3b9aca0e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x0" + ], + "nonceSeq": "0x1", + "sender": "0x0ff82c871fc813d9ffd8a18c692133970109afec", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x003cfafae33790c86befdb13a5bfeeccd92bdfed2ef6f583f5018b7cfc7d0faea510e62c5f22d33dce208b8fb543fa25b3f7db2c7d2562d85d01500fcd96da5e08", + "signer": "0x0ff82c871fc813d9ffd8a18c692133970109afec" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x5931fa3a83b90fecf74db1634daafd6eddf954ccfe71ff0c6d6b49e01f0e73d7", + "blockNumber": "0x1f2bd", + "contractAddress": null, + "cumulativeGasUsed": "0xe0446", + "effectiveGasPrice": "0x3b9aca07", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0xbdc3f", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x0000000000000000000000000000000000000000000000001d780286444da000", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000ff82c871fc813d9ffd8a18c692133970109afec", + "0x000000000000000000000000ac01c30f28b32dd31d3c2854012e673e74f6b100" + ] + }, + { + "address": "0xac01c30f28b32dd31d3c2854012e673e74f6b100", + "data": "0x000000000000000000000000000000000000000000000000000000000000000e10d45829eebd6126fbf5c8a29ce23dbcddbba9ba6a638e47fa9383973ce2694f", + "topics": [ + "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", + "0x1b0d1cb746e496579a18dc1bde707733aa0828eaebf1e07c9ed2dffe5adaceb2", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x49de", + "logs": [ + { + "address": "0xac01c30f28b32dd31d3c2854012e673e74f6b100", + "data": "0x10d45829eebd6126fbf5c8a29ce23dbcddbba9ba6a638e47fa9383973ce2694f", + "topics": [ + "0x74a39535d9bb6f1d488cac65267f169b5a14380508b02e52625bff3cc46635bf", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x37cca6d9ac8fad0a6066bc4c75923f52beb889fc1533c281758b3a182fde9ff4" + ] + } + ], + "stateGasUsed": "0x17e80", + "status": "0x1" + } + ], + "from": "0x0ff82c871fc813d9ffd8a18c692133970109afec", + "gasUsed": "0xe0446", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0x5931fa3a83b90fecf74db1634daafd6eddf954ccfe71ff0c6d6b49e01f0e73d7", + "blockNumber": "0x1f2bd", + "blockTimestamp": "0x6ab4cd83", + "data": "0x0000000000000000000000000000000000000000000000001d780286444da000", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000ff82c871fc813d9ffd8a18c692133970109afec", + "0x000000000000000000000000ac01c30f28b32dd31d3c2854012e673e74f6b100" + ], + "transactionHash": "0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895", + "transactionIndex": "0x0" + }, + { + "address": "0xac01c30f28b32dd31d3c2854012e673e74f6b100", + "blockHash": "0x5931fa3a83b90fecf74db1634daafd6eddf954ccfe71ff0c6d6b49e01f0e73d7", + "blockNumber": "0x1f2bd", + "blockTimestamp": "0x6ab4cd83", + "data": "0x000000000000000000000000000000000000000000000000000000000000000e10d45829eebd6126fbf5c8a29ce23dbcddbba9ba6a638e47fa9383973ce2694f", + "logIndex": "0x1", + "removed": false, + "topics": [ + "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", + "0x1b0d1cb746e496579a18dc1bde707733aa0828eaebf1e07c9ed2dffe5adaceb2", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "transactionHash": "0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895", + "transactionIndex": "0x0" + }, + { + "address": "0xac01c30f28b32dd31d3c2854012e673e74f6b100", + "blockHash": "0x5931fa3a83b90fecf74db1634daafd6eddf954ccfe71ff0c6d6b49e01f0e73d7", + "blockNumber": "0x1f2bd", + "blockTimestamp": "0x6ab4cd83", + "data": "0x10d45829eebd6126fbf5c8a29ce23dbcddbba9ba6a638e47fa9383973ce2694f", + "logIndex": "0x2", + "removed": false, + "topics": [ + "0x74a39535d9bb6f1d488cac65267f169b5a14380508b02e52625bff3cc46635bf", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x37cca6d9ac8fad0a6066bc4c75923f52beb889fc1533c281758b3a182fde9ff4" + ], + "transactionHash": "0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x08000000000400000000000000000000000000000000000000000000000400000000000000000000000000000000091000800000000000000000000000000000800000000000000000000008000000000000080000000000200000440000000010000000020000002000000000000800000000000000000000000010400000000000000000000000001020000000000000000000000400000000000000000000000400000000000000000000000000000000000000000000000000000000000000000002000000000008000000000000000000000000000040000000000021400000000000000000400000000000000000000000000000000000000000000000", + "payer": "0x0ff82c871fc813d9ffd8a18c692133970109afec", + "status": "0x1", + "to": null, + "transactionHash": "0x17e40b0ace92c462f53b2f0ba17121ccfb1f46135f107cf2b0be15530bbbc895", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x127d5d41f5f906", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 3, got 1" + } +} diff --git a/test/fixtures/chain/0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019.json b/test/fixtures/chain/0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019.json deleted file mode 100644 index d8c8072..0000000 --- a/test/fixtures/chain/0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "meta": { - "clientVersion": "ethrex/v23.0.0-hegota-testnet-31b5322665fd48f07337f6dd7cede21e7de7866a/x86_64-unknown-linux-gnu/rustc-v1.93.1", - "genesisHash": "0x7ca0f7358d127dc4a68983050eb88837a5f384225254d1b009fa87fbcd0f2332", - "capturedAt": "2026-09-06T16:10:34.262Z", - "block": "2782", - "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" - }, - "tx": { - "blobVersionedHashes": [], - "blockHash": "0x0f9eafe34605c35d638a2799f2a6ac39f6874e9451037fa9df6286e6e79d784b", - "blockNumber": "0xade", - "chainId": "0x1fcd", - "frames": [ - { - "data": "0x", - "flags": "0x3", - "gasLimit": "0x13880", - "mode": "0x1", - "stateLimit": "0x0", - "to": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "value": "0x0" - }, - { - "data": "0x261235482dbb6517aacdd5efb9f76038024b535919bf021a854eb09e549cfa03bddecdd4", - "flags": "0x0", - "gasLimit": "0xef251", - "mode": "0x2", - "stateLimit": "0x86470", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "value": "0xde0b6b3a7640000" - } - ], - "from": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "hash": "0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019", - "maxFeePerBlobGas": "0x0", - "maxFeePerGas": "0x3b9aca0e", - "maxPriorityFeePerGas": "0x3b9aca00", - "nonceKeys": [ - "0x0" - ], - "nonceSeq": "0xa", - "recentRootReferences": [], - "sender": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "signatures": [ - { - "msg": "0x", - "scheme": "0x1", - "signature": "0x0051fd75608c79b6be8d5dda096745a2cb9cf9906499550caaddf50fde6f81ce8b1aacc858d90de2969adcdb88e48e16346167b1721ee9890f9fb0992f7edcb897", - "signer": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b" - } - ], - "transactionIndex": "0x0", - "type": "0x6" - }, - "receipt": { - "blockHash": "0x0f9eafe34605c35d638a2799f2a6ac39f6874e9451037fa9df6286e6e79d784b", - "blockNumber": "0xade", - "contractAddress": null, - "cumulativeGasUsed": "0x10cd9c", - "effectiveGasPrice": "0x3b9aca07", - "frameReceipts": [ - { - "gasUsed": "0x64", - "logs": [], - "stateGasUsed": "0x0", - "status": "0x1" - }, - { - "gasUsed": "0xbf50e", - "logs": [ - { - "address": "0xfffffffffffffffffffffffffffffffffffffffe", - "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000a9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "0x000000000000000000000000753d91eef10c8e26924aabcb0ad73052f8fc4522" - ] - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x00000000000000000000000000000000000000000000000000000000000000002dd32b6609c5a8e80505ac44c5cb8e9f712115c1f63f59b18be08fc9b9250bf4", - "topics": [ - "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", - "0x1995ffa6e07e23bf8cab0742e653cf494e1f36af3a8b4e551039d4775a64fa22", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ] - } - ], - "stateGasUsed": "0x47b80", - "status": "0x1" - } - ], - "from": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "gasUsed": "0x10cd9c", - "logs": [ - { - "address": "0xfffffffffffffffffffffffffffffffffffffffe", - "blockHash": "0x0f9eafe34605c35d638a2799f2a6ac39f6874e9451037fa9df6286e6e79d784b", - "blockNumber": "0xade", - "blockTimestamp": "0x6a99ed82", - "data": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - "logIndex": "0x0", - "removed": false, - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000a9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "0x000000000000000000000000753d91eef10c8e26924aabcb0ad73052f8fc4522" - ], - "transactionHash": "0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019", - "transactionIndex": "0x0" - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x0f9eafe34605c35d638a2799f2a6ac39f6874e9451037fa9df6286e6e79d784b", - "blockNumber": "0xade", - "blockTimestamp": "0x6a99ed82", - "data": "0x00000000000000000000000000000000000000000000000000000000000000002dd32b6609c5a8e80505ac44c5cb8e9f712115c1f63f59b18be08fc9b9250bf4", - "logIndex": "0x1", - "removed": false, - "topics": [ - "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", - "0x1995ffa6e07e23bf8cab0742e653cf494e1f36af3a8b4e551039d4775a64fa22", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "transactionHash": "0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019", - "transactionIndex": "0x0" - } - ], - "logsBloom": "0x00000000000000000000000000800000000000000000000000000000000400000000000000000000000000000000000000800000000000000000000100000000000000000000000000000008000000000000000000000000000000040000000010000000020000002000000000000800000000000000000000000010000000000000000000000000000000000000000040000000000000020000000000000000000000000000000000200000000000000000000000000000000000000000000000000002008400000008000000000000000000000000000200000000000020400000000000000000400000000040000000000000000000000000010000000000", - "payer": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "status": "0x1", - "to": "0xa9b53a21f77fc3e89b8297d1ea79dd4246fa162b", - "transactionHash": "0x23f915bf3e8ac36baa39f1f9dcf72e219df996eb774babb22138beae889ff019", - "transactionIndex": "0x0", - "type": "0x6" - }, - "simulate": { - "executionError": null, - "executionStatus": null, - "frames": null, - "gasUsed": null, - "maxCost": "0x5cd70bfe254da", - "payer": null, - "prefixShape": "SelfVerify", - "valid": false, - "violation": "Invalid Transaction: Nonce mismatch: expected 23, got 10" - } -} diff --git a/test/fixtures/chain/0x26ed3e31ef89ea6f6713dafb34e952ca510bb30634d3e8c0ebc5abc9dadc12a3.json b/test/fixtures/chain/0x26ed3e31ef89ea6f6713dafb34e952ca510bb30634d3e8c0ebc5abc9dadc12a3.json new file mode 100644 index 0000000..5e7a9c3 --- /dev/null +++ b/test/fixtures/chain/0x26ed3e31ef89ea6f6713dafb34e952ca510bb30634d3e8c0ebc5abc9dadc12a3.json @@ -0,0 +1,98 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:58.299Z", + "block": "49563", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x90d00ef2910e19aebe3120a4b0f4ec9942036f560493858668c60861ba3cec7e", + "blockNumber": "0xc19b", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x1388", + "mode": "0x1", + "stateGasLimit": "0x2fd00", + "to": null, + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x1388", + "mode": "0x2", + "stateGasLimit": "0x0", + "to": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "value": "0x1" + } + ], + "from": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "hash": "0x26ed3e31ef89ea6f6713dafb34e952ca510bb30634d3e8c0ebc5abc9dadc12a3", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x77359400", + "maxPriorityFeePerGas": "0x1", + "nonceKeys": [ + "0xc0b3d0a", + "0xc0b3d0b" + ], + "nonceSeq": "0x0", + "sender": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x01ec5110f3c86e5117a6801b277b3783f01244abd4455d8c05bca352445c55c6f0297e5052f3e0217b7589f0f30f2192062e45652b8d383d399921b2d13137aadf", + "signer": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x90d00ef2910e19aebe3120a4b0f4ec9942036f560493858668c60861ba3cec7e", + "blockNumber": "0xc19b", + "contractAddress": null, + "cumulativeGasUsed": "0x352c6", + "effectiveGasPrice": "0x8", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x2fd00", + "status": "0x1" + }, + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + } + ], + "from": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "gasUsed": "0x352c6", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "payer": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "status": "0x1", + "to": null, + "transactionHash": "0x26ed3e31ef89ea6f6713dafb34e952ca510bb30634d3e8c0ebc5abc9dadc12a3", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x195e49749f800", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac.json b/test/fixtures/chain/0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac.json new file mode 100644 index 0000000..a63a8d1 --- /dev/null +++ b/test/fixtures/chain/0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac.json @@ -0,0 +1,211 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:58.498Z", + "block": "8155", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xaa6ffdf738bf421870c9e5720564a1ff952a342892b4debe7b3a92ddb6fd8330", + "blockNumber": "0x1fdb", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0xe06e601046631b3e1dc3943f4f7d058de6da6772644dca7af1a42500c810b2f8000000000000214b2dd32b6609c5a8e80505ac44c5cb8e9f712115c1f63f59b18be08fc9b9250bf4", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x0000000000000000000000000000000000008272", + "value": "0x0" + }, + { + "data": "0x142c5f6d9a8738a54e2ee90f4409a6af834e9104df472933979972022c350cec1b8366bedf0be780c8eddfbc9a220c5e23f9394a2e4c9cb95b9715362587ae440742b159781b0b9b6a9f8b54b592d10ead22e27f91be0a381e59ccad3b7e684915b39b0e9e8458b919b3d632bbd81ddab4a2206e7a84493f95925ae811ef714e2da7102c987266852bb48814048f69134d5966c152093c34965845fb1a61d26e2395f196c441a0de516e35e00d7c04d6d4eeb3b0b804584864ab5c14dca148422e14cdbae16846eb9719ea3a90798843ccf6e40446fe01e99eaa2bbec6bac8232117f5c268e6b8f2eabe99cbff62087937230654d484c73d2723d8a76aaa74a9", + "flags": "0x3", + "gasLimit": "0x4e200", + "mode": "0x1", + "stateGasLimit": "0x2fd00", + "to": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "value": "0x0" + }, + { + "data": "0x921fcac72dd32b6609c5a8e80505ac44c5cb8e9f712115c1f63f59b18be08fc9b9250bf4000000000000000000000000000000000000000000000000000000000000214b0000000000000000000000000000000000000000000000000000000000000000216043a52690fc9df26a1cafc64da798262b2e15c05d28325da41c71382734921479940291777d1f0f3d58bf8a46cf21c33e7cfcd8b9876766a33d3a39b3e8212e0eb2f5b5991e31cdde9b66c0da09e603e20744ec9c708e93ba4409bc3e8cbc2a50d6a48d22560f434a83f4f5d13b5dbdbe9c9ea9756518c97795baf29ce8cd265eaed4eaf0cf73d07a90560bab4c1c65975ac7d2362396c0a93c5d14d1dabc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000278b8a2fc5fd3cc62c5e181bf22462569554b197", + "flags": "0x0", + "gasLimit": "0x155cc0", + "mode": "0x2", + "stateGasLimit": "0x86470", + "to": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "value": "0x0" + } + ], + "from": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "hash": "0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x3b9aca0e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x1479940291777d1f0f3d58bf8a46cf21c33e7cfcd8b9876766a33d3a39b3e821", + "0x2e0eb2f5b5991e31cdde9b66c0da09e603e20744ec9c708e93ba4409bc3e8cbc" + ], + "nonceSeq": "0x0", + "sender": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x018481b6062625571762def4943c90896074e97842377cd3bc64a21f120457a20e131ce869d65117c0ceaef18a64786d743ae1aec8a3a5eed8f95cb2b0d6c35b9e", + "signer": "0x278b8a2fc5fd3cc62c5e181bf22462569554b197" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xaa6ffdf738bf421870c9e5720564a1ff952a342892b4debe7b3a92ddb6fd8330", + "blockNumber": "0x1fdb", + "contractAddress": null, + "cumulativeGasUsed": "0x180682", + "effectiveGasPrice": "0x3b9aca07", + "frameReceipts": [ + { + "gasUsed": "0x15cb", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x3e2dd", + "logs": [], + "stateGasUsed": "0x2fd00", + "status": "0x1" + }, + { + "gasUsed": "0xc27a9", + "logs": [ + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "data": "0x", + "topics": [ + "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", + "0x1479940291777d1f0f3d58bf8a46cf21c33e7cfcd8b9876766a33d3a39b3e821" + ] + }, + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "data": "0x", + "topics": [ + "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", + "0x2e0eb2f5b5991e31cdde9b66c0da09e603e20744ec9c708e93ba4409bc3e8cbc" + ] + }, + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "data": "0x00000000000000000000000000000000000000000000000000000000000000011ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", + "topics": [ + "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", + "0x2a50d6a48d22560f434a83f4f5d13b5dbdbe9c9ea9756518c97795baf29ce8cd", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + }, + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "data": "0x00000000000000000000000000000000000000000000000000000000000000021ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", + "topics": [ + "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", + "0x265eaed4eaf0cf73d07a90560bab4c1c65975ac7d2362396c0a93c5d14d1dabc", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ], + "stateGasUsed": "0x47b80", + "status": "0x1" + } + ], + "from": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "gasUsed": "0x180682", + "logs": [ + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "blockHash": "0xaa6ffdf738bf421870c9e5720564a1ff952a342892b4debe7b3a92ddb6fd8330", + "blockNumber": "0x1fdb", + "blockTimestamp": "0x6aa93353", + "data": "0x", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", + "0x1479940291777d1f0f3d58bf8a46cf21c33e7cfcd8b9876766a33d3a39b3e821" + ], + "transactionHash": "0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac", + "transactionIndex": "0x0" + }, + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "blockHash": "0xaa6ffdf738bf421870c9e5720564a1ff952a342892b4debe7b3a92ddb6fd8330", + "blockNumber": "0x1fdb", + "blockTimestamp": "0x6aa93353", + "data": "0x", + "logIndex": "0x1", + "removed": false, + "topics": [ + "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", + "0x2e0eb2f5b5991e31cdde9b66c0da09e603e20744ec9c708e93ba4409bc3e8cbc" + ], + "transactionHash": "0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac", + "transactionIndex": "0x0" + }, + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "blockHash": "0xaa6ffdf738bf421870c9e5720564a1ff952a342892b4debe7b3a92ddb6fd8330", + "blockNumber": "0x1fdb", + "blockTimestamp": "0x6aa93353", + "data": "0x00000000000000000000000000000000000000000000000000000000000000011ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", + "logIndex": "0x2", + "removed": false, + "topics": [ + "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", + "0x2a50d6a48d22560f434a83f4f5d13b5dbdbe9c9ea9756518c97795baf29ce8cd", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "transactionHash": "0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac", + "transactionIndex": "0x0" + }, + { + "address": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "blockHash": "0xaa6ffdf738bf421870c9e5720564a1ff952a342892b4debe7b3a92ddb6fd8330", + "blockNumber": "0x1fdb", + "blockTimestamp": "0x6aa93353", + "data": "0x00000000000000000000000000000000000000000000000000000000000000021ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", + "logIndex": "0x3", + "removed": false, + "topics": [ + "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", + "0x265eaed4eaf0cf73d07a90560bab4c1c65975ac7d2362396c0a93c5d14d1dabc", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "transactionHash": "0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000002000000004040000000000100000020200000100000000000800200000000000000000000000000080000000000000000000000000000000000000000000008000100000000000010000000000000000000000000000000008000000000040000000100000000000000000000000002000000000200000000000000008000000000000000000000020000000000000000000400000000080000000000000000000000000000000000000", + "payer": "0x8fdab78244c5fa43809d064fc93e6c0e5041971d", + "status": "0x1", + "to": null, + "transactionHash": "0x2b90c598179b0cb8fca70a1c7c9211ca9a27945eeaae6c51ad0d8d0d501b45ac", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x8f77e28c094ee", + "payer": null, + "prefixShape": null, + "valid": false, + "violation": "EIP-8272 recent-root reference expired: slot 8523 is outside the usable window at slot 136624" + } +} diff --git a/test/fixtures/chain/0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a.json b/test/fixtures/chain/0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a.json deleted file mode 100644 index f792065..0000000 --- a/test/fixtures/chain/0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "meta": { - "clientVersion": "ethrex/v23.0.0-hegota-testnet-31b5322665fd48f07337f6dd7cede21e7de7866a/x86_64-unknown-linux-gnu/rustc-v1.93.1", - "genesisHash": "0x7ca0f7358d127dc4a68983050eb88837a5f384225254d1b009fa87fbcd0f2332", - "capturedAt": "2026-09-06T16:10:34.778Z", - "block": "2787", - "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" - }, - "tx": { - "blobVersionedHashes": [], - "blockHash": "0x354d83ad60d7e156b186c2b1b49dcb5ce1d9abb163472b78126218a18fca084a", - "blockNumber": "0xae3", - "chainId": "0x1fcd", - "frames": [ - { - "data": "0x208385a22ddc8c8c8b955bfc668c306b2a8ec618bb9de2b3aed4b776cd58ad590d1106c216fe42dec2fc62a7f1d1616aa04907295e095ba96ff404ac90269a312aa80a42e8ecca08d9f2b658e6f8a4503ef733482740ebec538150137e930bbe22c620edfaa6035e7297b50d9cab6003a27b0f564048d6b91981ba77fc82dc47247a9d9d6028f9ef7ee9d7d5ebab4fb0c49e151a9eeb7a7a083e2870f0cf5b280b335200e43dc0f51e4567ee812ed2a8c0cd6683ae6fbcf90b6732b4489f84531f8f8c3fa704d4e0eae9e3b0b4b25499f5a6ffb6d08ef4f4326b240710e2c3bb1e1613577e566c524d837d08f3b6be2044b18adebfaf29071fb2ead962201b50", - "flags": "0x3", - "gasLimit": "0x4e200", - "mode": "0x1", - "stateLimit": "0x0", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "value": "0x0" - }, - { - "data": "0x921fcac72dd32b6609c5a8e80505ac44c5cb8e9f712115c1f63f59b18be08fc9b9250bf40000000000000000000000000000000000000000000000000000000000000af100000000000000000000000000000000000000000000000000000000000000002e336247aaf32c84c96cf86110e28720d8a08ad924deca014440ff584859603b055b6c2720e71fbe4d5fa4ad130f4f7b68879ee7d062d0e21af30c5e8ce5839c08cda6582e3ed667ed4b907d27093659da30882f1d1437ee86125664ecf6f9ce2a50d6a48d22560f434a83f4f5d13b5dbdbe9c9ea9756518c97795baf29ce8cd265eaed4eaf0cf73d07a90560bab4c1c65975ac7d2362396c0a93c5d14d1dabc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b1a2bc2ec500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000278b8a2fc5fd3cc62c5e181bf22462569554b197", - "flags": "0x0", - "gasLimit": "0x155cc0", - "mode": "0x2", - "stateLimit": "0x86470", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "value": "0x0" - } - ], - "from": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "hash": "0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a", - "maxFeePerBlobGas": "0x0", - "maxFeePerGas": "0x3b9aca0e", - "maxPriorityFeePerGas": "0x3b9aca00", - "nonceKeys": [ - "0x55b6c2720e71fbe4d5fa4ad130f4f7b68879ee7d062d0e21af30c5e8ce5839c", - "0x8cda6582e3ed667ed4b907d27093659da30882f1d1437ee86125664ecf6f9ce" - ], - "nonceSeq": "0x0", - "recentRootReferences": [ - { - "root": "0x2dd32b6609c5a8e80505ac44c5cb8e9f712115c1f63f59b18be08fc9b9250bf4", - "slot": "0xaf1", - "sourceId": "0xb08f15750c491f4cfd65215c11a33b3962903a8896fc586bbd7c697851c26e20" - } - ], - "sender": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "signatures": [ - { - "msg": "0x", - "scheme": "0x1", - "signature": "0x012ec18950f5e00c98e495470f9730dcb5f362d09042b11146ca4dac89b7355c0047f6d8a09ae0832287bc5b63132f319f9e4f65083e421a2cee86c35c35f68a3a", - "signer": "0x278b8a2fc5fd3cc62c5e181bf22462569554b197" - } - ], - "transactionIndex": "0x0", - "type": "0x6" - }, - "receipt": { - "blockHash": "0x354d83ad60d7e156b186c2b1b49dcb5ce1d9abb163472b78126218a18fca084a", - "blockNumber": "0xae3", - "contractAddress": null, - "cumulativeGasUsed": "0x159ef6", - "effectiveGasPrice": "0x3b9aca07", - "frameReceipts": [ - { - "gasUsed": "0x47e79", - "logs": [], - "stateGasUsed": "0x0", - "status": "0x1" - }, - { - "gasUsed": "0xc27a9", - "logs": [ - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x", - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x055b6c2720e71fbe4d5fa4ad130f4f7b68879ee7d062d0e21af30c5e8ce5839c" - ] - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x", - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x08cda6582e3ed667ed4b907d27093659da30882f1d1437ee86125664ecf6f9ce" - ] - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x00000000000000000000000000000000000000000000000000000000000000011ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", - "topics": [ - "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", - "0x2a50d6a48d22560f434a83f4f5d13b5dbdbe9c9ea9756518c97795baf29ce8cd", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ] - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x00000000000000000000000000000000000000000000000000000000000000021ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", - "topics": [ - "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", - "0x265eaed4eaf0cf73d07a90560bab4c1c65975ac7d2362396c0a93c5d14d1dabc", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ] - } - ], - "stateGasUsed": "0x47b80", - "status": "0x1" - } - ], - "from": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "gasUsed": "0x159ef6", - "logs": [ - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x354d83ad60d7e156b186c2b1b49dcb5ce1d9abb163472b78126218a18fca084a", - "blockNumber": "0xae3", - "blockTimestamp": "0x6a99eda0", - "data": "0x", - "logIndex": "0x0", - "removed": false, - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x055b6c2720e71fbe4d5fa4ad130f4f7b68879ee7d062d0e21af30c5e8ce5839c" - ], - "transactionHash": "0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a", - "transactionIndex": "0x0" - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x354d83ad60d7e156b186c2b1b49dcb5ce1d9abb163472b78126218a18fca084a", - "blockNumber": "0xae3", - "blockTimestamp": "0x6a99eda0", - "data": "0x", - "logIndex": "0x1", - "removed": false, - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x08cda6582e3ed667ed4b907d27093659da30882f1d1437ee86125664ecf6f9ce" - ], - "transactionHash": "0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a", - "transactionIndex": "0x0" - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x354d83ad60d7e156b186c2b1b49dcb5ce1d9abb163472b78126218a18fca084a", - "blockNumber": "0xae3", - "blockTimestamp": "0x6a99eda0", - "data": "0x00000000000000000000000000000000000000000000000000000000000000011ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", - "logIndex": "0x2", - "removed": false, - "topics": [ - "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", - "0x2a50d6a48d22560f434a83f4f5d13b5dbdbe9c9ea9756518c97795baf29ce8cd", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "transactionHash": "0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a", - "transactionIndex": "0x0" - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x354d83ad60d7e156b186c2b1b49dcb5ce1d9abb163472b78126218a18fca084a", - "blockNumber": "0xae3", - "blockTimestamp": "0x6a99eda0", - "data": "0x00000000000000000000000000000000000000000000000000000000000000021ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", - "logIndex": "0x3", - "removed": false, - "topics": [ - "0x1c9386c619e61f45f16a19541b370266f8eb6fd22d241ff010e03cc31ea82368", - "0x265eaed4eaf0cf73d07a90560bab4c1c65975ac7d2362396c0a93c5d14d1dabc", - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "transactionHash": "0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a", - "transactionIndex": "0x0" - } - ], - "logsBloom": "0x00000000000000010001000000000000000000002000000000000000000400000000000000000000000000000080000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000100000020200000000000000000800200000000000000000000000000080000000000000000000000000000000000000000000000000120000000000010000000000100040000000000000000000000000000040000000000000000000000000000000002400000000000000000000000000000000000000000000000020000020000000000000400000000000000000000000000000000000000000000000", - "payer": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "status": "0x1", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "transactionHash": "0x5ad114d29ed7e9326bbc300b951c6ee9a59c648985dbba9497dfea454cccaa4a", - "transactionIndex": "0x0", - "type": "0x6" - }, - "simulate": { - "executionError": null, - "executionStatus": null, - "frames": null, - "gasUsed": null, - "maxCost": "0x82dbbb4fbef38", - "payer": null, - "prefixShape": null, - "valid": false, - "violation": "EIP-8272 recent-root reference expired: slot 2801 is outside the usable window at slot 42522" - } -} diff --git a/test/fixtures/chain/0x624ad97d70719a3d712610280de5b96f41fbe309e1facd193e06f2e659e1e490.json b/test/fixtures/chain/0x624ad97d70719a3d712610280de5b96f41fbe309e1facd193e06f2e659e1e490.json new file mode 100644 index 0000000..fe15d33 --- /dev/null +++ b/test/fixtures/chain/0x624ad97d70719a3d712610280de5b96f41fbe309e1facd193e06f2e659e1e490.json @@ -0,0 +1,97 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:57.647Z", + "block": "67", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x6ea493081bad2a2aad28196d61a0905e77c80ecb7ef0849a787abcfd33bddc01", + "blockNumber": "0x43", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x2", + "stateGasLimit": "0x2cd2f", + "to": "0xdead000000000000000000000000000000000064", + "value": "0x64" + } + ], + "from": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "hash": "0x624ad97d70719a3d712610280de5b96f41fbe309e1facd193e06f2e659e1e490", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x773ee65c", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x0" + ], + "nonceSeq": "0x65", + "sender": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x00f253921baffffa8309de7f4203c7b4d9b532d7e28ef86c3fd7c954772ff564237e5d132dc508da5fd6281b198506d1a95ab4275e26142caae70496964b21a5ed", + "signer": "0x189abc0be7003dd1238b7a404325b440c70e88e4" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x6ea493081bad2a2aad28196d61a0905e77c80ecb7ef0849a787abcfd33bddc01", + "blockNumber": "0x43", + "contractAddress": null, + "cumulativeGasUsed": "0xcff2", + "effectiveGasPrice": "0x3b9cd428", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x7530", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x0" + } + ], + "from": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "gasUsed": "0xcff2", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "payer": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "status": "0x0", + "to": null, + "transactionHash": "0x624ad97d70719a3d712610280de5b96f41fbe309e1facd193e06f2e659e1e490", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x2404f59176fec", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 10819, got 101" + } +} diff --git a/test/fixtures/chain/0x68f20f09eda8ac96c999e14db7be262a786d614d20cb80edea1528ce7be19200.json b/test/fixtures/chain/0x68f20f09eda8ac96c999e14db7be262a786d614d20cb80edea1528ce7be19200.json new file mode 100644 index 0000000..d4abb53 --- /dev/null +++ b/test/fixtures/chain/0x68f20f09eda8ac96c999e14db7be262a786d614d20cb80edea1528ce7be19200.json @@ -0,0 +1,90 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:59.216Z", + "block": "74", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x32a9537a567e048d8efac8ff83ad549c1f03eb731279ee3f54b5604e1ccd3248", + "blockNumber": "0x4a", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x17e80", + "to": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x61a80", + "mode": "0x0", + "stateGasLimit": "0x5fa00", + "to": "0x5470d828bbbc72d4bf0d982e2fc8bf230dfc8c8c", + "value": "0x0" + } + ], + "from": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "hash": "0x68f20f09eda8ac96c999e14db7be262a786d614d20cb80edea1528ce7be19200", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x77393ec8", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x82502001" + ], + "nonceSeq": "0x0", + "sender": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "signatures": [], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x32a9537a567e048d8efac8ff83ad549c1f03eb731279ee3f54b5604e1ccd3248", + "blockNumber": "0x4a", + "contractAddress": null, + "cumulativeGasUsed": "0x874db", + "effectiveGasPrice": "0x3b9b976d", + "frameReceipts": [ + { + "gasUsed": "0x6d", + "logs": [], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0xc8e8", + "logs": [], + "stateGasUsed": "0x5fa00", + "status": "0x1" + } + ], + "from": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "gasUsed": "0x874db", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "payer": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "status": "0x1", + "to": null, + "transactionHash": "0x68f20f09eda8ac96c999e14db7be262a786d614d20cb80edea1528ce7be19200", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x6fbaaa3314cb0", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4.json b/test/fixtures/chain/0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4.json new file mode 100644 index 0000000..99fe34e --- /dev/null +++ b/test/fixtures/chain/0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4.json @@ -0,0 +1,124 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:58.099Z", + "block": "70", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xd7830406ac1a76651f480038f43a2f2ee87c62f558d585ca08aa6a7abce7fadd", + "blockNumber": "0x46", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x17e80", + "to": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x2", + "stateGasLimit": "0x2cd30", + "to": "0xc0de000000000000000000000000000000000064", + "value": "0x64" + } + ], + "from": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "hash": "0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x773bd374", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x81410068" + ], + "nonceSeq": "0x0", + "sender": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x005688a4a41332f476c46980e514e177af9a568475f9d5813faa6c27634d832a537c2707a260b937769cd9fd70b781caa6edf611b04ba6b8057b692a73459ccf09", + "signer": "0x189abc0be7003dd1238b7a404325b440c70e88e4" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xd7830406ac1a76651f480038f43a2f2ee87c62f558d585ca08aa6a7abce7fadd", + "blockNumber": "0x46", + "contractAddress": null, + "cumulativeGasUsed": "0x1ea76", + "effectiveGasPrice": "0x3b9c27e5", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0xbb8", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000189abc0be7003dd1238b7a404325b440c70e88e4", + "0x000000000000000000000000c0de000000000000000000000000000000000064" + ] + } + ], + "stateGasUsed": "0x0", + "status": "0x1" + } + ], + "from": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "gasUsed": "0x1ea76", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0xd7830406ac1a76651f480038f43a2f2ee87c62f558d585ca08aa6a7abce7fadd", + "blockNumber": "0x46", + "blockTimestamp": "0x6aa86d29", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000189abc0be7003dd1238b7a404325b440c70e88e4", + "0x000000000000000000000000c0de000000000000000000000000000000000064" + ], + "transactionHash": "0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000002000000010000000000008000000000000000000000000000000100000000000000000200000002000000000000000000000000000000000000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000008000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "payer": "0x189abc0be7003dd1238b7a404325b440c70e88e4", + "status": "0x1", + "to": null, + "transactionHash": "0x6f07b8e74f8bdd8d69c2df20a30edb4d2d29e9e7b8c8d49c43cffeb65f71e8e4", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x2f28010d47fa8", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0.json b/test/fixtures/chain/0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0.json new file mode 100644 index 0000000..dc59775 --- /dev/null +++ b/test/fixtures/chain/0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0.json @@ -0,0 +1,117 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:59.869Z", + "block": "72", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xd3a615f2ce8c5572e37c106e5895c4037a71ebffcf53e1600037d5b267ff0dc9", + "blockNumber": "0x48", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x17e80", + "to": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x2", + "stateGasLimit": "0x2cd30", + "to": "0xbeef000000000000000000000000000000000002", + "value": "0x64" + } + ], + "from": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "hash": "0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x773a5cd0", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x82500000" + ], + "nonceSeq": "0x0", + "sender": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "signatures": [], + "transactionIndex": "0x1", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xd3a615f2ce8c5572e37c106e5895c4037a71ebffcf53e1600037d5b267ff0dc9", + "blockNumber": "0x48", + "contractAddress": null, + "cumulativeGasUsed": "0x94472", + "effectiveGasPrice": "0x3b9bd601", + "frameReceipts": [ + { + "gasUsed": "0x6d", + "logs": [], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0xbb8", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040456702b7d2725b8fdc5c7b33ca21eab350b42a", + "0x000000000000000000000000beef000000000000000000000000000000000002" + ] + } + ], + "stateGasUsed": "0x2cd30", + "status": "0x1" + } + ], + "from": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "gasUsed": "0x4a233", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0xd3a615f2ce8c5572e37c106e5895c4037a71ebffcf53e1600037d5b267ff0dc9", + "blockNumber": "0x48", + "blockTimestamp": "0x6aa86d35", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "logIndex": "0x1", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040456702b7d2725b8fdc5c7b33ca21eab350b42a", + "0x000000000000000000000000beef000000000000000000000000000000000002" + ], + "transactionHash": "0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0", + "transactionIndex": "0x1" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000010000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000002000000000008000000000000000010000000000000000000000000400000000000000000000000000000000000000000000000040000000000000000", + "payer": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "status": "0x1", + "to": null, + "transactionHash": "0x7d656faf6de87e98fabb12ef4505f42491e14c31978f019263a68ab7eb2d19c0", + "transactionIndex": "0x1", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x2eaeaf07b5260", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0x80ca74022f3cddddd54ff57bd0e454b8d41af5ee654f7ff609b09dd75ea55d03.json b/test/fixtures/chain/0x80ca74022f3cddddd54ff57bd0e454b8d41af5ee654f7ff609b09dd75ea55d03.json new file mode 100644 index 0000000..7e01c29 --- /dev/null +++ b/test/fixtures/chain/0x80ca74022f3cddddd54ff57bd0e454b8d41af5ee654f7ff609b09dd75ea55d03.json @@ -0,0 +1,97 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:58.754Z", + "block": "49562", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x25095437bde531983f3bb8f52d7eadf998ed1e548c4848878549635bbd6de662", + "blockNumber": "0xc19a", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x1388", + "mode": "0x1", + "stateGasLimit": "0x17e80", + "to": null, + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x1388", + "mode": "0x2", + "stateGasLimit": "0x0", + "to": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "value": "0x1" + } + ], + "from": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "hash": "0x80ca74022f3cddddd54ff57bd0e454b8d41af5ee654f7ff609b09dd75ea55d03", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x77359400", + "maxPriorityFeePerGas": "0x1", + "nonceKeys": [ + "0xc1b3d0600000000000000000000000000000000000000000000000000000077" + ], + "nonceSeq": "0x0", + "sender": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x016086c3a9965bda2d1cda557e02731f7203b275fd2747303321b8a76bc75fc3f148a0dfa8c194b427f983b94da7460228bc846b955e255c74ba116f64d2b41981", + "signer": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x25095437bde531983f3bb8f52d7eadf998ed1e548c4848878549635bbd6de662", + "blockNumber": "0xc19a", + "contractAddress": null, + "cumulativeGasUsed": "0x1da06", + "effectiveGasPrice": "0x8", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + } + ], + "from": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "gasUsed": "0x1da06", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "payer": "0xa7aff3568865020aa95d4ee82eee7c4d7ec01a5c", + "status": "0x1", + "to": null, + "transactionHash": "0x80ca74022f3cddddd54ff57bd0e454b8d41af5ee654f7ff609b09dd75ea55d03", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0xe3db86dd6800", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12.json b/test/fixtures/chain/0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12.json new file mode 100644 index 0000000..f758c0e --- /dev/null +++ b/test/fixtures/chain/0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12.json @@ -0,0 +1,124 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:46:40.811Z", + "block": "129105", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xab3c27a34fa0935016486cceddb4c5b04b214de3bd69ee8b694502f4584fdc42", + "blockNumber": "0x1f851", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": null, + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x2", + "stateGasLimit": "0x30d40", + "to": "0xa34b29e9eab1ba2182fd607ce3c52de431b5cae9", + "value": "0x38d7ea4c68000" + } + ], + "from": "0x23d3957be879aba6ca925ee4f072d1a8c4e8c890", + "hash": "0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x4f5572ce", + "maxPriorityFeePerGas": "0x4f5572c0", + "nonceKeys": [ + "0x0" + ], + "nonceSeq": "0x0", + "sender": "0x23d3957be879aba6ca925ee4f072d1a8c4e8c890", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x01566aa7fb96b7bf24344abb0422b7b14343b19ff07a827db6dfd685bb31d3f13a63ef3c952ef670d2de5695be2832780d3154a5b652bea947a4f87b64f7268e4c", + "signer": null + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xab3c27a34fa0935016486cceddb4c5b04b214de3bd69ee8b694502f4584fdc42", + "blockNumber": "0x1f851", + "contractAddress": null, + "cumulativeGasUsed": "0x33326", + "effectiveGasPrice": "0x4f5572c7", + "frameReceipts": [ + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0xbb8", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x00000000000000000000000000000000000000000000000000038d7ea4c68000", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000023d3957be879aba6ca925ee4f072d1a8c4e8c890", + "0x000000000000000000000000a34b29e9eab1ba2182fd607ce3c52de431b5cae9" + ] + } + ], + "stateGasUsed": "0x2cd30", + "status": "0x1" + } + ], + "from": "0x23d3957be879aba6ca925ee4f072d1a8c4e8c890", + "gasUsed": "0x33326", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0xab3c27a34fa0935016486cceddb4c5b04b214de3bd69ee8b694502f4584fdc42", + "blockNumber": "0x1f851", + "blockTimestamp": "0x6ab4f0ab", + "data": "0x00000000000000000000000000000000000000000000000000038d7ea4c68000", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000023d3957be879aba6ca925ee4f072d1a8c4e8c890", + "0x000000000000000000000000a34b29e9eab1ba2182fd607ce3c52de431b5cae9" + ], + "transactionHash": "0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000002000000000000000000000000080000000000010000000000000000000000000000000000000000000010400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000008000000000000002000000000000000000000000000400000000000000000000200000000000000000000000000000000000000000000", + "payer": "0x23d3957be879aba6ca925ee4f072d1a8c4e8c890", + "status": "0x1", + "to": null, + "transactionHash": "0xa05de9a4f929ac36a2d6b7ed09bdc6506656b3ac0c374c85a34f3b9e6954fc12", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x192e9b1ad2294", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be.json b/test/fixtures/chain/0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be.json deleted file mode 100644 index 53d32c5..0000000 --- a/test/fixtures/chain/0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "meta": { - "clientVersion": "ethrex/v23.0.0-hegota-testnet-31b5322665fd48f07337f6dd7cede21e7de7866a/x86_64-unknown-linux-gnu/rustc-v1.93.1", - "genesisHash": "0x7ca0f7358d127dc4a68983050eb88837a5f384225254d1b009fa87fbcd0f2332", - "capturedAt": "2026-09-06T16:11:05.209Z", - "block": "42241", - "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" - }, - "tx": { - "blobVersionedHashes": [], - "blockHash": "0xe28e3a2f9a8c83ea57c8851d67f50a47abde92cc387f3e0e03f6a68bc5c75f9d", - "blockNumber": "0xa501", - "chainId": "0x1fcd", - "frames": [ - { - "data": "0x323ceda005e5d526ae4244209781f3affd26ab91afce6f631dd1d622d5fd5621602038601f19015f395f515f55608b806100185f395ff3fe6009b0600ab09060021901146084576008b063c34781db60e01b5f526060600452602452615800604452615780806064525f81816084b55f610974806158045282615824b55f5490608420036080576161a4602081805f7306c03c98c9c6223787cc21ae0dd386312eb948135afa1560805762495a6360e31b905103608057005b5f80fd5b60035f80aa00323ceda005e5d526ae4244209781f3affd26ab91afce6f631dd1d622d5fd5621", - "flags": "0x0", - "gasLimit": "0xe290", - "mode": "0x0", - "stateLimit": "0x61a80", - "to": "0xe8f86fa0e5e3d24d9c5f82ae596d77472105ced7", - "value": "0x0" - }, - { - "data": "0x", - "flags": "0x3", - "gasLimit": "0x9c40", - "mode": "0x1", - "stateLimit": "0xafc8", - "to": null, - "value": "0x0" - }, - { - "data": "0x", - "flags": "0x0", - "gasLimit": "0x7a1200", - "mode": "0x0", - "stateLimit": "0x493e0", - "to": null, - "value": "0x0" - }, - { - "data": "0x", - "flags": "0x0", - "gasLimit": "0x186a0", - "mode": "0x2", - "stateLimit": "0x7a120", - "to": "0xf7917227874c6ee42b884b978e7dea0ac3fa9098", - "value": "0x640b5eece000" - } - ], - "from": "0x954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "hash": "0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be", - "maxFeePerBlobGas": "0x0", - "maxFeePerGas": "0xbebc200", - "maxPriorityFeePerGas": "0x5f5e100", - "nonceKeys": [ - "0x0" - ], - "nonceSeq": "0x0", - "recentRootReferences": [], - "sender": "0x954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "signatures": [ - { - "msg": "0x", - "scheme": "0x0", - "signature": "0x000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000045c00000000000000000000000000000000000000000000000000000000000004620000000000000000000000000000000000000000000000000000000000000454000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000011a000000000000000000000000000000000000000000000000000000000000022c000000000000000000000000000000000000000000000000000000000000033e00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000ce000000000000000000000000000000000000000000000000000000000000000200064eabd003a53e0000e133e002760b400335a53002cc82800053077004d53190006656f00021ef70052cf3e0006baee006293850045981f00768389006513ba00122d420060f2680075d2820043fd7a00762e6c000c94c1006c848d001bdccc005d59850053272d006832fd007651ad0026160300001730007f11dc00027e3700276c410034163e0042ac30006b4f3b00259c99002074ff003f61b70067d749001776f4004829b500201e98003ed0e100024ed0003f09f80005fac7004124990043df0f001d8286004a69c3002518ae005a80690058eb24000b511d00564f8d001315610055195a0037ac2400495d810035ba6d006308c30009d352007f6fb30066a6ac00080d63007d8545000e424a003582f000148808003dd24100388cbc00539a90005d5316004f85f1004fb93c000fe4b0002986ba0038653b007d183e000311a2004e5c2e0060265c006f59a5001364bf0069b0d3002683fe00657ec80051c65500328e1d0024579d0008ad660037876500464535003b4384007b9fc6004c5c20005208ab003640fa0001899e000c6e35007216f30007570a006ef5e10073ab8a00650f460047bcf0000b277f005a6e0b003dbf6a0027e844002280cf007bc48d00650852003b1177000d0bc50037f021002d52720077fa88001ada1f0007657b002a16bb005f59cf003eece20042bf0b001a649e007a0d94004aa9c600125ad400379b30004dd07a00583d4b005de2de0018a92e0004add2003133a9004721cf00414bf800439a670041149d00158481003650500037d2850074f7420025ef75005d33f8007bf31100639d2a00475e4d0072dbac004f296000578a4000469f72000cce180063ebf700271594002d1e9a005217940044561e001cbabe006f87250071528a004502330017592b00305cab00730e640033b7070079140e006e812d004dcd81004b91d0006dc3e2000c22d0005af84d003b4c19006c0c600071f5050017cd6f003fa17f005d5cf8003bd0d80035aca00040f0ad00164200007218a4004a007e0076703b000b9446005202a40060c8a5006ad8a6004bb88d000263950063b73b006c70c4003f6d5b00083bd3006c3232000f6fc00073da9e005b556e00520b5a007974070034c47d00648b12007da132006fb755007b435f003b40fe000b66bb003944ff001772eb002719f700237d43003a877d004eaf4c000772db00467029005981b0003deb960001d992004419b50007bb6f00160dc70006602a007ee1a3006d81c00067d3c400349df400012cc3003163b2003def7a001ec96e00595578000777bc007bf8ca003a9c13001622ff000973230009974f0011cb19002697c90059bf5200247869001ea9e1007cdd62005523c8001d1ffb001f60b90032566b0050fb14002bf1640053b33f00770125003f3f620011c4f40000000000000000000000000000000000000000000000000000000000000020002c8cda0010bcb00065483200672376003fe90900765f110032ccc10059e16300426bd80002032f0050ff11003aee2c0000217900392bf900142daa002b3a94003be0ce0075be25002df6950011be74005b9b3a001a73040061fe35001e08b8002e35bd002f94c9007fbd1300546c6b005ba3c200636f210068e4190065b69000271027000ea18d0056882700699eb2000a2de7003281580034d3450033c8eb00215e260017d277007b4677005e216e002e20f70018617a004607fe006140e30036b87c006c555800380edf006ad5f6004239f6007db6fc001f1ca4007d3c7b003785d10016317c006ea16b00257fd30056ca6a0046ec52000df124003ebaab00268f700026f04c007d9b2a004b6e5e003864680068f2f500494ed6006031c60036017b007c835d0015147400230b42000b03c1003146da0076dbde00669db0000609840066498600679eaa007e157f0077726c0072a982005b70d4000b1fb40045d9600072f0090025d05f00629df5003d11de003a90ca004394b400410916005bf674003f6d0200277a760071ac590011c3750001115b00722e4a00515dea0033efbe002387cd000e97cc0057b01b0027ae3300694e5400665b500018c6ec007334af0077a266007c40a4000e228d0053d111001cef6c001b59b1003509e3005161c6007286930022ec1f00719216002e3c180000d06600440dfa0006840700199fe6000130cd006a2d7b005e4d4d005a5a620003d287006ab4a400197de4003b1587003ccde0000cd4f3007a2fa300495ba5000a96f4003da7a400716f360069f3540074118c0014f3a6003f72f1004d871700702339006694450065ec07003dd888003808c7000771680014a36a000a45a300412273004c244000491dd7000bee660044a461005b9d7b007744cc003721d70065c3cd0062a3f30064fd3f004f53a300758ff8005b1c0e0045d80800185256002f380b005e23cf000df9300030993f0036e2ee006ab6ff0065eeca006e8f42006bf06d002188cc003d797b0045aa3a0001797e002e598500080c1000555d590009610d003c7631004a4677004df8b100293276000c23b60004fe5b00149d5a007e36c5001fc406003f97dc0064e0b8003c3b6e007bbc75002713e4003424d1003be6fc000118b00058d0ec003095330066e04c0009d3e500186f130051e7ea0019f1850045af7f003a118e000714ab00303b820001221b00468030007281e6001248d7002887100007dec7007fb23c007c4f9a004d59d600081f21000cd93e002c1064003957d4001a2c30004a820a00560e39006685fc003a0a9300774e540051ae6200108bc700399a67001249af007ca1760031f0660064b57d002c46e8002c1e35000ffd2f00674bee005bab8e0042df36007b3d3f0035fd680052e7e50003c679004987fa002e27ae000000000000000000000000000000000000000000000000000000000000002000357f18000369bb004d0e9c005d6c040054439e000075b10007d5130029a42e00298629006a5f8d0043da95000bc153000e01ee001d484d006d277f007d2778004f891e005ad65b001a6903000c5042006aacbf002e106a0012e1ca0077c380001b8c88002f0f70004bf8ca002290ef000b047b003c6f89006c30130031ee04001de78a000e2b5200798e470013b12f007b4d8d000dc7f90043a0f700248bd7003a74e10030dc7d00788a53000283210059ecfd001a264300769dd500149b21006056ce007a061e007f5bf4002ac648007d17e800223c25007c70d9006f989c00697ef90018a8d60071f2b4004e7afe002926e50039cab6005cc638007e5f4900330d3d001017a50056ef4c0078e0f80037fb0700543db9002f1f440048a8660021be240038df8500325918004fef1a005af4670062b8a9007a01d6002404a5002649c9004009e700594b17007094b5004c401600188aef002969a7002dfc560067aa6400416587007e98050069fc330029800000582ead00435de10074f795002d62ea0074c1990073f95e0072acf6006047ee003899810022e171003039370054f83f006abf7b002c82d1002e92ce0049b9b1007e0347002446c800288b6f0074bf3b00439781004de3f1004215780001c88a001168df00651467005e8a150006dc88006ad435003c93730012f4e60003ee820042cf40000d3d1a0001dbfc001c20d2000c7481006625dc00054c18003a4ac5004705be0030abd2003df58d0011c9db00355136004d10de0069c547001d0d9a00700cb0007a8e7b0001ac120020cb0e00704e18007e61560047bf8000309e870076056800184805003abecc003191410040046f005df394005131d2001b7481006a8466003c01c0004ea86c007cf36d007fd21f0016731400064d55003d1ab4006cb1450066034d007625ab00632ed7002f4867004a210c000e7037006f660e00453a43004d93d0004e3dab0024d44c0079cfca00518e2800001af10028d73500555c72005ba0f3006297d9002629d9006eb4ff0054339a0017b56d004b10810030464c0011eaa30051db1400613d0800520e840018fa9500561bb9006b9f900070bd6b006f794000120c570037dad6006175bb00136e0a00287558000f96120062ebb80074bcad00102b86005939d00051f24f00588a7c004fe25f000b274f001efec8001496bf000cff84007f39200043cc7500635a2a00404267000cca62000ab627006b17d90044c3c1003c64cc005bed3a000d9dcc002f9c40001e5392004d9de90074b2930021f6e6006de45e0059b417003845100023c0280040499e002be46000448cc0007f434100462f38006dd5e300768c7200346e360071ad250070513b0053a9280045d6b8006d110100499ba0001d50ef006a8482000929b40033650400687d4b007f317f00000000000000000000000000000000000000000000000000000000000000200023162b0004d6bb005dbe33006c150f006a697f004319b3000f51740073f0f500202bc8003e05870075210e001b99ec00260a9a003bc7a400255183000e69da0062e45700441c76005b8dfb00561bee003da983002acb1f002683c8006840ec000de2bb002f9c7d0034449a002a4367007c90b8002a6ca60006b1860054969e00752bb5003611f90015e7f900144662000e897e0053345c00656b8b0072a5ea004a35270025ace5002c79ef003b4d90003064f3006c261b00125f9f004e8f510034c93e0065073c007b6f8a002044cd0065d0ab006ca60800590a87003133a0006663290023d885005786b700223f0d0039d3920029d5570010635f00181a40002b553b002815580009454500615a4f000a0ee1005cfeab001777fc0062780600450cd1004158d20045ab3c00677b44002852c2000e6f2300528eb50066d6d4004b04cd0037650000630dc8007030f60052f639007f9dfe0059d535004d6a15007202c600372cc200152826000d341d0024b7fd00544fab005e7d7d0025ada1007363d900013acb001327d40065d2a10054d329002144a50051ecb70004471b0029c05b00549e55000f9fb6005638430068ab57001398a40077c1ce00283a4800421d5500412e63006e6f00005c93d6001c3e0d004ce74e0068c24f0063c38a0005586e0056aecb007035c4004f17b800319e190059b8ab00535a70002338fb0064f5720038e60c0054b49c00089b280034bc69003926d600671c7c005125b9004a6620004076a40062f3ff007161db0013beff004369670029b76b0050dc2f0069c4ca0045db840053b6180016969c0025e70e001b83e4002e9c3b005a4f8a00012029004e7568003c1394004077a90002a3560044e8230021d1ae000849d400215fe30003ffc30055e8d300212a16006d6c59006ab24a004e53be004c85f60070dbdd0008757a0063815d000c45ec003031eb0027940c001d4a8b001fdba9001275b3007de2e80044bd83003b2631003e0766007c77d60062210e004bda6e0050f23f0007b8ba00221776005d39b8002a8b390015ac01004e3f0e003e8760007b0de8002e3e800057737700149d42003ca5f8002b5080006698940052bac3005d720b0007c4330016e2fc0028bac30036ea8a0065d3610000508600087717006ddcd9004b54cb006ecc760013ebea007949740046c17f0072c741007be7de005a212b007091c600310ed6003603b700456bee000b0d990024ccf9007cf6530042681c002649ec00388cf7001e7fbb000d15090028f6e600032c9d00076f0600228cdc00429c6300485efd0079c9f6003115320015d16e001c360d003ed516004c4aaa004c899800041f400061426f005a6304007e318a00367c7a005e849a006c12230005712f0052f86d00219a750075b4730069724200300e2d004cf8060000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000ce00000000000000000000000000000000000000000000000000000000000000020000043e60018c9c7000c1b02006d9b5a005d975700385d0d002d7f3900197c3b000444b50064bc0b0042fc88000341a0006dc3010002a62c0012bc2800015bd80040aad8004db340004c08ed0063a470003d5645007cf5090021015c007bc8e200153769001fa71200655db6003f2786004628ef003ffe1e002ad9e1007d58f70047e53c00300318006c60950071c46700166d2600663a42006b95d70003f2eb002bd0a7002849b9003fab3d006054b90014dc0a003338cc002f702a0024d1ea004875d50021af80000d383e002cfccd001a99140004b7b700795801006d2e7e007f8cfb007e96ad005c627f00737be2005e6d24003543b7004d631f005e264800525c2f006c769100677118005c6dc8000881ce00038fc900295c130074d1970012dc95005c3955001b6f0f00556f38003ebe08003eeb0b0055787400683fce00613f77007f57eb000f0589004ac7f000006770003e39af006bf2bb00190676004508e2007c3dd900753d3f007e7b6a002417220003ec45001857df00230b230036c23200785a7a0034c67f0058b81400398f6e001091a9005b5e4100509bbf0010fa1f001d24270065006b005b4ec500791afb0041f673007b9e3600795b0e007ec26d0067d34900713b80007063c800158f9d005c46df0020520f001f4831003cdd4400135680003953d5007bae5f00187e9700464d72004499ce005a165c007e801d00622e1c003c34c200450099006ca5a3001ec3ec002c7adf003ba36e0051775b004ed782003728e300739683006809a7000158560060a0550058af8d007dc2d5003c4f52004f93620057b2d0001c919f003b57a7007a65fc00013fdf006a2ade003269b60023287d005a5fd3002c5f45006172a1000a7c8b0062829300659fbd00387c3a004a7fde0029414f007c751c001549720022ccfb0056fc31002860fd0071219d00022c6c0078191f004fecdc0001b4aa000f7177005bc07b003acbe2001e4d11006f558f005a7e1c007f8439005760ad0009432f0025ca3d00686951004f333200761c8e002a8ceb0037803e006c9a3c004e3c2800575e07005a6ed2003c5e0c005b035900366b51003a6de20023c9ec002cd7d2005497e000585cdc0000cb56003dd23f00633e970041326600596f0d002bb6c90007e43100745ac1005efe50000809df0060396b004c6e2c0065e9930036ada40020f3120000b20f004a5e8f00381c4d001e6739003400010035e8770036efdb00216494001d4a1d002dc33e0002fe3900458227001e4b4d00122f92002a23db0051a0a400483d4b000806a10055b6bb000c076a0025fee900544c180075ee42003f16c2005f131d004d9b9d00767df00063ac77002f46cd001f0d430000f130007454a9004a06a7002a5dc10045a190004c7b06005c56c10012ab88007a7ee50054f2a50000000000000000000000000000000000000000000000000000000000000020003c9b8700091484004c0a8d00317056000c5f270009c2bd005c9921002ed2b60027b19f005574c20040b1900039b54700312c80000aad0d0018b88c004f4a00003569690067913900455faf0019542b0066f7310074c18a006c8a8c004c06f6003cb8ae0038919d0031f107006ee745007b2f3200172163006ac387005e6d3a0018dd6b004f705b006c945f003d1a22000cdc5e0068f0bc003bf91f005db6f8000e241e00726f0b003107810070670d000f91ad00654149004ce3be006ea8000049f176003117eb00725a9500739db6001a06b0002a1cf60048e1920077747400493f63007185c1006e887c0061b5540032acea003f74e200548f0e0053d48a00002ae70055829500112ba50066aa99004e0d65003df8ad0076ac150045363b006fa30d00518218003069fd001609c8002e58a60042b4e9005ba71e0072ddb0001fceb800357015000e9a9e005cadf5001848e6001d7011003556400027ee730062b3ce00402b20006811e20062220b005fe71d0035359d00671acf00417f0f0069f1ba007f5a5c004ea1dd0016e29a001eabb0001b06500005c6fd0058118f0024a188007bcdf20018cd0f00441c21003bdef30017d730005ab1820065765400760816007812b6004577f8002b0dc4001cd1e30076b7c80001fd620044349000252f18007c7fc900065cc80028bb270001d7200047f4de00346851002d72b9001f8974005aeed5006c578b0076a989003533b6001eacec0027a7f40022743f001575410034560e003610660001ae650012d5a7006b46d50065b157002a98950039ba410067d03b00294d700002dab400717afa00665b28006fa286005646dd00185d2200265d4d00104e13005464b20042c6c400296fe90012ab34007a1ee2006239db004e47c700534b9200051edd0013ac8c00592ef6003ba025001b70fe0058b981005a4dc4005b8c080002102e007547d800379b23002e5836006559370057df00005a7892005da54d004682110042327b002f25f1004879e600426f88004059460045deee0012371b0055ae130011fccf003e45d30014931a0031fe4c003bffcc007475fe0077c2e8007d6910006d2337003dd415003faa22004bc91f0044253b00618b7c001c4ccb0026cf0f005302ff0044398f0035b068004dbe2d004e0fb8005c0f27006a5e87001a57f300520a54007132530038170e0079172b0071310000502bbd004b1fc8006c1baa002da05f0049af8b00411076006b0e12001614ba002ee11300015e8e007c868f0030282c003640bc006e2ed5000584f80048a0100017371d0013fddd005db6ec00724b86006354c700127c2700121bed004bb14a00737614001be00a000b0c3b00245d1a0012e96f004fa4af006715f5004d6aa50040403300632e2e007b7033007a2090001086460006232100217803000000000000000000000000000000000000000000000000000000000000002000008255003638070068b55f00785bc50071c88c005173d600714c06006e477d00414dd90036e8d000398e2b006d35ad0047f171007fa471007f98a1005b1063002af8f00042524f0019715300545036002406d3006248f00007c7bc001ad2f70007e458004684420019e0eb005bf347004cfbbf00363627001ba62200362a5c000ece32006f154200374a3000099aa0002744370035d2370013801b006e80130050c66100148a0b0044f036001f5f87004b151f0045857900561d0b0020bd64006af887002492ce0020ec790045c3a90077f15c003e6e3b0065ef9d0029584c006519c800404c4f007f066a007c950d0011bd150011773c0069be8f007067300019b77100117b8b004bb2c5005be3ba00136bf800135b08004eb826001bc1da002a4f300032ccf4002865d7006f96f9003302eb005e630b00254926006a4c900069e6c5001e45ae0040a35600175cea003b5d130060ee220047263f000610d600511a1e0001f454006e91db000e11ac0020e07b0071489700511c180011573c006dbb02000634dc00061384007d4f35003ed464005f5c2d004a6e160058cbb800568eb9005841be000838c200681e8200355998004b8c740072cb540025b210006e9aec00223b580014e0010029b4b10073a7a50038dd560000c899004230110013d2fd004856a0001cbdee004afb820011bdab0056d38b006b9dac0059d6d2007213bc0060900e000725e1000297a20026463d00718a0a0023400b00231e12002329cb0011c0e8004b6a0d004c1bce005768780045a35a0009bb8b004ef53700581580004f063c000a74b8005dcb6300492b6a007b17bd004c0a5b001336bd00270752007c5d270035575700006e40004e63f00028e2ea0032f0ea007755ee00068fa30037fe15003a794b00029f5a0017a177002b18fb002ac3af0007533e001de1a600531dad005e3ba1002fbaa0006309dc007703a000080ca3003c7a9a001995d1005b9cc20074816f004ee4f80039e3ad001696060032ef8c000f8a1a005b0da4006d8bc9000bede20018d6500017b7d40077f61e006c936d005ac3a5001a9da90031ddb70078c5c3002d8ea8006479810019db5100468a3b005ada800076d1b7003b51b80063fb78000ed4f8004f528c00555add0046715b001edd71000024c80021a263001d0ea100582324006a3e0700060cbc006b522c0029af120022e5d8005b3e9500340b1a001e6290001d186b00223ff90028757e0024eeb200466bc500699b95001983a8002f0a7e0028b4b9004d33a3002a78dd005c2bb4007767dc004ba77b0074b17b0050a2580053433d00290a8700254cc300033f76000b7f2e0073e35200395e3700762c8c0020a0e30012e9ba0043791e00651f0c0012f5740027a1840034ddb1006ee8f600300c420013e3ae0074380600544c430000000000000000000000000000000000000000000000000000000000000020002cb5fb002dd8e00043e892004ebe5e004b1072002a49e100766a88006a9479005ef011007a5ded000fd270002c0a58002d037c0026636c003849a300186f5e004b69810025e6f4007f8f940011b88d0054eb5e0045db3f0017074a005a401700452c37005185d30005156d003f30880074f1e700715d80001f74a0003f31b60019ecdf005b561400645f020034bf8500127dc50006ea5f002dd08c005e91e2002f737b000cb6f5004c8f8a004f09fb00301c550011eb4000337555004a61e8006894dc001e332800731f5c006efc7b0036202a007016ea000d346200018802003fa2b7006bc2190046eb4000091c83000c3bef001d856100342482001d22f5002ebceb000115870046d7ec00776987002eefa9006d1d2b00657a4b004dcb51003f19a80034b2090077613c002ec11a00765b8e006f9d2b0048686200709926003000240067947d002176cf0067df6d0007e92c001ca06c001479d2002d031d000b4e9d004676610033f403004fd046005ac15f00153af400716ca00075dd6600056c66007b0c750031bd7b00252b7f00525c1a00795e81005f613c0015d38d00177d370019d0a3004e30e800309ddc007b8adb0046409c0000fd1d005eae960073a8720002cd81006d512d00155cca0056d0300060e7ba001a5d06006a0d77003ffcef003b4eed006522110026537f0011e7af0054febf006454d900222a5e00353fbd001bd3ce00495c8f005f35e900757a4e006272990042d6f50058cb600029d3f9001b2594007f32e20017134c001edb7900414f5c000d04000076eea70025269f005dc8fe001beb67002f2cba0014fbe2003bf57f0073cec100221996003d0289005dfeaf00060e080023e39000390e9100483cff00550b3e004510600036ad0a0042071b003e6726005de6b6005b15d1007ba6f700187764001dfb65006bb1ff000b7c660075d00d0029eae5005367fa0049e8ef00102edc00407995001a8a460012049a0036fe34007f7b140039a12d0022b1f10033d19c003aad1a002718210073c88f00374f92001066750079ef1f0004f27b0004f8940020b237000d90c4006dfef300095aae002a80ab006c1e42001d82710056236b00565185001a167d005deeab0004e53600365f5800093322007794af00307f46007a6e48001ccea0001ec56b0039fb870000c0c2001d7875004dbebd0015cb78003482bf001cc095006ae08e003039f30006d46a00719dba000cb99400381af3005094fd00513e8500202ca2007b4d1f0067500c00361634001596e6002064fa00084d6f0049f992007dc495002fc5af001820210007367f001508fb00192cfd0077a2770062cd900040a03b002db6690008f494000709e6003ee1aa004e009b0057d66d003f132b003c546c004f49d600348fd6000f3da80010a5900054fa540053a8b00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000ce000000000000000000000000000000000000000000000000000000000000000200078440f0014b645001976780025e91c00454d29003c3c6e003730e10004aea0006543a5002e0c58000f087400452afb0032a9f8004c338c0076da0d005cda34006b9168002b3d9c0025a27900320adc000faf0c0042607d0031816b001e855b00340366001674fc0078de000013d39c00105f740020433a0069b6cc000e7a6f007f095c00078230003396c30007a9ae0046e1150015d11c0035f562002188340073973f00313ec800610194000bccbb0045517c006973b800544a2b0025f34f0005b02400469c71000796c300101d0c0032bca700733283006d26c40042b2a2001b33b80047f5a50060dc5a0025ea9b004736b4001b362200390aae0023f07d002b00250009bbf40023664c002641d5004ada090069c50b00662b7a000153f4007018af0004cdfe001598760048ff6200158806003b2617000e330d004161cc0075ce2000141dde006f33df001dadd1006c45c600798b14005a8bf5000f75c600667722006aebbb0004848f00027af9004947b70035e4330063e023007bdad8000d08e80040c89b00013ec300396c0600201dc7000379880050f1c7001327f1006cd9d7001e4210005a1a100057e83e007ccf36000f6d77004f55cc0066834c005b34190038e1800072d9170009bb120023c6740030b2730052835d00796e540016ca2800037125002608df007eac06005ae662003048f8002f053c0046501b004288e4002c756c0014a6760017c57f000d01ca002ce3e700457eda00589d5d0061309800086778002801ad004edbce006204cc003744720076c76100337a63001457fa0019b65a005f017a0057ff2c00390fec007ee181003ab9bb001c32b6003c086e004b8288007401500077f5d000034e5600702f30007efc91003916180055a738005cc1170005a6e600364eef005f6acc00547c07005543f0002f1c950046eef10069593900215d92003110ec004ae9a30041b01a000f3cd9000f24bd0005adb5005220ee004995de001fc1ac004fa3e40025cfc2001ab13b000d7a7d0033961400636fb10001345a000afb75006e5cb400415a6300304790002e7478005c95aa003f14b2007d5749000f4c880015f45c004ae0fb0036ca2e000fc54c00475edf001a798f00039d110032c4410040d8270051169d0018193c004801fd006bf867003162d6006c99ef00033bd5003c9b3e0004d8330042ec01007e8228005cb7d9001c0f020071593a00404a5700200459000ba5e600479f150030d983003c2621003a2e3200589975001b1b4d006f76f30042289800466dd300332f1b0014d571005d6507000c86f500497d80000c40d1001a5605002a0c9400364cef0065a1cd0047c0fb004919f9006af099001f7a01002b86f00056ab4400727515005cf6c4002c982f0004207c004f533b0008c195002e331500772981001aac50000000000000000000000000000000000000000000000000000000000000002000070d10001563fb001cfe83004444b40047fab9002a80670031c097001b81ab000c10620024ae4d00227a3200655a190005f12e004908d900034f5900143203000524f600753d260061c01d0060b4a9006f497a0075bb48000afa7a0044fb9c005c89e3002ef8f4007c1f4f001ada09007c6abc00691f7500543b17000462d300660c350062b3a5006e240a002295cc004608120030e24e006141a40074e7920034bdeb0027c7910053b0b2004e4f3c0054930c005d392100182ba800407b570061181d001caea3006a1e4400736214000128c80070015b00429cf1003aa63a00005014005c35840036d66b0045f01400268d500000bfd300478467004c66270073af390019431d0019371c003d5c43001145240004365a002d86470043f04d0031be3e0066416e001bcee100180c3b002f5c0f000846c900278c79004a30330066b9e3004a7d410060be85000dffac0049422f007bdda9003d146f0077512300428c1e001dd358003dd0870011b7d60038fd3f00168210001b3fc400347e87003da39a00670a78001c8a6c003cbd86004402520011122c007a8363003a4c7000494ad4003d4a72005db91900079bf4001494f1005e7a7e00699d500016c9480059b2d200046dc40028c043005fd111001e304f00606b060070ec26005cade80001acd100092200000a3e9c00517a27005ce3060036611900486b31000c371a00538504000e0d20003e807d004645b9004f1d9b005d58b20022c82b00611f470030235c001e36a300761ad50043246b0049f9f3001cbe3a00630cd5003f546d0076a103007ece9f00117bd60065e92e001a5a2f000e4995004c39c800300e0100017ee500045a3f0049bc72007f818e006e562a00755d2d002a78c2001cce36007cd9b8004dee990013c0570075ebad0064773e00049a3000341557000e3a1f00399d16001804fe00248f17006a791a00674216007bfbd00030db7f00604c1600415af2006d3dda006a9413002d2141002a7e9500186688007d82c0007b268600231706005ca5f90004d70d00210c860035467a0025ed710079e0f2003f6ed7007cd7fa003f0bf3006e060b0011b8240028b6110045d9b5005f940e0059ad7d001fe8e60001ca75004973b800560bad000a0503004a8c48005b20e70010b998005ebfa6002a6fb70042265d0047c0b10050e4c5000dbbe9000b501a0065217e007792e9002ec38d00364687004d98be000e59080057212a000b992800713c5f0011c14200507d0e000c2cc7007f27460038f7d00067aab4001b890e00001e0f0031b176001e4257005560ee0012d8f6006bc9be002289190034c89200045e3100545466001c858000770d150050f9ca0059ea7c0063fcca00789725006bc57e006d94fb006226940007ba3d005588ee007902ac0000d86a006e2036003337080000000000000000000000000000000000000000000000000000000000000020001454fc0068aeab00670b960032d14f000ae8a40067fee8005c9bf1002e51200018004d001eaa17003109490039d1ac007f95830066df660026583f0026ed7b006e0a0200091807000be66600314182004e611a0071aa7a007162fc003e4d81006d3e7a0044074a0023f47a003a3843004104910005f5e700245a3d0032faac00384d3d006a6737004de475003568600033496800062ac50022f485003d5169006873e2002d5b170050aea2002af6c40045a40d003c2cc10052900b0032bc92005ae0590023f235000d2675007f83ef00655945001afd1a007dd1b800133091003b9bc8005e99750047c5ba0068848e007e035100517b85005d7b05005c9a280079987a001b4adf000b4c8700673926007d8363006426130007efe3005a31fc00468a6700652c39003253c0001753d3007ccb670025cd63000800be001664bc003d0e41000f8e3300130263000e2a410005de8c004a5d8f000e999c0041b3320015d9b20071309a0061e9bd006f1495001757400014b62000343cd8005f871e003b9ad3007661250019e74a0051a1000040911f001b8e59000b1ccf0008b3af004a2a7a0017e00e000cc42a001ff87500508c980039dc4200424bbe000533c400050f7c00264fad00579ba40027666e002b543900162559004666390066545f005924ae006e13e60056c4c00060e9070008495700760e43001e7d07006804040008b32c0036943300508a5500033f610012d5ec00042fc8005b41d70015c64a0039a12c0027b27b00156b0d0025c876007195a8006bbc1d000193a0003ec484005f34260030a82c004aff1b0055b8e2006dc07b004c1d9a00015f36002114d20014ecae0018477400259120000468040070e247007b540a007fda220030e2480072bf60000a030f0028bd5b00223840002e92dd0042d396007cbbb6002ffa120071f64a006f677a001d9ac600086e00005b152a00778e6e00008eeb00044b9c007dbb4b00283fa50067ca8b0012b4e30056138b001e3aad00689479005f1dd300100533006a4b16001e37a1004d62a50044ea77006605af0050157900699deb003ecb0e006b937e003a3abc004c4ca70020b4be00300be1002da4e20065e7830051993d00129ecf005740e00014b9d0004b0c2a0010c88c0066f822002b259c0033f2c90071296e005724c10009bfd6005d04e600480f6c0008e09d002652ab0060166300221ec8001abfd40024cf73005231fc00631bf0003a421a00464ec000153b34007436e500526cc80065c79f00248a03000786440062bd6900049cab00274c410069aa6f0011ebe000576df0004ffc98006c26c9006c49ba002e86e6002197d6003937480018f0f700582c32001b76f60079c1bc000f6963001407da0046da0a00173eaa0008753c0025e788001430b8003af6400059de1300344c7d00000000000000000000000000000000000000000000000000000000000000200009baf8000062ba00402fa8007dfb57002e3fdb00134abd000ba53e00531c16002169f70027340200078ab4005568890056eaee00625cfb0060715700297699001a75370056c16b004d6d45001ab1e40019101c0073e6b3007e4749001ddf1a000d4c820035358b0021a039007b3be300530418007af1da000752020040a3f7004b86200065357d00736105007a59c4005c4c6c003bd052003c1477007856e1003d429700351fd40074ff4000340e0f005d1171002f033f005891fc000fa340004c9867001c4f700053912a00128c8f0061acdf003ddc33000237550078ad1a007986a400725d7800363865000fe3c4000a5f74005726c300728d3f004fea54000eec1f0059a7cf002839af006b319500706b900032811a0057eac30003d204000da80100341d0e00718bf400685593000e67c30030ef9800002009000baf170060a8fe007e48300056d91000690ff9003babb4004c372e00319ce4000477a00076589b004753d5002caa3000194d9e006b0be800669db10070ac530048be1c007cb6af007beeea0030d7ab0061d4b0005252ae004b3f57006c676c007abe02003602c5004c5c450001374b007e99100061d09300769a1b0018e003001d1f8f006c72810002c5d50016cb160006b81f00759c8d007ade2c004a4da10028c51e006f3f6e000e12df00122e010001a8f5002a42fa00200fa4001b6ee30048fa78007db11b006f9aa3002a7ae50008f7fe00599ca40047d04c005dcb580076dde80054d371004c6ec3000793f600444059007066e600767b29006fb5c9001a9df500012b050056ab600030fa880022a9c1007d73740004a0ae0019d7010049d3fa0055f1fe007d283e0061e21b0005c0c300640e2600009c92003236900070d7110075dafc0027ec35002c16aa00638882003088420014035e002459e000749a5c0031890e0034519b0073cc83003987dc0034e8740041d6810073be31002fe5ac006ef6f60000609500068c95007ea62300415b46006feea6006663e20065b530006055160067ea87000785e8000518f50061475400782e6c0016b8e2002839b2002b9310001df1c500587aee007e06b100104bf4001947970016a0c200463a8b00212901001a66c60008d8e20062f167007bb8040008a5930042fb91004b76d3002c49ca0011a26100238cf70027cd6b0070ca4d0045bb130027b5fe005a114200584b2000431481005c631800499396005d9b6b0037e903000cd902006d6671000340ec0045401d004409fe00003e16006fd9f6005c78d4002509ff000e34e5007566c00033631c0027fa2900076f76000d68e3001d2bed005176ab002d964000255bec003e4908006b2fae000d5d150048257e003d954400207a64003f673500738e3d0038ef590012ef9b00686aaa004e6bf90014c009002f9083005250b10000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000ce000000000000000000000000000000000000000000000000000000000000000200069fde80038f0f2001112970062d3c7007c8a94000bccee0006f3bf00578b6a0040508f006de506004a75f000696c4d0016e8c90022ed980050e897005b1529000ce77200345424002eac4a001a2e690049e3c20077bf990010396500190ea00033d8510012841200470d9400404beb0024462b0057d6d00059a2c10053d1630009372000332867000fa1ff0021141400768c6a00062e110025c3140012320c003eaf48007267a1003a57f0005ab766006416b9003a38990003e416000db97d0067419400301fcb004223520061017d0034976e00033c820007096e006cedbc005049be00760331007daa120046227300766d0a006bc78b0050c283002010d5004b0869006bebbf003acd2b0024961c0002df37000ea188006fdcb20035035500561f570025b98b00611dac004f9fbc006c644c001d335f0031a65f004b16da006d49ec00046bb00023eac100034af8005691420066da5f002fea820027335100274b2f0079e32800683189001c9b680047f3150000083c00167991003c12190075f9b6002d00a3003347db0016e3a500282d2a00159ac100340b91000bd4f10050c8d2002a525b002fbdc7000084af000082e60051e7f2005bb7be007fbf28005aba69007f932f0018481e006381630073cc7800605fc20016ed1c006f1cf200152e9c0029303400477d2700584ef50059bf6a004faf370073f4fb001637a7003907c5002586f8000f04340023ec5800182aeb002978f5006b97590054c2a700786672001ada490047bff80042049200291379003d7260006fb959003028b400782eb40017828100537fa5007421b5005656a6004b1b0e0008cc07007f6f510058e210003b0ef1005401d50039661500064e7600241202003fe98c0017084e006374dd0043432700358845004b350a001ac67b005070640023d734000bd01e000ee61b006b1afa0022bed400477fb0004901130064a57400530ba2002b561b003e1dcb0074449d003ba955001b158e00298952002b9f07002c00ad0036eeb70037d51d00676b8a00082b890033a117007dcf12005690e20030229000228f190069b6400039708f00712c1900582d9f004d6ae2007e4914007aa22100319e7600377be6006896f10000a8150032cf58003fa63d007eb9ac00013da0005c2221001a2ab4000c7d1a005c20d80053b5f700040b6a006671ad0032600b0078dfab0028a42300693f6e005c65e90003d48e002da2fa0004e117004d6329004387050055f8390064611000325d1f004a40fc00568dba00250f780067ecbe006690990039e97a002664b7002e3fdc007f9ec50075758200571fce0060f4ef00244a220001f8c7006d7da0003be3a4004681d2002ada0a002bad0e002703120039d5c400385a000078560100509f6700301007006093370024cc8f004967c600634e3c00000000000000000000000000000000000000000000000000000000000000200057c3f20079fa4a004e30ff0059a9af0022741e0000116d0076560d00722dbe00682ceb0057a349000d2c35007f5f49003903540069928f0048e34f0068190e001ff6490001517000130440005b987d00382778006d9a650006ceb60060cc250007005d0049d8500040c2b6005e2f5400332c30002d527200725f38002ab76e00223384007081b20002a3e7006b94b5006cb7af003356d4007a79b800216db30032e3ea00093c8b00126af6001a175c00405dd8005caaa3001bf2190054b97400406efd002b5ed100787e060054fccd00295c18001a976f0031da4b001510f00062124c004b57da000d9ae30055baa4000f91420017219d004ee04500490fa50022b599006a10f5005428de004e732e000de75a003a31ec006659a40050977c00400e7f00519b2a001984b5005a73c500531e490075dd8b002ea8d700645d33001bd9550057dd640031b3bd00498ad50077ee6e005a70f100091a19002920a00023c64d001399de00791a050079d21f00012682001a843700508017004df598006bcb010038c28a006beed4007ec6be005164ac001a80ec005961bb0015cd09002a1eb30045d036002a561c003c634d002400bd006bc8c50022803600550bf600083f520023640700477878003ff587003ce4e10038f8e0005102960031eb4b0013bc580001688b0016af03004a68a40073579c0055b122002b4db3006a88790066af33006a969b0011aa38002336c10047720700781778001d98ce0019f391004c16450007bc2a002582030072076e003698d6001314450036647a0004f954003a279e000d1fa000267018002ba2c1003e26ae005d1556005037e90073f1cd003685360045d97500119e09005b026c0047a16f0051d2130067af990079b567002643850012c8f6003e0608000c28fa003a215c006a9a330069a220004f493200340f8d001b239d005674610017f228004750ad001eed9200365d090068dc1d0036ec32002fa4ad000e39290029f86f004afecd007e0a4c00413c87001d669b003f57cb00363fe4007757ae007f8339004f937d001befaf006c7164002475ee0042b65000159a86005de5e4003b6c02005310b80045609c0009dff00065079d006e7b86001aad13006078720005640000100980005517090044275a0027f6220050bc1e0020587800432bad0039e669006a816c00250da10026eee8001917f40037ff2c006133be0028cf21003c60c1000aa6a500602900004cc2a800590491002cccc500496247007cb12c0040f6e0000b6a2f003bbcc600005002002871ad003b7e400038cb7c005f7f56004ed3cc0008086c006c9877004311d40006097200705ed00007d533002f778a00494a09005d21c4006b95e90063507400277285006b988000092b570003d14200592ea5007e50320001b868004b4c39005b766700000000000000000000000000000000000000000000000000000000000000200006a2e3007e439d000959810059894c004a3e91002361bb004576380013f341007cf4a00067682c002f845500261313002bb3ee00599cb60073493e006f4dc0004c3ec1004b2e510059f6c0002038850026afa9007ad88b002b61040064275a00514d690065969b004ba86600440d000057ad060058b90c002465bf003acb5f005d844b00649648001bbee10029aff0001cf3b8007630f20051b728004d64dc001cad380077f0a900600abc007c6c4f00185f710078f1030066ca7e0020c1b10053f71e00145bad00024427000acdbf005ece8d00709927006b8c420042e777007bfd6a000d6fe8007a554c0005bbd10054392a000bc5d900252d34006525f60050674b0073d2af00705cb0003e90d30039cb630015734000700780001911340028af160014f1430023691f0022ff40000f43050059d8cf003094a2003e15760014e114004935ad002860f10033ff75007339ec00475c2c003b769000376ec1006ee91300460d230045b17f003a5f4a0015ea210030a102000461ac005f37f4005deb19003fde5e00512fdc002bc37a0073ba64004f741b002e04b0007ed5c0006e1630002fe2ee00287ba5006e17cd0036b3410020155b0010fc220047b4c0002d4203005c1e870008180c000801060052188b0002af0a001fafa50019105f0018b9bc005f7dea005e8bcd002744d20073e98d007c160500278835002462ee0060d5f6005516e50058fe82001f163a0075118c006d1734000b1b6d004a0e2e003ab21e0050a86e0050dd7700652c200036f1da0049c1b80017623a0076d298004f166d0061b3a4007f163500211ec3005b0936005f7acd00667f38004679290038d8c40049f9f700095d77002ed4c300667d1600227195005dad740074128e00707049001c6415006128e0006d7382003d9150004f99390042473f005db6fa003fb24d00121c47007afd8500683b7a001165580037cd81003de5c9001f156000217a7c005f7e6e006edb4f007aeb450012727e0075cbfa004dc7460041c64b001c39df003779480034ed5600176744006f7d62006f55eb006ba8b30022a5f500166e6600361143006eeabf0030f29f006b7fca001144730074c1100069ca5e0057055d0029dd74000d5238002c6caa0047fa89007df7d7002c226900242bf9005c017e006089e20038e0c4005eb9a20007e0b6006c3c1b00456c9a00197f35003906750074bee8000a349b0062ca12007288c000158d66002f5716005efc6b004d0cee001453a9002e79f8004d8e5e0078dff90078a02c0020b4b2000d1ea80004c1d50042ce2d0048686f006c310c0070b5fc0060d00e006910870023c0fe005ea7210062af58006860c00079930c001e77d2005bad39004f38450036d5d100755b610041c3860064bf5500157a02000a306d003b5ca6003e389c004d88900000000000000000000000000000000000000000000000000000000000000020004288be0031d16b0026252200097f920076fae100132156001977b3000b0b39001a23dc0048d2a90005649b001b49d30055945800349f90000ceba90022e23c007292a80064ba55002d85b10050c5fc00302f4a0052360a00711f350063896a007b82ea0015c8b5007e5b000039b9fe00159ba60060ce84000b21dc0058fdb3001bf2600067159600297277007a8a330046ae810027aea50050e81b0072f711004b6cc3003e632d007bfa890050f23100459cf600168df6003ea8e200226baa00400f60004ae337007fc9d70075d62c0059253300472d3a00683272001fee56005377de002c3bf8005419b0002d22af00771cb8006ec782006f0d1b0059cdbd004d766300518a1a004ee715002e15e00050d7ec00152407000fd23a005ce431002f693b0025a4e90064edda0027c00e00169d1b00259b300031f6fa00285511003edeaf0061192b0006e79400518d48005f4f0c0055f3ac00076272004d454c004021fc00612ddc00643dcc00151abe00564e010079c2e80026a54f0058891d00751154005af47f00018692007bcf97004d7cf500614f070069fe66007ed8ad0020bef6003ba78d006185800008a943001c2e57003bd52b005633740022172f00317c7f005639bf0061a147007cc064003b73f0004905fd004672cb0026722f00288fea000f6121007d01bc001aeaf60067e544004c7108006869ba004ddda20062f1f70060f41e0003cdb0005d6ca8004374a7006271b8003f7f0a0004c11c0010de3a007612bf0054b061002151e7000c0b03003349c50077a7950060591000248b1d002c961300164632003d0f22004abe70006e68b1000c150c006623370074287a006b7192005a14e2004011c700079f9c004ec5b80011abe2001b896300543f80003f790c00768dc000246036004be5a800707e0a0052890c0000236f001eeb740018f30d00728bcf001ffb70003d78de0058d7c70059784e007e642d00233b3a00577ea3000c971f003bcdc6002365ed00570110007dbc0b000d568e007ed762004621df00185aec003238a30036d4a50026499e002c04680028d23a000aba3b000a530400019af6007e510d0054cb2b0064da0000043afd001b1a8400266b49004022d7007aa040001c0a7b00560273005f0088005fc8a8000095c1002c0e38007fcd05000a42b800654f0d00061ebc00126bf4006f3a70001be051000c5626007ca120000ca924002af719001aa31b0065c3a9001ceb460030481a0010714e0046f7bc0041d7f7007d99c20030734f0040af0b004899130029e2f1007d8ceb00464e400043534700595a2e0038510300774c600036249900070a6600008672005251e5004d640e007eac3d00222071003d4ee00015bcff005ae3f700348d48005524c50043e6860071b711003b8354006e4ddf0007a597004127950000000000000000000000000000000000000000000000000000000000000040c00f5f997f0d0c02d1535fea4c798eaab9e386873266f84abc2268533ecdf66332ee45140ed14e922b37b88bc36eed4c41d42e6fa415b05e1a8d9a80c0929f3e000000000000000000000000000000000000000000000000000000000000114000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000008c00000000000000000000000000000000000000000000000000000000000000ce00000000000000000000000000000000000000000000000000000000000000020002b238f001a05710009c7200079fb41004bbd53002ade8e00001806006c5355005c3a0b0068511700795bc90073ed81004ce5e800483a5f000899450049b2a500072dd5005960c600679524006150900041c0f900768c480010f00e00396a24006af5ef0049ba63003a024a001333eb002d1eeb00418e130029a38e0062d31e00366318004376cd007292ec005e12cc00158fe9007abaaf0043560f0047a54200606b600074f454003fca5c000a452b00062b6c006cdaa20027a1e000549fd3002f7cf6005e725f0029cf48003c79cf003c0d52004ac21200561da5007c71a4005c37fb0059eb19007e2b3d007e26c9007db63e0071da70003f77590005d7e8007cf5f20043ed35002a7f580073f36b0064864800610e03006c52dc0057625000791163003d8a890068654b00094b1d0046eed5006f3ba1001c13d20079c199002b7faf0027c0f3007c6937004ac63d006c272b004e0c0f00396ae3004b5d46005c29760057fd6c006d39b20007678a007573d000770c990014219d0076ec62000f621300384341007289c200218b8d0007f0230023a4ba0066b82e002add7800787604005132170035b81a003efcd9007705fc002b15d70067162f003c3838003919c1003b5ab4005f79a3005898ff00093605005d7960003f52150036825d00035e11002e67df0074636300416a76006daf5e006950c9006b929a0023a867003d680b00427e9500412e0c0021da50006c29e00070b36d00125f1d00426d820022d5c30072013700647c89003be486007d921900547eb10038ee54004ad7720033e6990034a73700124a230034ca7a0024cdea006ddd590066de0f0070a720005c25bd002fe89a003a7496006a4ee1000034d10060c441006d34040064785b001aa4ae002576670024a39200723347006ab5070046c62f0065dcb7003beeba000d92ad00184921006499980037704b005a50b80042bc380020e16a0054a7bf0057fea6000e59650035ae7e00447a870014f6e8004d1401000a6e53006bbe850069a61c007cc4380013fe3a005b9d5d00471a4c007a2c59006dfc90000eaa24000c52740034a7120048c1ee003ad8d000702bd4000f4ad7007651e0007d3c0f003fc5bb0015880b0026f0bb006d12040016a4d60073513f001c0803007337d90079f7e0001740120013045c00632ed70040a8ad006271db006e8fdd0025559c0024dce8001f1cfd002b3390005d65a6006a430200238d880058817a00190edd005ad4e40019594e003cfc11007f4b58005e89be005a116b00185e830039995e005e512f006b1fdc0076746b002fa4a30031efb0004498660010cebd002f7173007cf2c2000a4951001fa89f000a4310001f3d2e00135d350060085b0063e712000af65f0011808a0066637f00704c9f00223f33006db0b400073e61000a577b000000000000000000000000000000000000000000000000000000000000002000104b7a006e946100127ad90065f9b90041ee080069baa10061496e000094eb004bfa2b0039a8d600033ee300507ced00520ec30072089b000a1f68001f90700005fb00003fdb3e00353e5a000d0214004a06c00036d346003b7e7a003e956100109f33002681e0000b1b6e003dcc28003a3d69002685130040ee23007897dd004e4a17001f93da0049f13200158fd4004dae0100542bbb005335e5003fec10004f0fd3001197ed000592ee003f37190074b09b0036fe470068e87c003feee90000ad25001a4adb0038ef550049daf300667587000b1099005965b6005c204a0008113e0067548e0018e916003f870b002ada0b00695f54003008890025982a005fa9d6001af903005c065d005b7ea400063757006d9b1f00137fb6007d85190032afc400428ece004e8098006c73010051a9070041bdd4005ef56e002203f1005cb7b1003e9268001513f70062d53f0020ef01000de87f004b83b900031677006a6a6a006cd5f5007c0439004625ca004f26d80001e635001f5c23006680970021c83e0026691b0051531c0058b5df002aa2d9007a6d60005f8d4e000e3359003a10df0076129a00176a37004d52710035a52700608f2e000325ac0033fd190060df7100216094002f25030010a00900304356002b42d10048e92f004ef7f4000664140052ca950068843c004b36b100762e7d001b37bf0000f4ad006900c1002bc5b600424f4c003e1293000d3cf400161068004122440025dc9a006cb54b0029384d000c84f100519f6e007b1e6e00674a62002537fc005478170047242b00792c8600480b5e0021b0f5005aeedb0065f426005415f700077c38004735ef0025f0cc005b8a23003b9524005500af004ab5d0000fda1400172f4e00154caa0050ce57005fe47a0072a32b001307a1000abfde0057d94e006543060069261a001bcdf000440b8100545bcf00231e950039b52d00566530000f1f26001dbaf3001dc64800616dbd004cef0a001f7e97007702bb0061574900243363002fba11005df2f30068d0720058d068004bfbe50063e8810072fe80005c568d00285685004095620034fe44002e79770049b4cf002f6d01000e7c7000559896002cdfc2001f67a8001f47a300048a0200614c5900265ec0001bc371004c100e004383f6007e2a14002ba70f0002314e0057ddea001667940034a83e00568a5f0027a159007239a50062538500664e4b00105daa00228739004612660038807700229985004170fc0035509a0076d4aa0061321100457ab40046fc460056810e000a9055006b01a80037f8d1001429c8001054470033802b0066c09e0076a458003f73d900720f2f00434204001a40ba00778afd00583446000ced1000661e100022668d00509f53003ed48b004c159f0044ebc50063c31200519d39003dd33100145ad100000000000000000000000000000000000000000000000000000000000000200034745800567f4e004460750045e867005c3fbc006b325900789b850060803e006956370011e6d200138cd5000396bd0012fa17003b0e810059ecc4005dad3a0022d6c900071a910047c2f1006ffa87006b4117000caf7d000a50990069157c007a96f0005da77b007f6a3f00585e01006d8337005ffb6a0041800c001c8f0a0060cdc9006761630029f05e007ea1a1002428c2004f70e4001c919600204e7000142341005361330035dcaa002409fa0019cdc800303f64004b98f00056d082006ac7cf0073beab007c538f0067a5340001448c002ee837003f2685000a88d4007c7980003e894000631a7b00648f4d0059065400449c08006b79820007d297005c359300025bcc00147930005685780027cbbf00127fd7002527eb0026be14002e5554000f2cfe005153d900409d3600640eee007ce8b1004f91640075406200503d7e002ae9d30026d601004c066000573d0c001f95c50016832600349bbd0017f6a8002496090076e5fb003c13cc004de23b0051425a000d2fea004d6a90000dec400043f300001558cd0000f25e0043924d003da3910059aeb700255c39002091f1001300290065ec52000c99eb00137b290076c49800007837007e6336005a1396004887e9003cf601005e82ba0005499e0002d87e0013f62c000ff30e00533574000f51460045443a0013af5f0027c2f0007c3576004ab6c3004edb620042db5c00384207005e0a630059ea6b0004a126006a2b2d0075215800209ae500533ecd0029256900529de8001e200f006a28e4004c9cdf00410a150010c920001de04c001f5c31002981cd0051c9b70068fbef0041a4f3001a9f2b0023caff00785f8a006a5b53004f1a88004b872500522342003425f4004c1fdb0052c5dc00571cdf00164aa9007ad9ed000a980c004b9625004a12fd0079b2a6003842a5007781390064ffa10056c1dc001c0df7007d0c53006c35ce004b87e3001ea54f005994570025774700199b6a003d7ffd00352a4f001d920f004e53ae0044286a007f129f00587dd9003220b700307ec8001051150003ef72007e9da90033f470006d2241002c1a1d0031150a0005a319005b6de200577a8d00287bbd0018f990005f13930006c4ac0032f695000476630051d2c20050c35000351eec001fb55300686d640064f0ac000ad4df00371a070040581b007dec31000f21de0031a05b005e10ca0018098f002aeb5b00288fbb004ae82a004a1f05002c0454002c83990035feaa00090e2900457f290005140b007abe54005328e9004e90f1002378ae003ada6a000e0f1e004d3c3e00032c5d001e27de0048baf400031ad8005847c200161a700002c24a004ec868000f0b34002776110023cbf60048dfc10066d4ec00550914000d1cea003cda8800300b8b004ff7000062690c005a4d3c002177d000000000000000000000000000000000000000000000000000000000000000200031ca6e007672cb007bc8a6007ecf96006141c000346f97001de7bf003377e80030e30f005b4cc90054c868003fd50a0023ddc800260bd9007d8c5000657e3600740e51001cf827005ca85d002261e40026c5e9004887900016d081006bcd650037ec3800307f7700204193007b7792006a8bf1002dcd0f00050f4e000598a5000a02bd0076721800602107001aef4a007e745d0068002a000a109d004bb349001c899e002b1117007c5caa00587de5001fcac700554dfd00681d3f0013ec1e004c494200531913003abb6300190e85006e71870071949c004dfd2a00204bbe007ed96b00707451002a01b70031c5b7002950f80034b4c6004fe0bf003ad895002a2d4e0041af74004bb18d0042e7ed001a1f890020f8db002444660049f2590055a32f004f9384003889110067b6dd00670fd3003db0bf0031cef20026455c003717f8001447b1001ec07b0009c9fc0020387d006ef414006a9a74003ff14e0064ec78006b9c1500046c2f0023e6e50070635e003c65030075223700350b770015d120005fb05c0069802f001dde21000ff6d100193b6d0057988d00699cb10009a6860045ced80001db6b00279ab300544778003ca26800197a95007865630057f07100467cf400541e8600500fd10005e6e500383048000eda12002ecba900081c85004a225c005b2334004419aa00671758006bf178005c95bd0051c60900742ea900549b4a006cf48f0056f3e5005fc6a6006fae09006f4e13002d9471000c70f30073f5a70057129e000474a2005462e6002f06f70054023500367619007a44e9000a08b90039adf6000b43d6007dad10007bdb3f003ee0ae0033e7f400303c4f002b61530036301200765ae30066b216002682d300401ca2001f4b150037e61c0002f789003e7fa300595ba50068c576004e413b001b573e0030872700128283005e72dc0025c0ef0068579d0056849a005bebf5000d06fc003dc92b00091401003677a30061b9ae00500d04004767040003018e0031da020057773500167f5f00784d5000647a500027b7670007c7bc000429f000005071007eb8e0003cede900797444007ccd980023186e00789208002fddb70071490d006805ae002fa5b60019d965004a5482002d6213002883320046ad4f0045309d0021162e0005e0180036736d003d8861003b6445000d4aa00070d38a0075c8180046e8890042e0cc0051e178007ed005002c1e9e0066489c00661fad005c593e004221e9003458ec0064d526003f8ab10059dcbd00272047002fa7e400208dd4000e39160009789f0012e208007f8f55005a4c9c0071272000643b97006bacb8000c92e400470bee002373e400729bce000f6d700017fb5b002eea88002be2120079e100001cb1c2004318e6007cbbc60014081d00074eff005c881b007b0a3300793f576426f74a9973b5bb5a014cacd9e3c1a637ad6a82f7a0a66580e5c3c41850bb417f76400c9401a1ce05cf93e30eb701ac8ff197431558723271f02d89871f5182e1e9c24a5212ce15b76fb3b9678acab75d0a4ad1d018dd932025c4b90ae961532ab17bb8be39068c84f0d649174bc5e18a49b03d1375b64c9f971d2a54e2662efb8bf73a2780455df80b478d9678a045a84039a137331df88d3ea795e5d508a9fd2c653ebebe2ee434bb2dfe34d0eff87f7180941e6a25fbf3a2178217283934f1494a5fa02d2d8a18613c1f1c08b1fa8df438fb52268be20258748ad048eb9758b6260ba67f28f79220b84d9689c5dbaa54a3a2de75f357b523aa5b507e15de0069f63316eb47fcd931b37345d301857967ace8144f412737901f75080a5e014eec1bf9ed6672b5087bd4d91c7c080b63c5cff716449a9325fb8bbe614f23f648ccbda5a428d8fc8a41b306832e14575a8543aa786776e7c5fb1fb2e4e95355361c9ac255edf01a72e8173db21296375b5203c61a0a5eb9ee8d22eb2fd7238b5ed74caf342a45a00f1b194fe6a6b603bc6b26f7df0591d86c98f5cb7d56ea3c3a07d05f5d196a26a1724aae752da5d16f535f6d08d16f48ab63e426b8a8a4db55d504629434de6e0e57c2a682fe6a9134377f84bf8cb4113344e54d9fdc0af956de0ce1b8ea71a89383056003a94f4615816ecd3188cade873ce6dafe58769b477bf3f9e08025c3ddd3926b2a8fd391aea4374e563882b7199c3d9d88acdd3c35f61c4118a3f9482683da7c1f0354da86484858521785021550bca7024d7e7c41c66d32e1621577f96e2d8237735bf202d511a3300f1d2152b546a1d3660198353c4ae7bc544c6e7e5e39fa8905847484c3efc69f0f4adc118acc595e0aa7df4b20ffc6ca58ccc1f8ec08227e232e52e2b1c68e67ef24f05c6c6fa98cb0807eadad832b7432947868251b52d3f5ff5d9daedc48788db15f99348b31a08b3dc4592232257e3b8f65969d638f4c3f0f886ceef391bf6bc182bdd094e4b08d643102fe80aeced1c0b4da513fd385e5c9d307bb41fff68756b224db247e58e33d21cd77922d2a5d865e68dd43f2a50413a113ca02834a402f24a371f8057337bf8b76480c113b9808d773ce2e3fa891201b1ac5fef32d407737c13b84391ba0ffe21012b713ac7a2fbe58d32d4da16a99274b57a77c78cff662cb265fc6c15bac51770f8515acccdc655631c4037872f96ff890ccf2d64fa2974caacbe211b3b54110da806246aaa63bf43576f1dd6e9fa8b3985851946b19d2b054b2c69acef31ca3d7276cde54a3e76fb6ebab82a696a58e87f0b6e901144628be1a8569cf52f682476d15ebc5553ccec5ab5add86c04b1fff34414e5e286ac9d99921c8b56560f4f282a7c64ded16fd70d441ab04c8f681d3e13cc0bc16c28176e328b66f63df031a8709089d88f6321d0ef45a17884f2aba954e718a6d2c520a2293c5be3f8f667c2b62641af3160e5c31a51221a5802c09d890c6a561f695435186ff4a202e47581706dde0a98c6701ae6ac452bc556705359255c94a41ed66a9a17075e5aec066344065ba01b5ee102819b8c1e5a49496aa78ed58edec9b67ac1ac2a95cf8ab2d0ce6a9b52c7f67d94236ea230b1cfcf6a9c02d9b3b65b2bd835ae691a8c17b19da68aea96d1b8825385143e483da665f3021bdc5ab45e32e2bbbc0d31f24668cfd317a80a2f9f82d22f2dacac6a9736801637894bc418bc689f88b920730f957dc6e55ff24b5a9c385653e4ccc254b63991f9ad4c645a09307ccf7994ba6a4529783c9fd1bcbae7f128a8a69b2557d408838f4e545826eef0626b975bce2120c9cfb0c748340bb76105091ec4794a8da1580696e37cf2dcac4534bb0850e09f8b34c1bc15b26d4751be1e777f3d500e90bf6949651cadc2133293eaf31fe93579bfa175c0b6c154fb9a6e5385645e5c02c768237b192a731fe32aa8792bd552330e8ad4a886b31b7e5b8d4134697574499fa9c2785df587f7a8ca7a53b3656171d831ec37b4cad5b17686a3ee1fcfab3d864a8535296d3b1c7f8f39b2dcb0749dad0d0fb2c2b7ce83203081216d36a9c42018a5cd47fd21491aad6f642467a7175a31720f2c2011784b268b2f62909f7658d0b0e1554975b05413c415d60ff19bf6c788e823bca5bd299cff3eb03636c24719f59e11bffd04eddd9d48628e670218355a554670a575d403b453418ebecbd5f6137605864f2ad813451cc493c8d9a0e3b8e887b7f2ba01e3b3a4084ddc52f78cf885ec1f851069e036020f7ffed6315df6650381346753289efa238a1ac0645704e3595a974e76432192eb9394c04bc548fce00f42cbef3b99bb84394bfa933242ee6f3135dd2049b33ca99f9b0deda501dc061d1f114d53c1bca532449f60e626342ba656695a330b667bf0bca92d4f12208a10c2cb06523f8e2b528ad9bf275de3c5ceb7b75f15d05246a8c1a7ae5f94c21a6f3273ea385fa1db553e07bd50b420cf124d7f3e0cbeeed5de505024a3e2476464f111961c4f6804172634f7375336f2787a9bd9cdeaec3905b26b8bd217ca6d1d7aba571904211c934b7483736fb1a24996dd354c0cf1ed2b521155d41769d8b0321abad3dd628a08b9e04925ef31d3aa20406c1c23ec0816dc93947be65ce7b59c517bab46728608e54dff0210c4011e23455d933be8ea206c8bf83656bf41ec13196b9d119bd04e7ca28eaf93e1fe3858b73670a1f76985ce34a087383811d8067164ac9c141b62641d37b8f4b6fcdd1cac2854011c30b2705c35d3df0623d6ba13f586cbecb3136038221550392faa13d01975272e60e9e3b83030484b2aaad48e6771c3fc5440fcbc327876d9d0bcf54f2a8c802e0de352bca84ff015856d495944e7cd33ee011cb9838f07f1d1a028cadb03cea961f57aea7eeddf3b24fa9847f8e3c447ebd340c33b60a49b1ede303819b3b6636a9ef62cdf15f24a9fcf8dc13d5a89e690948db777f618bb2805b46de12077db26c9a3abaeb20cec6b5e50104c4f2fc883389b00d9911b944bd10f0205364ac168470a7d9f0047db2af7826ea2f9d3a6afa99200ae3351354557d44d65952098d4ee1c9ef4c19376bc5e0cc9c2c036482e2d7071f1db5fbe240bd46ed02fc837a0c0c5257c240e68e498ed4027ff9be5f627e668104852a6eec3276b0a3300fd7dda5d47a12ddf083f61dd6ece9d17c76aec458db1a928978bea6481d35f4b4d4f16d749697408c017b7ddf2927116cd4c375ca97e5af143afccf28b22b54749cd200163c3f4c58717377839798bcdae3001f222e79a0abafb4b5c8cbd1d6e20e1a3c51606a6e707783979dabc5cfe9f204091a1b404b4c5a888fa2abd500000000000000000000000000000000000000000f1e2f3c", - "signer": null - } - ], - "transactionIndex": "0x0", - "type": "0x6" - }, - "receipt": { - "blockHash": "0xe28e3a2f9a8c83ea57c8851d67f50a47abde92cc387f3e0e03f6a68bc5c75f9d", - "blockNumber": "0xa501", - "contractAddress": null, - "cumulativeGasUsed": "0x566367", - "effectiveGasPrice": "0x5f5e107", - "frameReceipts": [ - { - "gasUsed": "0x6ae7", - "logs": [], - "stateGasUsed": "0x4bd3e", - "status": "0x1" - }, - { - "gasUsed": "0x93", - "logs": [], - "stateGasUsed": "0x0", - "status": "0x1" - }, - { - "gasUsed": "0x493eab", - "logs": [], - "stateGasUsed": "0x0", - "status": "0x1" - }, - { - "gasUsed": "0xbb8", - "logs": [ - { - "address": "0xfffffffffffffffffffffffffffffffffffffffe", - "data": "0x0000000000000000000000000000000000000000000000000000640b5eece000", - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "0x000000000000000000000000f7917227874c6ee42b884b978e7dea0ac3fa9098" - ] - } - ], - "stateGasUsed": "0x2cd30", - "status": "0x1" - } - ], - "from": "0x954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "gasUsed": "0x566367", - "logs": [ - { - "address": "0xfffffffffffffffffffffffffffffffffffffffe", - "blockHash": "0xe28e3a2f9a8c83ea57c8851d67f50a47abde92cc387f3e0e03f6a68bc5c75f9d", - "blockNumber": "0xa501", - "blockTimestamp": "0x6a9d8a60", - "data": "0x0000000000000000000000000000000000000000000000000000640b5eece000", - "logIndex": "0x0", - "removed": false, - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "0x000000000000000000000000f7917227874c6ee42b884b978e7dea0ac3fa9098" - ], - "transactionHash": "0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be", - "transactionIndex": "0x0" - } - ], - "logsBloom": "0x00000000000000000000000000100000000000000000000000000000002000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000010000000000000000000000000000000000000000000080000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000002000000000008000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000040000000000000", - "payer": "0x954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "status": "0x1", - "to": "0x954f9c5de1b8d13b64b6be0872192b98754e2cb1", - "transactionHash": "0xa5176324860de896e541bb12914e086f95bd6dadd12679cea1c57b9c51c856be", - "transactionIndex": "0x0", - "type": "0x6" - }, - "simulate": { - "executionError": null, - "executionStatus": null, - "frames": null, - "gasUsed": null, - "maxCost": "0x6f2e9476ea800", - "payer": null, - "prefixShape": "DeploySelfVerify", - "valid": false, - "violation": "Invalid Transaction: Nonce mismatch: expected 2, got 0" - } -} diff --git a/test/fixtures/chain/0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66.json b/test/fixtures/chain/0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66.json deleted file mode 100644 index 0206723..0000000 --- a/test/fixtures/chain/0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "meta": { - "clientVersion": "ethrex/v23.0.0-hegota-testnet-31b5322665fd48f07337f6dd7cede21e7de7866a/x86_64-unknown-linux-gnu/rustc-v1.93.1", - "genesisHash": "0x7ca0f7358d127dc4a68983050eb88837a5f384225254d1b009fa87fbcd0f2332", - "capturedAt": "2026-09-06T16:10:35.294Z", - "block": "2792", - "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" - }, - "tx": { - "blobVersionedHashes": [], - "blockHash": "0x42534c39eeb2dd523080493aed3ff67430cf73028fcedc20fc40d28482b4f11a", - "blockNumber": "0xae8", - "chainId": "0x1fcd", - "frames": [ - { - "data": "0x267c5bcd8856b58253be0a11ab764ef43963e45c05cecb45a18f3c2dfb5a7a5a2ec3b83435fc58997bd3989b92bdd0f4f3d124db347b31296a49aabf23fdb8bf05e0cccd753e7dfaf2e5af23736e9ebc4ed674100436a18e10b4edeba8a28e7f109d4ca618bba4611b1baf7d808b3c8b555c8c44f51180e9ba34f36753c07b5d15ffb2487a963582fbe1bbb845ae2436bc49b585b9a6c74b333abc6b1cc699ef16ded43511e147b2a851acac9ce10f508b975e15161b8d7bbfdc07c50abade8515d4ab2d1ea0defd4d785149019c758e5b5a9e110e645fa37d917ae8faf36853003062314ca90eca071fb83fe16606108658fae2a57d508e74fea8a60afbe5b0", - "flags": "0x3", - "gasLimit": "0x4e200", - "mode": "0x1", - "stateLimit": "0x0", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "value": "0x0" - }, - { - "data": "0x921fcac71ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df4220000000000000000000000000000000000000000000000000000000000000af600000000000000000000000000000000000000000000000000000000000000002e336247aaf32c84c96cf86110e28720d8a08ad924deca014440ff584859603b060ed959302b15fe85a8e0358e936cb5ca584d174295b603d46d5c3dc1a654d419dcb924895a2dc08568ae34801b4a393d9a37a75091b3c0d9c2378f62fe7ae523f1b896ada6ee5dac80945b11329e7ab64412c2be9f5c87cfa3261cc1d8216f2fd476622c67c880b3049a76c7337192362834c9d6dfb55c5060bb96c98932bb00000000000000000000000000000000000000000000000007a1fe160277000000000000000000000000000000000000000000000000000000b1a2bc2ec5000000000000000000000000000000000000000000000000000000000000cafebabe0000000000000000000000007cece7cf428f7eaf4498727edd8dee2f9ae064eb", - "flags": "0x0", - "gasLimit": "0x155cc0", - "mode": "0x2", - "stateLimit": "0x86470", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "value": "0x0" - } - ], - "from": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "hash": "0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66", - "maxFeePerBlobGas": "0x0", - "maxFeePerGas": "0x3b9aca0e", - "maxPriorityFeePerGas": "0x3b9aca00", - "nonceKeys": [ - "0x60ed959302b15fe85a8e0358e936cb5ca584d174295b603d46d5c3dc1a654d4", - "0x19dcb924895a2dc08568ae34801b4a393d9a37a75091b3c0d9c2378f62fe7ae5" - ], - "nonceSeq": "0x0", - "recentRootReferences": [ - { - "root": "0x1ea261e94b9f2b02699e293bd4ad36b4c39cf23975b84c4cc39794bb577df422", - "slot": "0xaf6", - "sourceId": "0xb08f15750c491f4cfd65215c11a33b3962903a8896fc586bbd7c697851c26e20" - } - ], - "sender": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "signatures": [ - { - "msg": "0x", - "scheme": "0x1", - "signature": "0x00d8dc8207b1825672c63f6f95a035d5b70d5ee4e5e72568fb1c093cdddfa4ae284a54a6032d453d9d196e6ebda9bdd848ad41507a9306135c9a68d1210c68f326", - "signer": "0x7cece7cf428f7eaf4498727edd8dee2f9ae064eb" - } - ], - "transactionIndex": "0x0", - "type": "0x6" - }, - "receipt": { - "blockHash": "0x42534c39eeb2dd523080493aed3ff67430cf73028fcedc20fc40d28482b4f11a", - "blockNumber": "0xae8", - "contractAddress": null, - "cumulativeGasUsed": "0x6de5c", - "effectiveGasPrice": "0x3b9aca07", - "frameReceipts": [ - { - "gasUsed": "0x47e79", - "logs": [], - "stateGasUsed": "0x0", - "status": "0x1" - }, - { - "gasUsed": "0x6397", - "logs": [ - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x", - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x060ed959302b15fe85a8e0358e936cb5ca584d174295b603d46d5c3dc1a654d4" - ] - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x", - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x19dcb924895a2dc08568ae34801b4a393d9a37a75091b3c0d9c2378f62fe7ae5" - ] - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "data": "0x00000000000000000000000000000000000000000000000007a1fe1602770000", - "topics": [ - "0x459f560336b72d57e46610439b7c1a8426cf7b7a2a0428d5fb5c7b0b7528b60d", - "0x00000000000000000000000000000000000000000000000000000000cafebabe" - ] - } - ], - "stateGasUsed": "0x17e80", - "status": "0x1" - } - ], - "from": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "gasUsed": "0x6de5c", - "logs": [ - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x42534c39eeb2dd523080493aed3ff67430cf73028fcedc20fc40d28482b4f11a", - "blockNumber": "0xae8", - "blockTimestamp": "0x6a99edbe", - "data": "0x", - "logIndex": "0x0", - "removed": false, - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x060ed959302b15fe85a8e0358e936cb5ca584d174295b603d46d5c3dc1a654d4" - ], - "transactionHash": "0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66", - "transactionIndex": "0x0" - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x42534c39eeb2dd523080493aed3ff67430cf73028fcedc20fc40d28482b4f11a", - "blockNumber": "0xae8", - "blockTimestamp": "0x6a99edbe", - "data": "0x", - "logIndex": "0x1", - "removed": false, - "topics": [ - "0xd13faa8100906cf559aebacf9c16532cfc9708645c198c8f15798ee049dbcfc1", - "0x19dcb924895a2dc08568ae34801b4a393d9a37a75091b3c0d9c2378f62fe7ae5" - ], - "transactionHash": "0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66", - "transactionIndex": "0x0" - }, - { - "address": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "blockHash": "0x42534c39eeb2dd523080493aed3ff67430cf73028fcedc20fc40d28482b4f11a", - "blockNumber": "0xae8", - "blockTimestamp": "0x6a99edbe", - "data": "0x00000000000000000000000000000000000000000000000007a1fe1602770000", - "logIndex": "0x2", - "removed": false, - "topics": [ - "0x459f560336b72d57e46610439b7c1a8426cf7b7a2a0428d5fb5c7b0b7528b60d", - "0x00000000000000000000000000000000000000000000000000000000cafebabe" - ], - "transactionHash": "0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66", - "transactionIndex": "0x0" - } - ], - "logsBloom": "0x00000000000000000000000000000010000000000000000000000000000400800000000000000000010000000000000000400000000000000000000000000000020000000000000000002000000100000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000080000000000000002000000000000000000000000000000000020000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000200000000000000000000000000000000000000000000000000800000800000000000000000000080000000", - "payer": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "status": "0x1", - "to": "0x753d91eef10c8e26924aabcb0ad73052f8fc4522", - "transactionHash": "0xb17e6a8292d3ed1f559d7e78f85b62fad2962b589e51ce90eb6462440b6d2a66", - "transactionIndex": "0x0", - "type": "0x6" - }, - "simulate": { - "executionError": null, - "executionStatus": null, - "frames": null, - "gasUsed": null, - "maxCost": "0x82dd7a58aa5c8", - "payer": null, - "prefixShape": null, - "valid": false, - "violation": "EIP-8272 recent-root reference expired: slot 2806 is outside the usable window at slot 42522" - } -} diff --git a/test/fixtures/chain/0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5.json b/test/fixtures/chain/0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5.json new file mode 100644 index 0000000..4a7aff3 --- /dev/null +++ b/test/fixtures/chain/0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5.json @@ -0,0 +1,212 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:59.671Z", + "block": "38263", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x99d5bae8dec227065dd8782e87f2dd252908dc5d1fb17d24040ac76d9af50642", + "blockNumber": "0x9577", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x6fdb2b58", + "flags": "0x2", + "gasLimit": "0xc350", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "value": "0x0" + }, + { + "data": "0xfc735e99", + "flags": "0x1", + "gasLimit": "0x249f0", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x781f89f91954ad4ff1fecdf22ed47a2deee02ada", + "value": "0x0" + }, + { + "data": "0xa9059cbb000000000000000000000000781f89f91954ad4ff1fecdf22ed47a2deee02ada000000000000000000000000000000000000000000000001158e460913d00000", + "flags": "0x4", + "gasLimit": "0x186a0", + "mode": "0x2", + "stateGasLimit": "0x30d40", + "to": "0x37ea1ac52f4f0148516b80079186d5a81e8af024", + "value": "0x0" + }, + { + "data": "0x609848ebae3bdb896352d0331d8bda83c92536f79ba0b6935b67048e270799e24a08443f00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a00000000000000000000000037ea1ac52f4f0148516b80079186d5a81e8af024000000000000000000000000000000000000000000000001158e460913d00000", + "flags": "0x0", + "gasLimit": "0xf4240", + "mode": "0x0", + "stateGasLimit": "0x61a80", + "to": "0x781f89f91954ad4ff1fecdf22ed47a2deee02ada", + "value": "0x0" + }, + { + "data": "0xa9059cbb00000000000000000000000040d56df84f5724b1052d83a34e4ed141b028d5ec0000000000000000000000000000000000000000000000015af1d78b58c40000", + "flags": "0x0", + "gasLimit": "0x1", + "mode": "0x2", + "stateGasLimit": "0x30d40", + "to": "0x37ea1ac52f4f0148516b80079186d5a81e8af024", + "value": "0x0" + }, + { + "data": "0x8321fa67ae3bdb896352d0331d8bda83c92536f79ba0b6935b67048e270799e24a08443f", + "flags": "0x0", + "gasLimit": "0xf4240", + "mode": "0x0", + "stateGasLimit": "0x61a80", + "to": "0x781f89f91954ad4ff1fecdf22ed47a2deee02ada", + "value": "0x0" + } + ], + "from": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "hash": "0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x3b9aca0e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x0" + ], + "nonceSeq": "0x3", + "sender": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x00fbacdf25f353ab118af56a6729561c1cf48639c6cfb5259c8a222ddb8297964e7a736b3f7518f61398a6ff0a439a0a7dd43b1c57641b299a0204b17ab4c15210", + "signer": "0xdd83c15d23d84d34797d13015667ce4fcc6317b2" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x99d5bae8dec227065dd8782e87f2dd252908dc5d1fb17d24040ac76d9af50642", + "blockNumber": "0x9577", + "contractAddress": null, + "cumulativeGasUsed": "0x93744", + "effectiveGasPrice": "0x3b9aca07", + "frameReceipts": [ + { + "gasUsed": "0x176", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x124f", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x7649", + "logs": [ + { + "address": "0x37ea1ac52f4f0148516b80079186d5a81e8af024", + "data": "0x000000000000000000000000000000000000000000000001158e460913d00000", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "0x000000000000000000000000781f89f91954ad4ff1fecdf22ed47a2deee02ada" + ] + } + ], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0xda36", + "logs": [ + { + "address": "0x781f89f91954ad4ff1fecdf22ed47a2deee02ada", + "data": "0x000000000000000000000000000000000000000000000001158e460913d00000", + "topics": [ + "0x0aa2284d4152c7f367c978b66c62619aa9ddec6e9cd8f5db0d76797cfe9f55ab", + "0xae3bdb896352d0331d8bda83c92536f79ba0b6935b67048e270799e24a08443f", + "0x00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "0x00000000000000000000000037ea1ac52f4f0148516b80079186d5a81e8af024" + ] + } + ], + "stateGasUsed": "0x5fa00", + "status": "0x1" + }, + { + "gasUsed": "0x1", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x0" + }, + { + "gasUsed": "0x25d", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x0" + } + ], + "from": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "gasUsed": "0x93744", + "logs": [ + { + "address": "0x37ea1ac52f4f0148516b80079186d5a81e8af024", + "blockHash": "0x99d5bae8dec227065dd8782e87f2dd252908dc5d1fb17d24040ac76d9af50642", + "blockNumber": "0x9577", + "blockTimestamp": "0x6aac1d4b", + "data": "0x000000000000000000000000000000000000000000000001158e460913d00000", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "0x000000000000000000000000781f89f91954ad4ff1fecdf22ed47a2deee02ada" + ], + "transactionHash": "0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5", + "transactionIndex": "0x0" + }, + { + "address": "0x781f89f91954ad4ff1fecdf22ed47a2deee02ada", + "blockHash": "0x99d5bae8dec227065dd8782e87f2dd252908dc5d1fb17d24040ac76d9af50642", + "blockNumber": "0x9577", + "blockTimestamp": "0x6aac1d4b", + "data": "0x000000000000000000000000000000000000000000000001158e460913d00000", + "logIndex": "0x1", + "removed": false, + "topics": [ + "0x0aa2284d4152c7f367c978b66c62619aa9ddec6e9cd8f5db0d76797cfe9f55ab", + "0xae3bdb896352d0331d8bda83c92536f79ba0b6935b67048e270799e24a08443f", + "0x00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "0x00000000000000000000000037ea1ac52f4f0148516b80079186d5a81e8af024" + ], + "transactionHash": "0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000023008000000000000000100000000000000000800000000000000000000000000000000008000000000000000000000000002000400000008000000000000000000010000000000000000000000000000000000000000000000000000000000000004000000000010000000000000000000000000100000800200000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000020000000400000000000000000000000000000000000000000000000000080000000000000000000000000000008000000001008000000000000", + "payer": "0x781f89f91954ad4ff1fecdf22ed47a2deee02ada", + "status": "0x0", + "to": null, + "transactionHash": "0xbd73cb7e2fb35c3bd002e936aad7bef590df3878e94198a2b64fff58592ac8d5", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0xc83c09f40de2a", + "payer": null, + "prefixShape": "OnlyVerifyPay", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 8, got 3" + } +} diff --git a/test/fixtures/chain/0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f.json b/test/fixtures/chain/0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f.json new file mode 100644 index 0000000..e219f3e --- /dev/null +++ b/test/fixtures/chain/0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f.json @@ -0,0 +1,117 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:58.947Z", + "block": "72", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xd3a615f2ce8c5572e37c106e5895c4037a71ebffcf53e1600037d5b267ff0dc9", + "blockNumber": "0x48", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x13880", + "mode": "0x1", + "stateGasLimit": "0x17e80", + "to": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x0", + "gasLimit": "0x7530", + "mode": "0x2", + "stateGasLimit": "0x2cd30", + "to": "0xbeef000000000000000000000000000000000003", + "value": "0x64" + } + ], + "from": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "hash": "0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x773a5cd0", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x82500001" + ], + "nonceSeq": "0x0", + "sender": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "signatures": [], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xd3a615f2ce8c5572e37c106e5895c4037a71ebffcf53e1600037d5b267ff0dc9", + "blockNumber": "0x48", + "contractAddress": null, + "cumulativeGasUsed": "0x4a23f", + "effectiveGasPrice": "0x3b9bd601", + "frameReceipts": [ + { + "gasUsed": "0x6d", + "logs": [], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0xbb8", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040456702b7d2725b8fdc5c7b33ca21eab350b42a", + "0x000000000000000000000000beef000000000000000000000000000000000003" + ] + } + ], + "stateGasUsed": "0x2cd30", + "status": "0x1" + } + ], + "from": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "gasUsed": "0x4a23f", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0xd3a615f2ce8c5572e37c106e5895c4037a71ebffcf53e1600037d5b267ff0dc9", + "blockNumber": "0x48", + "blockTimestamp": "0x6aa86d35", + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040456702b7d2725b8fdc5c7b33ca21eab350b42a", + "0x000000000000000000000000beef000000000000000000000000000000000003" + ], + "transactionHash": "0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000004000000000000000000008000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000002000000000008000000000000000000000000000002000000000000400000000000000000000000000000000001000000000000040000000000000000", + "payer": "0x40456702b7d2725b8fdc5c7b33ca21eab350b42a", + "status": "0x1", + "to": null, + "transactionHash": "0xcc0fb0a92c58e3df0ab563ce3e0afc743251f8cac0b5b1904722bc5c15c8cc8f", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x2eaf08737ac20", + "payer": null, + "prefixShape": "SelfVerify", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" + } +} diff --git a/test/fixtures/chain/0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1.json b/test/fixtures/chain/0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1.json deleted file mode 100644 index 35dc2f7..0000000 --- a/test/fixtures/chain/0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "meta": { - "clientVersion": "ethrex/v23.0.0-hegota-testnet-31b5322665fd48f07337f6dd7cede21e7de7866a/x86_64-unknown-linux-gnu/rustc-v1.93.1", - "genesisHash": "0x7ca0f7358d127dc4a68983050eb88837a5f384225254d1b009fa87fbcd0f2332", - "capturedAt": "2026-09-06T16:10:53.255Z", - "block": "42086", - "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" - }, - "tx": { - "blobVersionedHashes": [], - "blockHash": "0xbaccd41ac9f3b2d584929c897ebe098c94f05c09ac6677485b0f0e054743547a", - "blockNumber": "0xa466", - "chainId": "0x1fcd", - "frames": [ - { - "data": "0x", - "flags": "0x3", - "gasLimit": "0x13880", - "mode": "0x1", - "stateLimit": "0x0", - "to": "0x5f9ad6b07592cdee3139485a6774c2400d734791", - "value": "0x0" - }, - { - "data": "0x", - "flags": "0x0", - "gasLimit": "0x7530", - "mode": "0x2", - "stateLimit": "0x2cd30", - "to": "0xbad8dd97419fed11d77729dcc013c57f4fa5b688", - "value": "0x38d7ea4c68000" - } - ], - "from": "0x5f9ad6b07592cdee3139485a6774c2400d734791", - "hash": "0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1", - "maxFeePerBlobGas": "0x0", - "maxFeePerGas": "0x3b9aca0e", - "maxPriorityFeePerGas": "0x3b9aca00", - "nonceKeys": [ - "0x0" - ], - "nonceSeq": "0x0", - "recentRootReferences": [], - "sender": "0x5f9ad6b07592cdee3139485a6774c2400d734791", - "signatures": [ - { - "msg": "0x", - "scheme": "0x1", - "signature": "0x00efd58592801072e7783f89bce2b59384740a96ad22663af974b02a0f87f96fda6276f0f0e06b6a1f597a451232a0b7fcdfc63cb0c5275cbd20293e4bf7280a1a", - "signer": "0x5f9ad6b07592cdee3139485a6774c2400d734791" - } - ], - "transactionIndex": "0x0", - "type": "0x6" - }, - "receipt": { - "blockHash": "0xbaccd41ac9f3b2d584929c897ebe098c94f05c09ac6677485b0f0e054743547a", - "blockNumber": "0xa466", - "contractAddress": null, - "cumulativeGasUsed": "0x6af6", - "effectiveGasPrice": "0x3b9aca07", - "frameReceipts": [ - { - "gasUsed": "0x64", - "logs": [], - "stateGasUsed": "0x0", - "status": "0x1" - }, - { - "gasUsed": "0xbb8", - "logs": [ - { - "address": "0xfffffffffffffffffffffffffffffffffffffffe", - "data": "0x00000000000000000000000000000000000000000000000000038d7ea4c68000", - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x0000000000000000000000005f9ad6b07592cdee3139485a6774c2400d734791", - "0x000000000000000000000000bad8dd97419fed11d77729dcc013c57f4fa5b688" - ] - } - ], - "stateGasUsed": "0x0", - "status": "0x1" - } - ], - "from": "0x5f9ad6b07592cdee3139485a6774c2400d734791", - "gasUsed": "0x6af6", - "logs": [ - { - "address": "0xfffffffffffffffffffffffffffffffffffffffe", - "blockHash": "0xbaccd41ac9f3b2d584929c897ebe098c94f05c09ac6677485b0f0e054743547a", - "blockNumber": "0xa466", - "blockTimestamp": "0x6a9d86be", - "data": "0x00000000000000000000000000000000000000000000000000038d7ea4c68000", - "logIndex": "0x0", - "removed": false, - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x0000000000000000000000005f9ad6b07592cdee3139485a6774c2400d734791", - "0x000000000000000000000000bad8dd97419fed11d77729dcc013c57f4fa5b688" - ], - "transactionHash": "0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1", - "transactionIndex": "0x0" - } - ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000002000000000000000200000000000000008000010000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000002000000000008000000000000000000000000000000000000000000400000000400000000000000000000000800000000000000000000000000000000", - "payer": "0x5f9ad6b07592cdee3139485a6774c2400d734791", - "status": "0x1", - "to": "0x5f9ad6b07592cdee3139485a6774c2400d734791", - "transactionHash": "0xe936e39dc5a0e57f704f767b6a7c0a6589d82d3ba229dcc93ab6f6cb11925fb1", - "transactionIndex": "0x0", - "type": "0x6" - }, - "simulate": { - "executionError": null, - "executionStatus": null, - "frames": null, - "gasUsed": null, - "maxCost": "0x120142d140e0c", - "payer": null, - "prefixShape": "SelfVerify", - "valid": false, - "violation": "Invalid Transaction: Nonce mismatch: expected 1, got 0" - } -} diff --git a/test/fixtures/chain/0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6.json b/test/fixtures/chain/0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6.json new file mode 100644 index 0000000..2c8f35c --- /dev/null +++ b/test/fixtures/chain/0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6.json @@ -0,0 +1,139 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:30:00.068Z", + "block": "91066", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0x35f6e5228a934c0f401ac8a57a9b99a98468d99bb6ca1af3925e0c658f21b5b2", + "blockNumber": "0x163ba", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x547cfc4f401e977b1175672652e2be55cc429f0190ae92112afa031f71ec5d7100000000000178ced4b64160cc946900a55989517cab593348ba2f3b82f6e23711f51673dde79329", + "flags": "0x0", + "gasLimit": "0x1f40", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x0000000000000000000000000000000000008272", + "value": "0x0" + }, + { + "data": "0x", + "flags": "0x3", + "gasLimit": "0x7530", + "mode": "0x1", + "stateGasLimit": "0x17e80", + "to": "0x48811ed298cb76bb68268bbc9973824544c377d9", + "value": "0x0" + }, + { + "data": "0x104a2cdf000000000000000000000000000000000000000000000000000000000000dead00000000000000000000000048811ed298cb76bb68268bbc9973824544c377d900000000000000000000000000000000000000000000000000b1a2bc2ac643081056eec7dba1d5bb543367c86e1ddaa633a75c4c7d299e483bc432c55cdd9cf400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000001124fdb02276594de9e89102cc8a27277fbbaacc1ff601d6b5da885fd85e64dbb4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0", + "flags": "0x0", + "gasLimit": "0x2dc6c0", + "mode": "0x0", + "stateGasLimit": "0x7a120", + "to": "0x0bca60c0742354616c94e9da941cb5802dfe94cf", + "value": "0x0" + } + ], + "from": "0x48811ed298cb76bb68268bbc9973824544c377d9", + "hash": "0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x9", + "maxPriorityFeePerGas": "0x1", + "nonceKeys": [ + "0x747a99f9dedd414795eaf4b614858724513cba9aec3be03d4b4c0388cfee7f0d" + ], + "nonceSeq": "0x0", + "sender": "0x48811ed298cb76bb68268bbc9973824544c377d9", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x01ea07243d5be0391f83ecd9f298f6c42c890643ef758b1513a270917b343f89040f01b382b911ca1ab39febba37445d7fe7b4c9977f158dbb3d09e91b5d6fb4d7", + "signer": "0x48811ed298cb76bb68268bbc9973824544c377d9" + } + ], + "transactionIndex": "0x0", + "type": "0x6" + }, + "receipt": { + "blockHash": "0x35f6e5228a934c0f401ac8a57a9b99a98468d99bb6ca1af3925e0c658f21b5b2", + "blockNumber": "0x163ba", + "contractAddress": null, + "cumulativeGasUsed": "0x5c59a", + "effectiveGasPrice": "0x8", + "frameReceipts": [ + { + "gasUsed": "0x15cb", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x64", + "logs": [], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0xe9d6", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "data": "0x00000000000000000000000000000000000000000000000000b1a2bc2ac64308", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000bca60c0742354616c94e9da941cb5802dfe94cf", + "0x000000000000000000000000000000000000000000000000000000000000dead" + ] + } + ], + "stateGasUsed": "0x2cd30", + "status": "0x1" + } + ], + "from": "0x48811ed298cb76bb68268bbc9973824544c377d9", + "gasUsed": "0x5c59a", + "logs": [ + { + "address": "0xfffffffffffffffffffffffffffffffffffffffe", + "blockHash": "0x35f6e5228a934c0f401ac8a57a9b99a98468d99bb6ca1af3925e0c658f21b5b2", + "blockNumber": "0x163ba", + "blockTimestamp": "0x6ab1405f", + "data": "0x00000000000000000000000000000000000000000000000000b1a2bc2ac64308", + "logIndex": "0x0", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000bca60c0742354616c94e9da941cb5802dfe94cf", + "0x000000000000000000000000000000000000000000000000000000000000dead" + ], + "transactionHash": "0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6", + "transactionIndex": "0x0" + } + ], + "logsBloom": "0x00000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000010000000000000000000000000000004000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000402000000000008020000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000", + "payer": "0x48811ed298cb76bb68268bbc9973824544c377d9", + "status": "0x1", + "to": null, + "transactionHash": "0xea13068da88d0d6b47fee7baf779245fce355d3c84eb44938e2b7ed18bc17dc6", + "transactionIndex": "0x0", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x1f79a5d", + "payer": null, + "prefixShape": null, + "valid": false, + "violation": "EIP-8272 recent-root reference expired: slot 96462 is outside the usable window at slot 136624" + } +} diff --git a/test/fixtures/chain/0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971.json b/test/fixtures/chain/0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971.json new file mode 100644 index 0000000..03755dc --- /dev/null +++ b/test/fixtures/chain/0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971.json @@ -0,0 +1,184 @@ +{ + "meta": { + "clientVersion": "ethrex/v23.0.0-hegota-testnet-bdfc5d8f2e7f653e620a9901db915f89a87a3d0d/x86_64-unknown-linux-gnu/rustc-v1.93.1", + "genesisHash": "0x2036e3fe3eaaa15382a9befde816137b6b2cda1e3df8ca0c92e89bc17a3fca5a", + "capturedAt": "2026-09-24T09:29:59.417Z", + "block": "38219", + "rawSource": "reconstructed from JSON; pinned by hash in test/oracles.test.ts" + }, + "tx": { + "blobVersionedHashes": [], + "blockHash": "0xd416e4f252df9b4adf2b0a070d2afdadf0be5b8b83e33fea490a9b19c3ef5df3", + "blockNumber": "0x954b", + "chainId": "0x1fcd", + "frames": [ + { + "data": "0x6fdb2b58", + "flags": "0x2", + "gasLimit": "0xc350", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "value": "0x0" + }, + { + "data": "0xfc735e99", + "flags": "0x1", + "gasLimit": "0x249f0", + "mode": "0x1", + "stateGasLimit": "0x0", + "to": "0x9f610e100c751e7a632e7f362b2cc6302c758666", + "value": "0x0" + }, + { + "data": "0xa9059cbb0000000000000000000000009f610e100c751e7a632e7f362b2cc6302c758666000000000000000000000000000000000000000000000001158e460913d00000", + "flags": "0x4", + "gasLimit": "0x186a0", + "mode": "0x2", + "stateGasLimit": "0x30d40", + "to": "0x57b6cef8632ff83fdfb01f80939a7c93c106af5b", + "value": "0x0" + }, + { + "data": "0x609848ebea822a00bfa02d9279db7d0d5c7f797c35266c03195ec44478e4197c2774e79100000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a00000000000000000000000057b6cef8632ff83fdfb01f80939a7c93c106af5b000000000000000000000000000000000000000000000001158e460913d00000", + "flags": "0x0", + "gasLimit": "0x1", + "mode": "0x0", + "stateGasLimit": "0x61a80", + "to": "0x9f610e100c751e7a632e7f362b2cc6302c758666", + "value": "0x0" + }, + { + "data": "0xa9059cbb00000000000000000000000040d56df84f5724b1052d83a34e4ed141b028d5ec0000000000000000000000000000000000000000000000015af1d78b58c40000", + "flags": "0x0", + "gasLimit": "0x186a0", + "mode": "0x2", + "stateGasLimit": "0x30d40", + "to": "0x57b6cef8632ff83fdfb01f80939a7c93c106af5b", + "value": "0x0" + }, + { + "data": "0x8321fa67ea822a00bfa02d9279db7d0d5c7f797c35266c03195ec44478e4197c2774e791", + "flags": "0x0", + "gasLimit": "0xf4240", + "mode": "0x0", + "stateGasLimit": "0x61a80", + "to": "0x9f610e100c751e7a632e7f362b2cc6302c758666", + "value": "0x0" + } + ], + "from": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "hash": "0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x3b9aca0e", + "maxPriorityFeePerGas": "0x3b9aca00", + "nonceKeys": [ + "0x0" + ], + "nonceSeq": "0x1", + "sender": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "signatures": [ + { + "msg": "0x", + "scheme": "0x1", + "signature": "0x0071a1c9ba4a124d51c962d23c4e6b7b1bfc81069606f9f709887477493d6982534e034ce22b27d82ae5dec1c9ee141b2010ae69dfc16b3512d5f5d818b10fb935", + "signer": "0xdd83c15d23d84d34797d13015667ce4fcc6317b2" + } + ], + "transactionIndex": "0x2", + "type": "0x6" + }, + "receipt": { + "blockHash": "0xd416e4f252df9b4adf2b0a070d2afdadf0be5b8b83e33fea490a9b19c3ef5df3", + "blockNumber": "0x954b", + "contractAddress": null, + "cumulativeGasUsed": "0x92f17", + "effectiveGasPrice": "0x3b9aca07", + "frameReceipts": [ + { + "gasUsed": "0x176", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x124f", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x7649", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x1" + }, + { + "gasUsed": "0x1", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x0" + }, + { + "gasUsed": "0x7649", + "logs": [ + { + "address": "0x57b6cef8632ff83fdfb01f80939a7c93c106af5b", + "data": "0x0000000000000000000000000000000000000000000000015af1d78b58c40000", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "0x00000000000000000000000040d56df84f5724b1052d83a34e4ed141b028d5ec" + ] + } + ], + "stateGasUsed": "0x17e80", + "status": "0x1" + }, + { + "gasUsed": "0x19c5", + "logs": [], + "stateGasUsed": "0x0", + "status": "0x0" + } + ], + "from": "0x40a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "gasUsed": "0x2f0a7", + "logs": [ + { + "address": "0x57b6cef8632ff83fdfb01f80939a7c93c106af5b", + "blockHash": "0xd416e4f252df9b4adf2b0a070d2afdadf0be5b8b83e33fea490a9b19c3ef5df3", + "blockNumber": "0x954b", + "blockTimestamp": "0x6aac1c37", + "data": "0x0000000000000000000000000000000000000000000000015af1d78b58c40000", + "logIndex": "0x2", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000040a819fbba41d5a78b8b26fb2c899db2a64f4d8a", + "0x00000000000000000000000040d56df84f5724b1052d83a34e4ed141b028d5ec" + ], + "transactionHash": "0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971", + "transactionIndex": "0x2" + } + ], + "logsBloom": "0x00000000000000000000000200080000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000800000000020000000000000000000000080000000000000000000000000000000000000000000000001000000000000000000000000000002000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "payer": "0x9f610e100c751e7a632e7f362b2cc6302c758666", + "status": "0x0", + "to": null, + "transactionHash": "0xf34ec26c02d93978204a074baed7a95b018c43807f1bc6381514bedc4546b971", + "transactionIndex": "0x2", + "type": "0x6" + }, + "simulate": { + "executionError": null, + "executionStatus": null, + "frames": null, + "gasUsed": null, + "maxCost": "0x9512f73b16a1a", + "payer": null, + "prefixShape": "OnlyVerifyPay", + "valid": false, + "violation": "Invalid Transaction: Nonce mismatch: expected 8, got 1" + } +} diff --git a/test/fixtures/golden.ts b/test/fixtures/golden.ts index a07376e..9e1638e 100644 --- a/test/fixtures/golden.ts +++ b/test/fixtures/golden.ts @@ -1,8 +1,9 @@ import type { FrameTransaction } from '../../src/types.js' /** Two frames — a targetless VERIFY carrying data, then a SENDER frame with a - * target — one SECP256K1 signature with an empty msg and a non-zero state - * budget on the VERIFY frame. This is Ethrex v23's wire-format golden vector. */ + * target — one SECP256K1 signature with an empty msg, key `[0]` at sequence 7, + * and no state budget on either frame. This is `golden()` in ethrex's + * `test/tests/common/frame_tx_wire_tests.rs` at `bdfc5d8`. */ /** EIP-55 form of `0x…abcd`. viem's `getAddress` — and therefore `decodeFrameTx` * and `parseRpcFrameTransaction` — returns checksummed addresses, so the fixture * is written checksummed and `toEqual` against decoder output holds without case @@ -12,14 +13,15 @@ const ABCD = '0x000000000000000000000000000000000000ABcD' as const export const GOLDEN_TX: FrameTransaction = { chainId: 1n, - nonce: 7n, + nonceKeys: [0n], + nonceSeq: 7n, sender: ABCD, frames: [ { mode: 1, flags: 3, target: null, - limits: { execution: 0x5208n, state: 0x1e8480n }, + limits: { execution: 0x5208n, state: 0n }, value: 0n, data: '0x1122', }, @@ -46,15 +48,19 @@ export const GOLDEN_TX: FrameTransaction = { blobVersionedHashes: [], } -/** Copy VERBATIM from frame_tx_wire_tests.rs:67. Do not reflow or re-wrap: - * a hand-wrapped transcription of this string was already caught being - * 10 characters short. */ -export const GOLDEN_RLP = - '0x06f8b3010794000000000000000000000000000000000000abcdefcf010380c7825208831e848080821122de0280940000000000000000000000000000000000001234c4829c40808080f85cf85a0194000000000000000000000000000000000000abcd80b8410101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101cc843b9aca008506fc23ac0080c0' as const +/** Copy VERBATIM from `frame_tx_wire_tests.rs:69` at ethrex `bdfc5d8`, the + * body without its type byte; `scripts/hegota-testnet/frametx.py:113` asserts + * the same string. Do not reflow or re-wrap: a hand-wrapped transcription of the + * previous vector was already caught being 10 characters short. */ +export const GOLDEN_BODY = + '0xf8b201c1800794000000000000000000000000000000000000abcdeccc010380c48252088080821122de0280940000000000000000000000000000000000001234c4829c40808080f85cf85a0194000000000000000000000000000000000000abcd80b8410101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101cc843b9aca008506fc23ac0080c0' as const -/** From frame_tx_wire_tests.rs:69. */ +/** The wire form, `0x06 || body`. */ +export const GOLDEN_RLP = `0x06${GOLDEN_BODY.slice(2)}` as const + +/** From `frame_tx_wire_tests.rs:70` at `bdfc5d8`. */ export const GOLDEN_SIG_HASH = - '0x2518df13bab80fe6bcc7a7e462dea6d617ad6992d53411ddbaff26a8cbc22341' as const + '0x73827d510b0029220c237a46b27f6b6b8e7a3fd3b52c42a55c6c6e343fc45951' as const /** The golden transaction as the node would serve it from * `eth_getTransactionByHash`, minus the block fields. Field names and formats @@ -64,10 +70,11 @@ export const GOLDEN_SIG_HASH = export const GOLDEN_RPC_JSON = { type: '0x6', chainId: '0x1', - nonce: '0x7', + nonceKeys: ['0x0'], + nonceSeq: '0x7', sender: '0x000000000000000000000000000000000000abcd', frames: [ - { mode: '0x1', flags: '0x3', to: null, gasLimit: '0x5208', stateGasLimit: '0x1e8480', + { mode: '0x1', flags: '0x3', to: null, gasLimit: '0x5208', stateGasLimit: '0x0', value: '0x0', data: '0x1122' }, { mode: '0x2', flags: '0x0', to: '0x0000000000000000000000000000000000001234', gasLimit: '0x9c40', stateGasLimit: '0x0', value: '0x0', data: '0x' }, diff --git a/test/gas.test.ts b/test/gas.test.ts index 9ee91ac..b36b5e7 100644 --- a/test/gas.test.ts +++ b/test/gas.test.ts @@ -1,24 +1,53 @@ import { describe, expect, test } from 'vitest' -import { frameTxGas, frameTxMaxCost } from '../src/gas.js' +import { frameTxGas, frameTxMaxCost, nonceCalldata } from '../src/gas.js' import { GOLDEN_TX } from './fixtures/golden.js' +// Hand derivation for the golden vector. Billed fields, at 4 gas per zero byte +// and 16 per non-zero byte: +// frame 0 data 0x1122 2 bytes 2 non-zero 32 +// frame 1 data (empty) 0 bytes 0 +// signer 0x…abcd 20 bytes 18 zero, 2 non-zero 104 +// msg (empty) 0 bytes 0 +// signature 0x01 * 65 65 bytes 65 non-zero 1040 +// nonce calldata rlp([0]) || rlp(7) = c180 07 3 bytes 3 non-zero 48 +// total 90 bytes 1224 +// mandatory = 12000 + 2 * 475 + 2800 (SECP256K1) 15750 +// execution limits = 0x5208 + 0x9c40 = 21000 + 40000 61000 describe('frameTxGas on the golden vector', () => { const gas = frameTxGas(GOLDEN_TX, 'chain') test('mandatory gas', () => expect(gas.mandatoryGas).toBe(15_750n)) - test('billed byte count', () => expect(gas.billedBytes).toBe(87n)) - test('data cost', () => expect(gas.dataCost).toBe(1_176n)) - test('intrinsic gas', () => expect(gas.intrinsicGas).toBe(16_926n)) - test('state gas limit', () => expect(gas.stateGasLimit).toBe(2_000_000n)) - test('standard gas limit', () => expect(gas.standardGasLimit).toBe(2_077_926n)) - test('calldata tokens', () => expect(gas.calldataTokens).toBe(348n)) - test('calldata floor total', () => expect(gas.calldataFloorTotal).toBe(21_318n)) - test('max gas takes the standard branch', () => expect(gas.maxGas).toBe(2_077_926n)) + test('billed byte count', () => expect(gas.billedBytes).toBe(90n)) + test('data cost', () => expect(gas.dataCost).toBe(1_224n)) + test('intrinsic gas', () => expect(gas.intrinsicGas).toBe(16_974n)) + test('state gas limit', () => expect(gas.stateGasLimit).toBe(0n)) + // 16974 + 61000 + 0 + test('standard gas limit', () => expect(gas.standardGasLimit).toBe(77_974n)) + // 90 * 4 + test('calldata tokens', () => expect(gas.calldataTokens).toBe(360n)) + // 15750 + 360 * 16 + test('calldata floor total', () => expect(gas.calldataFloorTotal).toBe(21_510n)) + test('max gas takes the standard branch', () => expect(gas.maxGas).toBe(77_974n)) +}) + +describe('EIP-8250 nonce calldata', () => { + test('is rlp(nonce_keys) || rlp(nonce_seq)', () => { + expect(nonceCalldata(GOLDEN_TX)).toBe('0xc18007') + }) + + test('is billed for a keyed nonce at the calldata rate', () => { + // rlp([1, 2**255]) = e2 01 a0 80 00*31 (35 bytes), rlp(7) = 07: 36 bytes, + // 31 of them zero. 5 * 16 + 31 * 4 = 204, against 48 for the golden `[0]`. + const keyed = { ...GOLDEN_TX, nonceKeys: [1n, 2n ** 255n] } + expect(frameTxGas(keyed, 'chain').billedBytes).toBe(90n - 3n + 36n) + expect(frameTxGas(keyed, 'chain').dataCost).toBe(1_224n - 48n + 204n) + }) }) describe('frameTxMaxCost', () => { test('is maxGas * maxFeePerGas with no blobs', () => { - expect(frameTxMaxCost(GOLDEN_TX, 0n, 'chain')).toBe(62_337_780_000_000_000n) + // 77974 * 0x6fc23ac00 (30 gwei) + expect(frameTxMaxCost(GOLDEN_TX, 0n, 'chain')).toBe(2_339_220_000_000_000n) }) test('adds the blob term at the base rate', () => { diff --git a/test/live/simulate.test.ts b/test/live/simulate.test.ts index ed392ab..03fd31b 100644 --- a/test/live/simulate.test.ts +++ b/test/live/simulate.test.ts @@ -1,11 +1,11 @@ import { createPublicClient, http } from 'viem' import { describe, expect, test } from 'vitest' -const RPC_URL = 'https://rpc1.frames.ethrex.xyz' -const CHAIN_ID = 81410 +const RPC_URL = 'https://rpc1.privacy.ethrex.xyz' +const CHAIN_ID = 8141 const live = process.env.FRAMES_LIVE === '1' -describe.skipIf(!live)('Ethrex frames devnet', () => { +describe.skipIf(!live)('Ethrex hegota-testnet', () => { const client = createPublicClient({ transport: http(RPC_URL) }) test('reports the expected chain and an Ethrex client', async () => { diff --git a/test/nonce.test.ts b/test/nonce.test.ts new file mode 100644 index 0000000..3174bcd --- /dev/null +++ b/test/nonce.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from 'vitest' +import { FrameEncodeError } from '../src/errors.js' +import { NONCE_MANAGER, keyedNonceSlot } from '../src/nonce.js' + +// Expected slots computed with foundry's `cast keccak` over +// left_pad_32(sender) || uint256_to_bytes32(key), not with this library. +describe('keyedNonceSlot', () => { + test('matches the EIP-8250 slot for key 1', () => { + expect(keyedNonceSlot('0x23d3957be879aba6ca925ee4f072d1a8c4e8c890', 1n)).toBe( + '0x44b4c2b272c094a086fba3d2855ee62a9a7fe342d92cc698147066d6735b79da', + ) + }) + + test('is case-insensitive in the sender', () => { + expect(keyedNonceSlot('0x19E7E376E7C213B7E7e7e46cc70A5dD086DAff2A', 5n)).toBe( + keyedNonceSlot('0x19e7e376e7c213b7e7e7e46cc70a5dd086daff2a', 5n), + ) + }) + + test('refuses key 0, which is the account nonce and has no slot', () => { + expect(() => keyedNonceSlot(NONCE_MANAGER, 0n)).toThrow(FrameEncodeError) + }) + + test('refuses a key wider than 256 bits', () => { + expect(() => keyedNonceSlot(NONCE_MANAGER, 2n ** 256n)).toThrow(FrameEncodeError) + }) +}) diff --git a/test/prepareFrameTransaction.test.ts b/test/prepareFrameTransaction.test.ts index 5843147..5c67908 100644 --- a/test/prepareFrameTransaction.test.ts +++ b/test/prepareFrameTransaction.test.ts @@ -3,22 +3,28 @@ import { createClient, custom, getAddress } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains' import { toEoaFrameAccount } from '../src/accounts/toEoaFrameAccount.js' +import { keyedNonceSlot } from '../src/nonce.js' import { prepareFrameTransaction } from '../src/prepareFrameTransaction.js' import { assertValidFrameTx } from '../src/signatures.js' const OWNER_KEY = `0x${'11'.repeat(32)}` as const -function testClient() { +/** `storage` maps a NONCE_MANAGER slot to its value; unlisted slots read zero. */ +function testClient(storage: Record = {}) { const requests: { method: string; params?: unknown }[] = [] const owner = privateKeyToAccount(OWNER_KEY) const client = createClient({ account: owner, - chain: { ...mainnet, id: 81_410 }, + chain: { ...mainnet, id: 8_141 }, transport: custom({ async request(request) { requests.push(request) switch (request.method) { case 'eth_getTransactionCount': return '0x7' + case 'eth_getStorageAt': { + const slot = (request.params as string[])[1]! + return `0x${(storage[slot] ?? 0n).toString(16).padStart(64, '0')}` + } case 'eth_maxPriorityFeePerGas': return '0x3b9aca00' case 'eth_gasPrice': return '0x77359400' case 'eth_getBlockByNumber': return { baseFeePerGas: '0x77359400' } @@ -53,8 +59,9 @@ describe('prepareFrameTransaction', () => { ) expect(transaction).toMatchObject({ - chainId: 81_410n, - nonce: 7n, + chainId: 8_141n, + nonceKeys: [0n], + nonceSeq: 7n, sender: owner.address, signatures: [], maxPriorityFeePerGas: 1_000_000_000n, @@ -94,6 +101,37 @@ describe('prepareFrameTransaction', () => { expect(requests.some(({ method }) => method === 'eth_chainId')).toBe(false) }) + test('selects non-zero keys at their shared NONCE_MANAGER sequence', async () => { + const owner = privateKeyToAccount(OWNER_KEY) + const [one, nine] = [1n, 9n].map((key) => keyedNonceSlot(owner.address, key)) + const { client } = testClient({ [one!]: 4n, [nine!]: 4n }) + const account = await toEoaFrameAccount({ client, owner }) + + const transaction = await prepareFrameTransaction( + account, + [{ to: owner.address, value: 0n, data: '0x' }], + { validation: { execution: 1n, state: 0n }, calls: [{ execution: 1n, state: 0n }] }, + { nonceKeys: [1n, 9n] }, + ) + expect(transaction.nonceKeys).toEqual([1n, 9n]) + expect(transaction.nonceSeq).toBe(4n) + }) + + test('refuses keys that sit at different sequences', async () => { + const owner = privateKeyToAccount(OWNER_KEY) + const { client } = testClient({ [keyedNonceSlot(owner.address, 2n)]: 1n }) + const account = await toEoaFrameAccount({ client, owner }) + + await expect( + prepareFrameTransaction( + account, + [{ to: owner.address, value: 0n, data: '0x' }], + { validation: { execution: 1n, state: 0n }, calls: [{ execution: 1n, state: 0n }] }, + { nonceKeys: [2n, 3n] }, + ), + ).rejects.toThrow(/different sequences: key 2 is at 1, key 3 is at 0/) + }) + test('requires one call-limit entry per call', async () => { const { client, owner, requests } = testClient() const account = await toEoaFrameAccount({ client, owner }) diff --git a/test/signatures.test.ts b/test/signatures.test.ts index 66b65de..ee8a893 100644 --- a/test/signatures.test.ts +++ b/test/signatures.test.ts @@ -334,7 +334,7 @@ describe('assertValidFrameTx', () => { // The account path exists so a key this library never sees — a hardware wallet, a // KMS, an HD account — can sign. Its bytes must be indistinguishable from the -// private-key path, which the golden sig-hash and oracle 3 already pin. +// private-key path, which the golden sig-hash and the captured chain signatures already pin. describe('signFrameTx with an account', () => { const oneEmptyEntry = (sender: Address) => ({ ...GOLDEN_TX, diff --git a/test/viem.test.ts b/test/viem.test.ts index b0ec1bc..3008b54 100644 --- a/test/viem.test.ts +++ b/test/viem.test.ts @@ -76,7 +76,7 @@ describe('frameActions', () => { test('estimateFrameGas prices under the requested rule set', () => { const actions = frameActions(stubClient({})) - expect(actions.estimateFrameGas({ transaction: GOLDEN_TX }).maxGas).toBe(2_077_926n) + expect(actions.estimateFrameGas({ transaction: GOLDEN_TX }).maxGas).toBe(77_974n) }) test('composes with client.extend on a real viem client', () => { @@ -87,7 +87,7 @@ describe('frameActions', () => { frameActions, ) expect(typeof client.getFrameTransaction).toBe('function') - expect(client.estimateFrameGas({ transaction: GOLDEN_TX }).maxGas).toBe(2_077_926n) + expect(client.estimateFrameGas({ transaction: GOLDEN_TX }).maxGas).toBe(77_974n) }) test('waitForFrameTransactionReceipt polls until the receipt is present', async () => { From 90bc2eb153d447f811d6320100e91f0ad8860f8a Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 24 Sep 2026 13:04:46 +0300 Subject: [PATCH 2/5] refactor: remove the rule-set argument and compareRuleSets + export the hegotaTestnet chain definition --- .github/ISSUE_TEMPLATE/03-feature.yml | 3 +- .github/workflows/pr-title.yml | 3 +- CONTRIBUTING.md | 4 +-- README.md | 15 ++++----- docs/DESIGN.md | 5 +-- scripts/send-eth-eoa.ts | 16 +++------- src/chain.ts | 11 +++++++ src/divergence.ts | 44 --------------------------- src/gas.ts | 17 +++-------- src/index.ts | 5 +-- src/types.ts | 7 ----- src/viem.ts | 12 ++------ test/chain.oracle.test.ts | 4 +-- test/chain.test.ts | 10 ++++++ test/divergence.test.ts | 25 --------------- test/gas.test.ts | 35 +++++++++------------ test/live/simulate.test.ts | 7 ++--- test/viem.test.ts | 2 +- 18 files changed, 69 insertions(+), 156 deletions(-) create mode 100644 src/chain.ts delete mode 100644 src/divergence.ts create mode 100644 test/chain.test.ts delete mode 100644 test/divergence.test.ts diff --git a/.github/ISSUE_TEMPLATE/03-feature.yml b/.github/ISSUE_TEMPLATE/03-feature.yml index 11463da..a88cefc 100644 --- a/.github/ISSUE_TEMPLATE/03-feature.yml +++ b/.github/ISSUE_TEMPLATE/03-feature.yml @@ -28,7 +28,8 @@ body: - sighash - signatures - gas - - divergence + - nonce + - accounts - rlp - rpc - viem diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index ccaf878..12d7f4b 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -32,7 +32,8 @@ jobs: sighash signatures gas - divergence + nonce + accounts rlp rpc viem diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d511300..5736972 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -129,7 +129,7 @@ Historical state is pruned. `ethrex_simulateFrameTransaction` works only against `rlp` / `errors` / `types` → `envelope` → `sighash` → `signatures`. `gas` depends only on `rlp` and `types`. `rpc` depends only on `errors` and `types`. `viem` sits on top — it composes `envelope`, `signatures`, `rpc` and `gas` and holds no wire logic of its own. -`divergence` depends on `gas`. Nothing imports upward. +Nothing imports upward. ## Tests @@ -171,7 +171,7 @@ That means **your commit messages set the version**. Use | `docs:`, `chore:`, `test:`, `ci:`, `refactor:`, `style:`, `perf:`, `build:`, `revert:` | no release | Pull request titles are validated by CI against that list, with an optional scope from -`envelope`, `sighash`, `signatures`, `gas`, `divergence`, `rlp`, `rpc`, `viem`, +`envelope`, `sighash`, `signatures`, `gas`, `nonce`, `accounts`, `rlp`, `rpc`, `viem`, `fixtures`, `docs`, `deps`, `ci`, `repo` — for example `fix(envelope): reject a zero nonce key beside other keys`. Subjects start lowercase. diff --git a/README.md b/README.md index 8291774..68a679a 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,12 @@ your client and this library have to be the same viem. ```ts import { createPublicClient, http } from 'viem' +import { hegotaTestnet } from '@jaw.id/frametx-kit' import { frameActions } from '@jaw.id/frametx-kit/viem' const client = createPublicClient({ - transport: http('https://rpc1.privacy.ethrex.xyz'), + chain: hegotaTestnet, + transport: http(), }).extend(frameActions) const tx = await client.getFrameTransaction({ hash }) @@ -111,14 +113,13 @@ can put back on the wire byte-for-byte. ## Gas -`frameTxGas` prices the EIP-8141 transaction shape, including EIP-8250's nonce -calldata. The historical -`'chain'`, `'pins'`, and `'head'` rule-set names remain accepted as compatibility -aliases and currently produce identical results. +`frameTxGas` prices a transaction from its declared limits, including EIP-8250's nonce +calldata, and `frameTxMaxCost` gives the most it can cost the payer. ```ts -import { compareRuleSets, decodeFrameTx } from '@jaw.id/frametx-kit' -console.log(compareRuleSets(decodeFrameTx(raw), 'chain', 'pins')) +import { frameTxGas, frameTxMaxCost } from '@jaw.id/frametx-kit' +const { intrinsicGas, maxGas } = frameTxGas(tx) +const maxCost = frameTxMaxCost(tx, blobBaseFee) ``` Every gas constant is written as its published figure rather than derived. ethrex's own diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 9ce8c43..9f36d7e 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -134,9 +134,6 @@ non-zero key whose slot is still zero, and `NEW_ACCOUNT_STATE_GAS` (183,600) for from a sender that does not exist yet. These are budgeting figures for choosing `limits.state`, not terms of `frameTxGas`. -The `'chain'`, `'pins'` and `'head'` rule-set arguments are compatibility aliases and price -identically. - ## 5. Accounts and preparation A `FrameAccountImplementation` supplies `getAddress()`, `getNonce({ key?, blockTag? })` (the @@ -206,12 +203,12 @@ as a backstop. Strict decode is `decodeFrameTx(raw)` followed by `assertValidFra | Module | Responsibility | Depends on | |---|---|---| | `types`, `errors` | The transaction shape; typed errors | — | +| `chain` | `hegotaTestnet`, a viem chain definition | viem | | `rlp` | Minimal scalars, `walkRlp` | `errors` | | `envelope` | `encodeFrameTx`, `decodeFrameTx`, `validateFrameTx` | `rlp`, `errors`, `types` | | `sighash` | `frameTxSigHash` | `envelope` | | `signatures` | Canonicality, recovery, signer resolution, `signFrameTx`, `assertValidFrameTx` | `sighash`, `envelope` | | `gas` | `frameTxGas`, `frameTxMaxCost`, `nonceCalldata` | `rlp`, `types` | -| `divergence` | `compareRuleSets` | `gas` | | `nonce` | `NONCE_MANAGER`, `keyedNonceSlot`, `getFrameNonceSeq` | `errors` | | `rpc` | Transaction and receipt parsing, simulate, broadcast | `errors`, `types` | | `accounts`, `prepareFrameTransaction`, `signFrameTransaction` | Frame accounts and transaction preparation | `envelope`, `signatures`, `nonce` | diff --git a/scripts/send-eth-eoa.ts b/scripts/send-eth-eoa.ts index 7332c95..790c5ee 100644 --- a/scripts/send-eth-eoa.ts +++ b/scripts/send-eth-eoa.ts @@ -14,7 +14,6 @@ */ import { createPublicClient, - defineChain, formatEther, getAddress, http, @@ -29,6 +28,7 @@ import { encodeFrameTx, frameTxGas, frameTxMaxCost, + hegotaTestnet, parseRpcFrameReceipt, prepareFrameTransaction, simulateFrameTransaction, @@ -38,15 +38,7 @@ import { type RpcFrameReceiptJson, } from '../src/index.js' -const CHAIN_ID = 8141n -const DEFAULT_RPC_URL = 'https://rpc1.privacy.ethrex.xyz' const RECEIPT_TIMEOUT_MS = 120_000 -const hegotaTestnet = defineChain({ - id: Number(CHAIN_ID), - name: 'Ethrex Hegota Testnet', - nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, - rpcUrls: { default: { http: [DEFAULT_RPC_URL] } }, -}) function requiredEnv(name: string): string { const value = process.env[name] @@ -83,7 +75,7 @@ async function waitForFrameReceipt( } async function main(): Promise { - const rpcUrl = process.env.RPC_URL ?? DEFAULT_RPC_URL + const rpcUrl = process.env.RPC_URL ?? hegotaTestnet.rpcUrls.default.http[0] const owner = privateKeyToAccount(privateKeyFromEnv()) const recipient: Address = getAddress(requiredEnv('RECIPIENT')) const amount = parseEther(process.env.AMOUNT_ETH ?? '0.001') @@ -109,8 +101,8 @@ async function main(): Promise { const raw = encodeFrameTx(signed) const transactionHash = keccak256(raw) - const gas = frameTxGas(signed, 'chain') - const maxGasCost = frameTxMaxCost(signed, 0n, 'chain') + const gas = frameTxGas(signed) + const maxGasCost = frameTxMaxCost(signed, 0n) console.log(`raw tx: ${raw}`) console.log(`sender: ${account.address}`) diff --git a/src/chain.ts b/src/chain.ts new file mode 100644 index 0000000..b8a77d8 --- /dev/null +++ b/src/chain.ts @@ -0,0 +1,11 @@ +import { defineChain } from 'viem' + +/** The Ethrex hegota-testnet, the chain this library's envelope targets. */ +export const hegotaTestnet = defineChain({ + id: 8141, + name: 'Ethrex Hegota Testnet', + nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { default: { http: ['https://rpc1.privacy.ethrex.xyz'] } }, + blockExplorers: { default: { name: 'Dora', url: 'https://dora.privacy.ethrex.xyz' } }, + testnet: true, +}) diff --git a/src/divergence.ts b/src/divergence.ts deleted file mode 100644 index 1d935bd..0000000 --- a/src/divergence.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { type FrameGas, frameTxGas } from './gas.js' -import type { FrameTransaction, RuleSet } from './types.js' - -export type GasDivergence = { - term: keyof FrameGas - a: bigint - b: bigint - /** Always non-negative: the absolute difference between the two rule sets. */ - delta: bigint -} - -/** - * Every gas term on which two rule sets disagree for this transaction. - * - * An empty result means the transaction is priced identically under both — which - * is the common case, because the live divergences are narrow. A non-empty result - * on live chain data that the divergence ledger does not already explain is a - * finding worth reporting. - * - * The rule-set names are compatibility aliases: hegota-testnet prices every - * frame transaction under one set of rules, keyed nonces included. - */ -export function compareRuleSets( - tx: FrameTransaction, - a: RuleSet, - b: RuleSet, -): GasDivergence[] { - const gasA = frameTxGas(tx, a) - const gasB = frameTxGas(tx, b) - - const divergences: GasDivergence[] = [] - for (const term of Object.keys(gasA) as (keyof FrameGas)[]) { - const left = gasA[term] - const right = gasB[term] - if (left === right) continue - divergences.push({ - term, - a: left, - b: right, - delta: left > right ? left - right : right - left, - }) - } - return divergences -} diff --git a/src/gas.ts b/src/gas.ts index 0b8baf9..de12264 100644 --- a/src/gas.ts +++ b/src/gas.ts @@ -1,6 +1,6 @@ import { type Address, type Hex, concatHex, toRlp } from 'viem' import { byteLength, rlpUint } from './rlp.js' -import type { FrameTransaction, RuleSet, SigScheme } from './types.js' +import type { FrameTransaction, SigScheme } from './types.js' // EIP-8141 Constants table. Written as published figures, never derived: ethrex's // own suite missed the intrinsic dropping from 15000 to 12000 across 1372 tests @@ -100,11 +100,8 @@ function valueTransferCost(tx: FrameTransaction): bigint { return total } -/** - * Price a frame transaction. The rule-set argument remains as a compatibility - * alias; all names use the current EIP-8141 accounting rules. - */ -export function frameTxGas(tx: FrameTransaction, _rules: RuleSet = 'chain'): FrameGas { +/** Price a frame transaction from its declared limits. */ +export function frameTxGas(tx: FrameTransaction): FrameGas { const valueCost = valueTransferCost(tx) const sigCost = tx.signatures.reduce( (acc, s) => acc + SIG_VERIFY_COST[s.scheme], @@ -154,12 +151,8 @@ export function frameTxGas(tx: FrameTransaction, _rules: RuleSet = 'chain'): Fra } /** TXPARAM 0x06: max_gas * max_fee_per_gas + len(blobs) * GAS_PER_BLOB * blob_base_fee. */ -export function frameTxMaxCost( - tx: FrameTransaction, - blobBaseFee: bigint, - rules: RuleSet = 'chain', -): bigint { - const { maxGas } = frameTxGas(tx, rules) +export function frameTxMaxCost(tx: FrameTransaction, blobBaseFee: bigint): bigint { + const { maxGas } = frameTxGas(tx) return ( maxGas * tx.maxFeePerGas + BigInt(tx.blobVersionedHashes.length) * GAS_PER_BLOB * blobBaseFee diff --git a/src/index.ts b/src/index.ts index b440397..3954e2a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * from './errors.js' export * from './types.js' +export { hegotaTestnet } from './chain.js' export { type DirectFrameExecution, type FrameAccount, @@ -69,10 +70,6 @@ export { frameTxMaxCost, nonceCalldata, } from './gas.js' -export { - type GasDivergence, - compareRuleSets, -} from './divergence.js' export { type FrameReceipt, type FrameRpcClient, diff --git a/src/types.ts b/src/types.ts index dd19f8a..b034640 100644 --- a/src/types.ts +++ b/src/types.ts @@ -53,10 +53,3 @@ export type FrameTransaction = { maxFeePerBlobGas: bigint blobVersionedHashes: Hex[] } - -/** - * Which rule set to price under. - * Kept for API compatibility with the kit's gas helpers. hegota-testnet prices - * every frame transaction under one set of rules, so all three names are aliases. - */ -export type RuleSet = 'chain' | 'pins' | 'head' diff --git a/src/viem.ts b/src/viem.ts index 994f2dd..e5c6acb 100644 --- a/src/viem.ts +++ b/src/viem.ts @@ -14,7 +14,7 @@ import { simulateFrameTransaction, } from './rpc.js' import { assertValidFrameTx } from './signatures.js' -import type { FrameTransaction, RuleSet } from './types.js' +import type { FrameTransaction } from './types.js' /** * viem extension: `client.extend(frameActions)`. @@ -137,14 +137,8 @@ export function frameActions(client: FrameRpcClient) { return sendRawFrameTransaction(client, encodeFrameTx(args.transaction)) }, - estimateFrameGas({ - transaction, - rules = 'chain', - }: { - transaction: FrameTransaction - rules?: RuleSet - }): FrameGas { - return frameTxGas(transaction, rules) + estimateFrameGas({ transaction }: { transaction: FrameTransaction }): FrameGas { + return frameTxGas(transaction) }, } } diff --git a/test/chain.oracle.test.ts b/test/chain.oracle.test.ts index c410fbf..56a77bf 100644 --- a/test/chain.oracle.test.ts +++ b/test/chain.oracle.test.ts @@ -45,14 +45,14 @@ describe.each(cases)('signatures and gas: %s', (_name, fixture) => { test("our maxCost equals the node's", () => { // Blob-free on this chain so far; the blob term needs the base fee at capture. expect(tx.blobVersionedHashes).toEqual([]) - expect(frameTxMaxCost(tx, 0n, 'chain')).toBe(BigInt(fixture.simulate.maxCost)) + expect(frameTxMaxCost(tx, 0n)).toBe(BigInt(fixture.simulate.maxCost)) }) // The calldata floor binds the regular dimension only; state gas goes on top. // Storage refunds are not itemized in frame receipts, so fixtures are chosen // from refund-free transactions and this stays an equality. test('gasUsed = max(intrinsic + sum of frame execution, floor) + sum of frame state', () => { - const gas = frameTxGas(tx, 'chain') + const gas = frameTxGas(tx) const { frameReceipts } = parseRpcFrameReceipt(fixture.receipt) const execution = frameReceipts.reduce((acc, f) => acc + f.gasUsed, 0n) const state = frameReceipts.reduce((acc, f) => acc + f.stateGasUsed, 0n) diff --git a/test/chain.test.ts b/test/chain.test.ts new file mode 100644 index 0000000..a5c94a0 --- /dev/null +++ b/test/chain.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, test } from 'vitest' +import { hegotaTestnet } from '../src/chain.js' + +describe('hegotaTestnet', () => { + test('is chain 8141 at the public endpoint', () => { + expect(hegotaTestnet.id).toBe(8141) + expect(hegotaTestnet.rpcUrls.default.http).toEqual(['https://rpc1.privacy.ethrex.xyz']) + expect(hegotaTestnet.testnet).toBe(true) + }) +}) diff --git a/test/divergence.test.ts b/test/divergence.test.ts deleted file mode 100644 index bebb913..0000000 --- a/test/divergence.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, test } from 'vitest' -import { compareRuleSets } from '../src/divergence.js' -import { frameTxGas } from '../src/gas.js' -import { GOLDEN_TX } from './fixtures/golden.js' - -describe('compatibility rule-set aliases', () => { - test('all rule-set names use the current accounting rules', () => { - expect(compareRuleSets(GOLDEN_TX, 'chain', 'pins')).toEqual([]) - expect(compareRuleSets(GOLDEN_TX, 'pins', 'head')).toEqual([]) - }) - - test('a targetless self-transfer has no value-transfer charge', () => { - const tx = { - ...GOLDEN_TX, - frames: [ - { - ...GOLDEN_TX.frames[1]!, - target: null, - value: 1n, - }, - ], - } - expect(frameTxGas(tx).valueTransferCost).toBe(0n) - }) -}) diff --git a/test/gas.test.ts b/test/gas.test.ts index b36b5e7..19df21c 100644 --- a/test/gas.test.ts +++ b/test/gas.test.ts @@ -14,7 +14,7 @@ import { GOLDEN_TX } from './fixtures/golden.js' // mandatory = 12000 + 2 * 475 + 2800 (SECP256K1) 15750 // execution limits = 0x5208 + 0x9c40 = 21000 + 40000 61000 describe('frameTxGas on the golden vector', () => { - const gas = frameTxGas(GOLDEN_TX, 'chain') + const gas = frameTxGas(GOLDEN_TX) test('mandatory gas', () => expect(gas.mandatoryGas).toBe(15_750n)) test('billed byte count', () => expect(gas.billedBytes).toBe(90n)) @@ -39,21 +39,21 @@ describe('EIP-8250 nonce calldata', () => { // rlp([1, 2**255]) = e2 01 a0 80 00*31 (35 bytes), rlp(7) = 07: 36 bytes, // 31 of them zero. 5 * 16 + 31 * 4 = 204, against 48 for the golden `[0]`. const keyed = { ...GOLDEN_TX, nonceKeys: [1n, 2n ** 255n] } - expect(frameTxGas(keyed, 'chain').billedBytes).toBe(90n - 3n + 36n) - expect(frameTxGas(keyed, 'chain').dataCost).toBe(1_224n - 48n + 204n) + expect(frameTxGas(keyed).billedBytes).toBe(90n - 3n + 36n) + expect(frameTxGas(keyed).dataCost).toBe(1_224n - 48n + 204n) }) }) describe('frameTxMaxCost', () => { test('is maxGas * maxFeePerGas with no blobs', () => { // 77974 * 0x6fc23ac00 (30 gwei) - expect(frameTxMaxCost(GOLDEN_TX, 0n, 'chain')).toBe(2_339_220_000_000_000n) + expect(frameTxMaxCost(GOLDEN_TX, 0n)).toBe(2_339_220_000_000_000n) }) test('adds the blob term at the base rate', () => { const tx = { ...GOLDEN_TX, blobVersionedHashes: [`0x${'ab'.repeat(32)}` as const] } - const gas = frameTxGas(tx, 'chain') - expect(frameTxMaxCost(tx, 7n, 'chain')).toBe( + const gas = frameTxGas(tx) + expect(frameTxMaxCost(tx, 7n)).toBe( gas.maxGas * tx.maxFeePerGas + 131_072n * 7n, ) }) @@ -69,7 +69,7 @@ describe('the calldata floor branch', () => { ], signatures: [], } - const gas = frameTxGas(tx, 'chain') + const gas = frameTxGas(tx) expect(gas.maxGas).toBe(gas.calldataFloorTotal + gas.stateGasLimit) expect(gas.maxGas).toBeGreaterThan(gas.standardGasLimit) }) @@ -87,8 +87,8 @@ describe('the calldata floor branch', () => { ...base, frames: [{ ...base.frames[0]!, limits: { execution: 1n, state: 50_000n } }], } - expect(frameTxGas(withState, 'chain').maxGas).toBe( - frameTxGas(base, 'chain').maxGas + 50_000n, + expect(frameTxGas(withState).maxGas).toBe( + frameTxGas(base).maxGas + 50_000n, ) }) }) @@ -113,34 +113,27 @@ describe('signature verification cost by scheme', () => { }) test('an ARBITRARY entry adds 100', () => { - expect(frameTxGas(withSchemes([0]), 'chain').signatureVerificationCost).toBe(100n) + expect(frameTxGas(withSchemes([0])).signatureVerificationCost).toBe(100n) }) test('a SECP256K1 entry adds 2800', () => { - expect(frameTxGas(withSchemes([1]), 'chain').signatureVerificationCost).toBe(2_800n) + expect(frameTxGas(withSchemes([1])).signatureVerificationCost).toBe(2_800n) }) test('a P256 entry adds 6700', () => { - expect(frameTxGas(withSchemes([2]), 'chain').signatureVerificationCost).toBe(6_700n) + expect(frameTxGas(withSchemes([2])).signatureVerificationCost).toBe(6_700n) }) test('the cost is the sum across a mixed list', () => { expect( - frameTxGas(withSchemes([1, 2, 0]), 'chain').signatureVerificationCost, + frameTxGas(withSchemes([1, 2, 0])).signatureVerificationCost, ).toBe(2_800n + 6_700n + 100n) }) test('it feeds mandatoryGas: 12000 + 475*len(frames) + sig cost', () => { // Golden has two frames; a lone P256 entry -> 12000 + 950 + 6700. - expect(frameTxGas(withSchemes([2]), 'chain').mandatoryGas).toBe( + expect(frameTxGas(withSchemes([2])).mandatoryGas).toBe( 12_000n + 475n * 2n + 6_700n, ) }) }) - -describe('rule sets', () => { - test('compatibility rule-set names price identically', () => { - expect(frameTxGas(GOLDEN_TX, 'chain')).toEqual(frameTxGas(GOLDEN_TX, 'pins')) - expect(frameTxGas(GOLDEN_TX, 'head')).toEqual(frameTxGas(GOLDEN_TX, 'pins')) - }) -}) diff --git a/test/live/simulate.test.ts b/test/live/simulate.test.ts index 03fd31b..7a5e6b3 100644 --- a/test/live/simulate.test.ts +++ b/test/live/simulate.test.ts @@ -1,15 +1,14 @@ import { createPublicClient, http } from 'viem' import { describe, expect, test } from 'vitest' +import { hegotaTestnet } from '../../src/chain.js' -const RPC_URL = 'https://rpc1.privacy.ethrex.xyz' -const CHAIN_ID = 8141 const live = process.env.FRAMES_LIVE === '1' describe.skipIf(!live)('Ethrex hegota-testnet', () => { - const client = createPublicClient({ transport: http(RPC_URL) }) + const client = createPublicClient({ chain: hegotaTestnet, transport: http() }) test('reports the expected chain and an Ethrex client', async () => { - expect(await client.getChainId()).toBe(CHAIN_ID) + expect(await client.getChainId()).toBe(hegotaTestnet.id) const version = await client.request({ method: 'web3_clientVersion' }) expect(version).toMatch(/^ethrex\//) }) diff --git a/test/viem.test.ts b/test/viem.test.ts index 3008b54..7231c68 100644 --- a/test/viem.test.ts +++ b/test/viem.test.ts @@ -74,7 +74,7 @@ describe('frameActions', () => { expect(receipt.frameReceipts[0]!.status).toBe('skipped') }) - test('estimateFrameGas prices under the requested rule set', () => { + test('estimateFrameGas prices the transaction', () => { const actions = frameActions(stubClient({})) expect(actions.estimateFrameGas({ transaction: GOLDEN_TX }).maxGas).toBe(77_974n) }) From 2fb5e551c44c046bb9813d5f0101b2394a918e15 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 24 Sep 2026 13:30:25 +0300 Subject: [PATCH 3/5] docs: point the PR template at CONTRIBUTING's Tests section --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 498c069..e7e8dfc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,7 +16,7 @@ Which oracle covers this change, and why it could actually fail: - [ ] A live receipt (recovered signer, `maxCost`, or the `gasUsed` decomposition) - [ ] N/A, this change has no wire-format or gas behaviour - + ## Load-bearing rules From d60d6905fd2531acbdd3d4c1eea5d764b70216c9 Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 24 Sep 2026 13:41:24 +0300 Subject: [PATCH 4/5] chore(scripts): fail clearly when capturing a pending tx by hash --- scripts/capture-fixtures.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/capture-fixtures.ts b/scripts/capture-fixtures.ts index bc484c1..019fa75 100644 --- a/scripts/capture-fixtures.ts +++ b/scripts/capture-fixtures.ts @@ -64,11 +64,12 @@ async function main() { async function* frameTransactions(): AsyncGenerator<{ n: bigint; tx: HydratedTx }> { if (byHash) { for (const hash of args) { - const tx = await rpc<(HydratedTx & { blockNumber: Hex }) | null>( + const tx = await rpc<(HydratedTx & { blockNumber: Hex | null }) | null>( 'eth_getTransactionByHash', [hash], ) if (tx === null) throw new Error(`${hash}: not found`) + if (tx.blockNumber === null) throw new Error(`${hash}: still pending, not yet mined`) yield { n: BigInt(tx.blockNumber), tx } } return From 02838d9e502f857df922e4cd227b50248ec1f63f Mon Sep 17 00:00:00 2001 From: Ghadi Mhawej Date: Thu, 24 Sep 2026 15:21:16 +0300 Subject: [PATCH 5/5] fix(nonce): validate nonceKeys before preparing a frame transaction --- src/envelope.ts | 28 ++++++++++++++++++---------- src/prepareFrameTransaction.ts | 9 +++++---- test/prepareFrameTransaction.test.ts | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/envelope.ts b/src/envelope.ts index ba89530..23ebb59 100644 --- a/src/envelope.ts +++ b/src/envelope.ts @@ -328,6 +328,23 @@ function assertFrameCount(tx: FrameTransaction): void { throw new FrameEncodeError(`at most ${MAX_FRAMES} frames, got ${tx.frames.length}`) } +/** + * The key-list half of `assertNonce`, which needs no sequence. Exported so that + * `prepareFrameTransaction` can refuse a bad key list before reading any nonces. + */ +export function assertNonceKeys(nonceKeys: readonly bigint[]): void { + if (nonceKeys.length < 1 || nonceKeys.length > MAX_NONCE_KEYS) + throw new FrameEncodeError( + `nonceKeys must hold between 1 and ${MAX_NONCE_KEYS} entries, got ${nonceKeys.length}`, + ) + for (const [i, key] of nonceKeys.entries()) assertUint(key, 256, `nonceKeys[${i}]`) + for (let i = 1; i < nonceKeys.length; i++) + if (nonceKeys[i - 1]! >= nonceKeys[i]!) + throw new FrameEncodeError('nonceKeys must be strictly increasing') + if (nonceKeys.length > 1 && nonceKeys[0] === 0n) + throw new FrameEncodeError('nonce key 0 is only valid as the sole nonce key') +} + /** * EIP-8250 keyed-nonce rules, from `validate_static_constraints` at ethrex * `bdfc5d8`. Key `0` is the account nonce and cannot be mixed with non-zero @@ -335,16 +352,7 @@ function assertFrameCount(tx: FrameTransaction): void { * `NONCE_MANAGER` slots, never both. */ function assertNonce(tx: FrameTransaction): void { - if (tx.nonceKeys.length < 1 || tx.nonceKeys.length > MAX_NONCE_KEYS) - throw new FrameEncodeError( - `nonceKeys must hold between 1 and ${MAX_NONCE_KEYS} entries, got ${tx.nonceKeys.length}`, - ) - for (const [i, key] of tx.nonceKeys.entries()) assertUint(key, 256, `nonceKeys[${i}]`) - for (let i = 1; i < tx.nonceKeys.length; i++) - if (tx.nonceKeys[i - 1]! >= tx.nonceKeys[i]!) - throw new FrameEncodeError('nonceKeys must be strictly increasing') - if (tx.nonceKeys.length > 1 && tx.nonceKeys[0] === 0n) - throw new FrameEncodeError('nonce key 0 is only valid as the sole nonce key') + assertNonceKeys(tx.nonceKeys) if (tx.nonceSeq >= U64_MAX) throw new FrameEncodeError('nonceSeq must be below 2**64 - 1') } diff --git a/src/prepareFrameTransaction.ts b/src/prepareFrameTransaction.ts index 6ddbe7d..058560a 100644 --- a/src/prepareFrameTransaction.ts +++ b/src/prepareFrameTransaction.ts @@ -1,5 +1,5 @@ import type { BlockTag, Chain, Hex } from 'viem' -import { MAX_FRAMES } from './envelope.js' +import { MAX_FRAMES, assertNonceKeys } from './envelope.js' import { FrameEncodeError } from './errors.js' import type { FrameAccount, FrameAccountImplementation, FrameCall } from './accounts/types.js' import type { FrameLimits, FrameTransaction } from './types.js' @@ -17,7 +17,9 @@ export type PrepareFrameTransactionOptions = { /** * EIP-8250 nonce keys to consume. Defaults to `[0n]`, the account nonce. With * several keys, every key must currently sit at the same sequence, because one - * `nonceSeq` is matched against all of them. + * `nonceSeq` is matched against all of them. The list must also pass the + * static EIP-8250 rules: at most `MAX_NONCE_KEYS`, strictly increasing, and + * key `0` only on its own. */ nonceKeys?: readonly bigint[] | undefined } @@ -96,8 +98,7 @@ export async function prepareFrameTransaction< const blockTag = options.blockTag ?? 'pending' const nonceKeys = [...(options.nonceKeys ?? [0n])] - if (nonceKeys.length === 0) - throw new FrameEncodeError('nonceKeys must hold at least one key') + assertNonceKeys(nonceKeys) const [sequences, fees] = await Promise.all([ Promise.all(nonceKeys.map((key) => account.getNonce({ key, blockTag }))), getFees(account, blockTag), diff --git a/test/prepareFrameTransaction.test.ts b/test/prepareFrameTransaction.test.ts index 5c67908..3b4b976 100644 --- a/test/prepareFrameTransaction.test.ts +++ b/test/prepareFrameTransaction.test.ts @@ -132,6 +132,28 @@ describe('prepareFrameTransaction', () => { ).rejects.toThrow(/different sequences: key 2 is at 1, key 3 is at 0/) }) + test.each([ + ['empty', [], /between 1 and 16 entries, got 0/], + ['too many', Array.from({ length: 17 }, (_, i) => BigInt(i + 1)), /between 1 and 16 entries, got 17/], + ['out of order', [2n, 1n], /strictly increasing/], + ['duplicated', [1n, 1n], /strictly increasing/], + ['key 0 with another key', [0n, 1n], /key 0 is only valid as the sole nonce key/], + ['negative', [-1n], /nonceKeys\[0\]/], + ])('refuses nonceKeys (%s) before making RPC requests', async (_, nonceKeys, message) => { + const { client, owner, requests } = testClient() + const account = await toEoaFrameAccount({ client, owner }) + + await expect( + prepareFrameTransaction( + account, + [{ to: owner.address, value: 0n, data: '0x' }], + { validation: { execution: 1n, state: 0n }, calls: [{ execution: 1n, state: 0n }] }, + { nonceKeys }, + ), + ).rejects.toThrow(message) + expect(requests).toEqual([]) + }) + test('requires one call-limit entry per call', async () => { const { client, owner, requests } = testClient() const account = await toEoaFrameAccount({ client, owner })