Skip to content

refactor(transcript): run Jolt on spongefish ProverState/VerifierState and delete the legacy facade - #2110

Draft
quangvdao wants to merge 17 commits into
transcript/j6-protocol-grindingfrom
transcript/j7-sponge-port
Draft

quangvdao wants to merge 17 commits into
transcript/j6-protocol-grindingfrom
transcript/j7-sponge-port

Conversation

@quangvdao

@quangvdao quangvdao commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Jolt's prover and verifier now run on spongefish 0.8.0's own ProverState and VerifierState, and the legacy jolt_transcript::Transcript facade is deleted. Stacked on #2108 (J6).

  • One sealed transcript. Transcript and FieldTranscript<F> are implemented once, on both spongefish states, from three sponge operations: absorb a public message, squeeze, and require. The prover and verifier run the same method bodies.
    • ProverTranscript adds the proof-of-work search.
    • VerifierTranscript adds NARG reads, check_grind and check_eof.
  • Messages. Every typed message is absorbed through spongefish Encoding. Field elements are their canonical little-endian bytes, behind a new optional spongefish feature on jolt-field. Commitments, claims and labels are typed messages.
  • Challenges. Every challenge is drawn through J2's ChallengeCodec, one codec message per draw site.
  • The instance is the full statement. The verifier builds its transcript from JOLT_SESSION and a JoltStatement that holds the protocol configuration and every public input it checks. Commitments then follow as labelled messages.
  • Proofs. They stay serde structs with their nonces in fields. Each field the verifier reads is absorbed as a public message, which spongefish absorbs exactly as it would a prover message. JoltProof gains an empty narg field, and the verifier calls check_eof on it. J8 builds on this.
  • Grinding. J6's grinding runs on J3's single SpongePow core. J6's nonce plumbing, tests and site table are kept, as is its search: the calling thread scans the first 1024 nonces, the pool scans the rest only if none passes, and either way the result is the least passing nonce. Stage 6b prices its burst and squeezes it through J6's single Stage6bDraws::sequence; here the sequence is fallible, because a draw on the sealed transcript returns a Result.
  • Short Dory challenges. J6's challenge_short_scalar is a FieldTranscript method, implemented once for both spongefish states. Its width is priced from the loss as in J6. It squeezes ceil(b/8) bytes, masks the top bits and decodes with from_bytes_le_reduced. On a field without room for the width, the verifier's state is poisoned; the prover gets an error. The Dory adapter keeps J6's loss table: beta 2 (129 bits), alpha 4 (130 bits), d 3 (130 bits). gamma and the zero-knowledge Sigma challenges stay full width. ScriptedTranscript records each short draw as its own event. challenge_bursts reads that event log.
  • Sponge selection. Keccak is the protocol sponge, with Blake2b512 behind transcript-blake2b. jolt-transcript's default features are Keccak only; Blake2b and Poseidon are opt-in. The Poseidon protocol build is off (see below).
  • The prover's grinding stays out of verifier builds. The proof-of-work nonce search copies the sponge state, which spongefish 0.8.0 exposes only under its yolocrypto feature. jolt-sumcheck and jolt-blindfold gain a prover feature that holds their prove side. Only jolt-prover, jolt-kernels and jolt-spartan-prover turn it on, so jolt-verifier's dependency graph has neither the nonce search nor yolocrypto. A CI step checks that graph.

Who used the facade, and what each now does

