Skip to content

fix(rpc): felt types serialize as u64 limbs instead of hex strings#3504

Merged
rodrodros merged 1 commit intoNethermindEth:mainfrom
m-kus:fix/felt-pointer-serialization
Mar 24, 2026
Merged

fix(rpc): felt types serialize as u64 limbs instead of hex strings#3504
rodrodros merged 1 commit intoNethermindEth:mainfrom
m-kus:fix/felt-pointer-serialization

Conversation

@m-kus
Copy link
Copy Markdown
Contributor

@m-kus m-kus commented Mar 23, 2026

Summary

  • Fix JSON serialization of felt types that are stored as struct value fields — they were
    being serialized as [u64, u64, u64, u64] Montgomery limbs instead of "0x..." hex strings
  • Change MarshalJSON from pointer to value receiver on all felt types (Felt, Hash,
    TransactionHash, Address, ClassHash, SierraClassHash, CasmClassHash, StateRootHash)
  • Add regression test covering all felt types through any interface (reproducing the jsonrpc
    server's serialization path)

Rationale

Go's encoding/json cannot call pointer-receiver methods on non-addressable values. The jsonrpc
server stores handler return values in response.Result any (server.go:519):

res.Result = tuple[0].Interface() // stores struct as value in `any`

Values extracted from any via reflection are not addressable. Since all felt MarshalJSON
methods used pointer receivers (*Felt, *Hash, etc.), encoding/json could not call them
and fell back to encoding the underlying [4]uint64 as a JSON array.

This violates the Starknet RPC spec
which defines FELT as "type": "string", "pattern": "^0x(0|[a-fA-F1-9]{1}[a-fA-F0-9]{0,62})$".

Observed behavior

{
  "transaction_hash": [10235509696732094000, 15042186987767562000, 2604752151520920600, 522667376275625800]
}

Expected behavior

{
  "transaction_hash": "0x8de..."
}

Timeline

Date Commit What happened
2025-09-22 ccf85f6 feat(core): add new types for core felt based types (#3125) Introduced TransactionHash, Address, Hash, etc. as named types — without MarshalJSON
2025-11-06 e9c5c5e fix: json marshalling (#3244) Added MarshalJSON to the new types, but with pointer receivers — the root cause
2026-02-17 5d74f82 feat(rpcv10): response_flags; proof_facts, proof (#3380) v10 AddTxResponse.TransactionHash changed from *felt.Feltfelt.TransactionHash (value) — bug becomes observable

The underlying issue (Felt.MarshalJSON using a pointer receiver) existed since the core/felt
package was created, but was latent because all response structs used *felt.Felt (pointer fields).
It only surfaced when v10 introduced value-type felt fields in handler return structs.

Changelog

Fixed

  • starknet_addInvokeTransaction returning transaction_hash as an array of u64 limbs instead
    of a hex string, breaking starknet.js and other clients that expect the spec-compliant format
  • All felt-derived types (Felt, Hash, Address, TransactionHash, ClassHash,
    SierraClassHash, CasmClassHash, StateRootHash) now serialize correctly as hex strings
    even when used as value fields in structs passed through any interfaces

Test plan

  • New TestMarshalJSON_ValueInInterface reproduces the bug across all felt types
  • go test ./core/felt/... — all pass
  • go test ./rpc/... — all pass (v6 test updated to expect correct hex output)
  • go test ./jsonrpc/... — all pass
  • go test ./adapters/... — all pass

@m-kus m-kus force-pushed the fix/felt-pointer-serialization branch 2 times, most recently from 39bff64 to 90031aa Compare March 24, 2026 12:30
@rodrodros rodrodros force-pushed the fix/felt-pointer-serialization branch from 90031aa to 5d3d697 Compare March 24, 2026 13:06
@rodrodros
Copy link
Copy Markdown
Contributor

I forced push just to rebase from main (sorry if you'd already done it)

@rodrodros rodrodros merged commit b69b825 into NethermindEth:main Mar 24, 2026
13 of 14 checks passed
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.

2 participants