Crate Before After
jolt-transcript Transcript facade, DigestTranscript, SpongeTranscript, UnpricedTranscript, domain::{Label, …} Sealed Transcript/FieldTranscript on ProverState/VerifierState. ProverInit/VerifierInit constructors. codec::{Label, LabelWithCount, U64Word, BytesMsg}. Test-only ScriptedTranscript with a Script hook
jolt-verifier T::new(b"Jolt"), then absorb_preamble and a second hand-written copy for the prover validate_and_seed_transcript(…, init) builds the transcript from the statement. The stages draw through FieldTranscript
jolt-verifier-derive generated code named the facade generated code names FieldTranscript
jolt-prover stage 0 built T::new(b"Jolt"). The ZK replay compared state() stage 0 builds through ProverInit. The ZK replay checks against the forward transcript with probes
jolt-sumcheck RoundMessage::append_to_transcript over the facade, Nonced each round message absorbs a LabelWithCount word, then its coefficients as Encoding. The nonces are checked with check_public_grind
jolt-blindfold, jolt-openings, jolt-hyperkzg, jolt-spartan-* AppendToTranscript impls and bounds, one impl Transcript Encoding impls, FieldTranscript bounds
jolt-dory adapter over the facade adapter over FieldTranscript. Dory still absorbs every proof value itself
jolt-akita AppendToTranscript on the commitments Encoding. bridged_akita_session is changed only enough to compile
jolt-crypto, jolt-poly AppendToTranscript supertrait, EvaluationClaim impl Encoding
jolt-sdk, macros, examples, jolt-eval the ProtocolTranscript alias was used for both sides ProtocolSponge, ProofTranscript = ProverState<_>, VerifierTranscript<'a> = VerifierState<'a, _>
zklean-extractor — MleAst implements Encoding, because JoltField now requires it under feature unification
fuzz jolt-transcript targets used the facade's state() they compare the next 32 squeezed bytes

What was deleted

  • jolt-transcript:

    • legacy.rs: the facade, SpongeTranscript, and the facade's challenge decoders (from_challenge_bytes, challenge_width);
    • digest.rs: DigestTranscript;
    • unpriced.rs;
    • their tests (digest_transcript_tests.rs, legacy_digest_tests.rs);
    • the *Transcript type aliases.

    One decoder per concept is left: J2's ChallengeCodec.

  • AppendToTranscript, everywhere. Encoding replaces it.

  • The facade's challenge-set declarations. UnpricedTranscript's 2^256 declaration now comes from the test-only Script::UNPRICED.

  • The duplicated preamble absorb in the verifier.

  • Two jolt-sumcheck tests that built a field outside jolt-field (mersenne61_compat.rs, external-consumer/tests/external_field.rs). ChallengeCodec is sealed, so a field from another crate cannot draw challenges. That is deliberate: each codec's bias bound is argued next to its code.

  • transcript-poseidon and transcript-keccak on jolt-sdk, jolt-verifier and the examples. transcript-blake2b remains.

Byte-format change

Every proof and fixture changes. Proofs from before this PR do not verify.

  • Sponge. The Blake2b-256 digest chain is replaced by the spongefish Keccak duplex sponge (or Blake2b512 with transcript-blake2b).
  • Instance. The b"Jolt" label is replaced by JOLT_SESSION plus the encoded JoltStatement.
  • Absorbed bytes. Field elements are little-endian, not big-endian. Commitments and claims are typed messages: fixed-width group and field encodings go in without a length prefix, and variable-length parts (the Akita commitment bytes, its hiding evaluation, byte strings) keep a count word and a length-prefixed BytesMsg. Labels are 32-byte words.
  • Challenges. They decode from J2 codec messages, so the nonce values in the same proof slots change.
  • Proof layout. The serde proof structs are unchanged apart from the new JoltProof.narg (empty for now).

The fixtures and FS inventories are regenerated.

Fields the verifier reads but deliberately does not absorb

The test statement_binds_each_field_the_verifier_reads has two lists:

  • Bound fields. Changing one changes the next challenge.
  • Unbound fields. Changing one leaves the next challenge unchanged.

Each unbound field, and why it is safe:

  • The other MemoryLayout fields (input_start, output_start, output_end, stack_size, termination, program_size and so on). validate_inputs rejects any layout that differs from the preprocessing's, and the preprocessing digest is in the statement.
  • trusted_advice_commitment_present. The commitment itself is absorbed under its own label right after the statement, so its presence is bound.
  • vc_capacity. It belongs to the verifier's own setup, not to the proof.
  • The advice bytes. The verifier reads only their commitments, which are absorbed.
  • precommitted. It is derived during validation from bound fields (trace order, trace length, chunk bits, advice presence) and from the committed program, which the digest binds.

Soundness audit

An independent read-only soundness review (xhigh effort) compared every transcript call on this branch with J6. Verdict: sound with notes. No critical, high or medium finding. It checked, site by site:

  • Absorb before draw. Every message the verifier depends on is absorbed before the challenge drawn from it. This covers sum-check (round checks, then absorb, then nonce, then draw), BlindFold, the opening reductions, HyperKZG, Dory, Akita and the stage verifiers. Two runs of single draws became one draw each (Spartan outer tau, and the missing booleanity challenges). That changes bytes, not order.
  • Instance binding. The instance is the session id, then the encoded JoltStatement, each with its length. The statement binds every field the verifier reads, or the field is in the unbound list above with its reason.
  • Grinding. It is still per round and per site, with the same site set as J6: stages 1 and 2 twice each, stages 3, 4, 5, 6a, 6b and 7, Akita's stage 8, the three BlindFold sites, and every sum-check round. When a round needs no grinding, the proof must carry no nonce.
  • Unique decoding. Everything absorbed between two squeezes decodes uniquely. Field elements and group elements are fixed-width, and commitment counts are fixed by bound data. Labels, byte strings, the Akita commitments and the nonces carry their own lengths.
  • No panics. Challenge lengths are bounded before squeezing. Lengths are checked before absorbing. The NARG must be fully consumed.

Fixed on this branch after the review:

  • Low. The unlabelled UnivariatePoly round message absorbed its coefficients with no count, so a longer polynomial could absorb like a shorter one. Only fuzz targets used it, but it is public. It now absorbs as a labelled sum-check round with a count.
  • Low. The rule that every absorbed Encoding must decode uniquely is now written on the Transcript module. No type check enforces it.
  • Note. The prover-grinding comment said verifier builds leave yolocrypto off, but jolt-sumcheck and jolt-blindfold turned it on in every build. They now hold their prove side behind a prover feature, so verifier builds do leave it off. yolocrypto only makes the sponge state visible, and it changes no bytes.
  • Note. precommitted is now in the binding test's unbound list. Every unbound edit is also checked to actually change the inputs.

Left as notes:

  • ScriptedTranscript implements VerifierInit, but only test-utils enables it, and only dev-dependencies turn that on.
  • The verifier's own pcs_setup and vc_setup are trusted setup, not Fiat-Shamir input.
  • Dory continues with a zero challenge if its rejection pool runs out (probability at most 2^-161), then returns an error at finish().
  • Label's length assert! only sees constant labels.

Poseidon

The Poseidon protocol build is off: transcript-poseidon no longer exists on jolt-sdk, jolt-verifier or the examples. The sponge code stays in jolt-transcript, with its tests and the jolt-eval sponge invariants.

The reason: J2's codecs decode challenges from raw squeezed bytes, and they need a sponge whose bytes are uniform (UniformBytes). PoseidonSponge squeezes the little-endian bytes of BN254 scalars, whose top bits are far from uniform. So it cannot draw Jolt's challenges until the transcript absorbs and squeezes native Fr.

Draw bursts (listed, not merged)

A burst is several draw sites in a row with no absorb between them. Each site is its own codec message, as the design asked. The table shows what merging each burst into one message would save, measured on the muldiv fixtures with the verifier's real event order. The sponge is Keccak, which permutes once per 136-byte block it squeezes.

Each row is one burst in one honest verify of the muldiv fixture. The "Burst" column gives the stage and the ordinal of the burst's first draw within that stage. Every draw in these bursts is a single challenge. A negative saving means merging would cost bytes.

Burst (stage, first draw) Draws Dory, clear: bytes, permutations saved Dory, zk: bytes, permutations saved Akita: bytes, permutations saved
Stage 2, #2 15 (zk: 20) −15, 0 −20, 0 224, 2
Stage 2, #17 5 0, 0 — 64, 1
Stage 3, #0 3 (zk: 6) 0, 0 0, 0 32, 0
Stage 3, #3 3 0, 0 — 32, 0
Stage 4, #2 (zk: #1) 2 (zk: 3) 0, 0 0, 0 16, 0
Stage 5, #0 2 (zk: 5) 0, 0 0, 0 16, 0
Stage 5, #2 3 0, 0 — 32, 0
Stage 6a, #0 7 (zk: 9) 0, 0 −9, 0 96, 1
Stage 6a, #7 2 0, 0 — 16, 0
Stage 6b, #0 (Akita: #1) 2 (zk: 8; Akita: 5) 0, 0 0, 0 64, 1
Stage 6b, #2 6 0, 0 — —
Stage 7, #0 2 — 0, 0 —
Stage 8, #14 (zk: #16) 2 0, 0 0, 0 —
BlindFold, #13 3 — 0, 0 —
Total −15 B, 0 of 732 permutations −29 B, 0 of 936 592 B, 5 of 403

For BN254 Fr, merging saves nothing: the codec uses 52 bytes per element for 1 to 8 elements and 53 from 9 on. For the Akita field (p = 2^128 − 275), a message of n elements costs (n + 1) × 16 bytes instead of 32n, so merging saves 16 bytes per extra element.

Label words in the verifier's hash work

Every Label and LabelWithCount is one 32-byte word. Measured on the same verifies, with the Keccak permutation as the unit of hash work:

Build Label words Share of absorbed bytes Permutations saved by dropping every label
Dory, clear 642 23.4% (20,544 of 87,976 bytes) 93 of 732 (12.7%)
Dory, zk 417 12.7% (13,344 of 105,176 bytes) 36 of 936 (3.8%)
Akita 563 48.6% (18,016 of 37,060 bytes) 76 of 403 (18.9%)

The permutation counts include challenge squeezes. Labels are kept: they are what makes the absorbed stream decode uniquely across messages with no count of their own. Shrinking them (for example to a short tag) is a possible follow-up, and it would need its own unique-decoding argument.

API changes for J8 (transcript/j8-…)

  • The transcript traits lost their H parameter.
  • field_challenges, public_message and verifier_message moved into the shared Transcript/FieldTranscript traits.
  • New constructors VerifierInit<'a> and ProverInit.
  • New JoltStatement and JOLT_SESSION, plus JoltProof.narg.
  • validate_and_seed_transcript takes a closure that builds the transcript from the statement.
  • verify is now verify<'a, F, PCS, VC, T: FieldTranscript<F> + VerifierInit<'a>>.
  • New Script::check_bytes.
  • jolt-sumcheck's prove side needs its new prover feature: prove_batch, prove_uniskip_*, grind_round, CommittedSumcheckBuilder, CommittedSumcheckRecorder, and the recorders' absorb_round and finish. begin_batch and absorb_input_claims stay available to the verifier.
  • jolt-blindfold's prove, prove_with_row_committer and their witness and row-committer types need its new prover feature.
  • jolt-transcript's default features are transcript-keccak only. Crates that use Blake2b512 or PoseidonSponge name transcript-blake2b or transcript-poseidon.

Validation

All of this ran on a Boat sandbox, not locally:

  • Gates. cargo fmt --all --check, taplo fmt --check, cargo machete --with-metadata, scripts/check_style_invariants.py --base upstream/transcript/j6-protocol-grinding, and scripts/check-external-sumcheck.sh. typos reports only an existing false positive in jolt-metal.

  • Clippy -D warnings. Run on:

    • the workspace with allocative,host,zk, with allocative,host,zk,transcript-blake2b, and with --no-default-features;
    • jolt-transcript with all features, and with bn254,solinas,binary,prover-grinding;
    • jolt-field with no features, spongefish, spongefish,bn254 and spongefish,bn254,solinas,binary;
    • jolt-sdk with host, guest-verifier, and each with transcript-blake2b;
    • jolt-verifier on ten feature sets across prover-fixtures, zk, akita, field-inline and fs-audit;
    • jolt-prover on thirteen feature sets across akita, prover-fixtures, zk, profiling, allocative and field-inline;
    • jolt-sumcheck with no features, committed, r1cs, committed,r1cs, prover, committed,prover, committed,r1cs,prover and parallel,prover, and --no-default-features;
    • jolt-blindfold with and without prover, and jolt-sumcheck with jolt-blindfold together without it;
    • jolt-verifier's library alone, without the dev-dependency on jolt-prover;
    • jolt-transcript at its Keccak-only default, with test-utils, with transcript-blake2b, and with --no-default-features;
    • jolt-kernels and jolt-spartan-prover on their own.
  • Verifier feature graph. cargo tree -p jolt-verifier -e normal,features lists no prover, prover-grinding or yolocrypto feature. jolt-prover's graph still has all three.

  • Nextest. Every run passes:

    • jolt-transcript (default with test-utils, all features, sequential grinding, and its doctest), jolt-field, jolt-sumcheck (default, and committed,prover), jolt-blindfold (default, and prover), the external sumcheck consumer, the PCS and opening crates, jolt-kernels (with and without zk), jolt-akita, jolt-sdk, jolt-eval and zklean-extractor;
    • jolt-verifier lib, plus prover-fixtures, prover-fixtures,zk and akita,prover-fixtures;
    • fs_obligations (with and without akita) and fs_attacks (clear, zk, akita);
    • jolt-prover lib (prover-fixtures, and with zk) and the Akita profile tests.
  • After the rebase onto J6's 2d5e401. These all pass:

    • fs_obligations (with and without akita) and fs_attacks (clear, zk, akita);
    • jolt-transcript (all features, and Keccak with test-utils) and jolt-dory, including challenge_bursts and the short-draw byte tests;
    • the jolt-verifier lib, and jolt-verifier with prover-fixtures;
    • the full clippy matrix above and the verifier-graph check.

    The challenge census was re-blessed: stage 6b has one draw site, and the Dory adapter has one full and one short draw site.

  • After merging J6's 223511f (which brings Jolt main 14315e9). J6's new F8 uniskip test (crates/jolt-sumcheck/tests/f8_domain.rs) now runs on the spongefish transcript, with every assertion unchanged. It needs prover for grind_round, so its manifest entry and the two new binary-field CI steps enable jolt-sumcheck/prover. Main's minimum trace length (validate_trace_length) is kept, and its doc comments now name J7's API. These all pass:

    • fmt, and jolt-transcript clippy (five configurations) and tests;
    • jolt-dory, jolt-sumcheck (default, committed,prover, and binary) clippy and tests;
    • jolt-verifier clippy (--lib, prover-fixtures, prover-fixtures,zk) and its lib, prover-fixtures and prover-fixtures,zk tests;
    • fs_obligations with and without akita, fs_attacks (clear), and the verifier-graph check.

    The merge drops no test from either parent. Serialized proof sizes match J7's previous head d4e949c byte for byte on all nine verifier fixtures, clear and zk.

  • Regenerated. The verifier fuzz fixtures (clear and zk), the jolt-dory and jolt-crypto fuzz seeds, the fuzz lockfiles (jolt-claims' was stale and now has spongefish), and the absorb-site inventory. Every fuzz workspace passes cargo check --bins.

Left to CI (the heaviest end-to-end jobs):

  • Test jolt-prover: the plain, zk, akita, field-inline and Akita field-inline matrices, and the e2e jobs;
  • Test jolt-verifier: field-inline fixtures and akita fixtures;
  • the field-ops example;
  • Tracer serial/parallel equivalence and the x86 AOT tracer;
  • the Wasm build and WASM Verifier E2E;
  • the jolt binary check and the SDK verifier tests;
  • fuzzing.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

📏 PR diff

Note

Actual code changed: 4,026 lines

🟢 +1,987 added    🔴 −2,039 removed

Tests, docs, fixtures, and helper scripts excluded.

Category 🟢 Added 🔴 Removed
⌨️ Actual code +1,987 −2,039
🧪 Tests +2,302 −3,350
📚 Docs +401 −429
📦 Fixtures +576 −218
🔧 Helper scripts +26 −3
Total diff +5,292 −6,039

Every changed line is classified once.

@quangvdao
quangvdao added this pull request to stack #2112 October 21, 2026 15:50
@quangvdao
quangvdao force-pushed the transcript/j7-sponge-port branch 2 times, most recently from af6d719 to d04df1b Compare October 21, 2026 18:15
quangvdao added a commit to LayerZero-Labs/akita that referenced this pull request Oct 21, 2026
J7 (a16z/jolt#2110) was rebased, so 6691ec7cb is no longer on any ref.
At d04df1bb5, jolt-transcript's default sponge is Keccak alone; no Akita
crate relies on it, because every manifest sets default-features = false.
…cript

Bind the full statement as the spongefish instance, absorb commitments as
labeled messages, carry the NARG string in JoltProof and check its end, and
move per-round grinding onto the shared SpongePow core.
The fs-audit transcript is the production verifier state with a recording
script that also replays proof-of-work check bytes, so frozen replays over
changed inputs model a verifier whose every squeeze is fixed.
… the spongefish transcript

Keccak is the protocol sponge, Blake2b512 behind transcript-blake2b; the
Poseidon protocol selection is removed until the transcript absorbs native Fr.
jolt-prover names Fr challenges, so its transcript dependency enables bn254
itself instead of relying on feature unification. Drop an unused import in
the field-inline Akita fixtures and the SDK, and avoid unwrap in the zk
statistical test.
- An unlabelled UnivariatePoly round absorbs as a labelled sum-check round
  with its coefficient count, so a longer polynomial cannot absorb like a
  shorter one.
- Document that every absorbed Encoding must decode uniquely, since the
  sponge absorbs bytes without framing.
- Correct the prover-grinding comment: crates holding both sides enable
  yolocrypto in verifier builds; it changes visibility, not bytes.
- List precommitted as unbound in the statement test, and check that every
  unbound edit changes the inputs.
- Fix two test ports: count challenge draws rather than PoW squeezes, and
  squeeze the prover's tail once in the grouped-capacity loop.
- Re-bless the absorb-site inventory.
Import types and traits by short name on lines this branch adds, as
check_style_invariants requires, apply rustfmt, and commit the lockfiles,
verifier fixtures and fuzz seeds regenerated for the new transcript bytes.
Rename the order test's label to a dictionary word of the same length, and
drop jolt-sumcheck's num-traits dev-dependency, whose only user was the
deleted mersenne61 compatibility test.
The proof-of-work nonce search copies the sponge state, which spongefish
0.8.0 exposes only under `yolocrypto`. jolt-sumcheck and jolt-blindfold
turned `prover-grinding` on in every build, so jolt-verifier compiled the
search and `yolocrypto` too.

- jolt-sumcheck and jolt-blindfold gain a `prover` feature holding their
  prove side: the round loop, `grind_round`, the recorders' rounds, the
  committed builder and BlindFold's `prove`. jolt-prover, jolt-kernels and
  jolt-spartan-prover enable it; jolt-verifier's graph has neither the
  search nor `yolocrypto`.
- jolt-transcript's default features are Keccak only. Blake2b and Poseidon
  are opt-in; the fuzz workspaces that use them, or BN254 challenges, now
  name those features.
- CI lints the verifier-only graph, checks jolt-verifier's feature tree,
  and tests jolt-sumcheck and jolt-blindfold with their prover.
…ge-6b draw site

Stage 6b now draws through J6's one `sequence`, so its three further
`challenge` sites fold into one. Also rustfmt the stage-6b draw test.
Port J6's short Dory challenges onto the spongefish states.
`FieldTranscript::challenge_short_scalar` squeezes ceil(b / 8) bytes,
clears the bits above b = challenge_bits_for_loss(loss, SECURITY_BITS)
and decodes the little-endian integer, as J6's legacy method did. Both
states share one implementation. A width the field cannot hold returns
`VerificationError` before any squeeze and poisons the verifier, as an
unsupported challenge length does, so `GrindBitsError::FieldTooSmall`
goes. `ScriptedTranscript` passes the draw to its script and logs it as
`TranscriptEvent::ShortChallenge`, so FS audit tapes record and replay it.

The Dory adapter draws beta, alpha and d short with J6's loss table; a
refused draw fails `finish` like any other. challenge_bursts.rs records
real transparent and zero-knowledge Dory proofs through
`ScriptedTranscript`.
@quangvdao
quangvdao force-pushed the transcript/j7-sponge-port branch from d04df1b to d4e949c Compare October 21, 2026 20:58
…pt/j7-sponge-port

# Conflicts:
#	crates/jolt-transcript/src/challenge.rs
quangvdao added a commit that referenced this pull request Oct 23, 2026
J7 was rebuilt on main, which now carries #2107 and J6, so J7's old head
d04df1b is no longer its ancestor. This merge keeps J7's tree and
re-applies J8's own change on top: Akita's opening inside Jolt's sponge,
main's commitment wire behind #2107's shape guard, the standalone session
binding, and the FS replay freezing. The #2107 cherry-picks on this
branch are superseded by main's squash of #2107.

Dory keeps J7's label-based short challenges. The only lockfile conflict
was resolved by taking J7's lock and re-resolving the Akita pin.
…pt/j7-sponge-port

# Conflicts:
#	crates/jolt-transcript/tests/pow_tests.rs
quangvdao added a commit that referenced this pull request Oct 23, 2026
quangvdao added a commit to LayerZero-Labs/akita that referenced this pull request Oct 24, 2026
J7 contains A2's J6 pin and adds the FieldTranscript surface this branch
uses.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant