Repository navigation
Description
Check existing issues
- I checked open and closed issues/PRs for
hexToBytes,hexToBytes size, odd/nibble, and padding. isHex shouldn't accept odd number of nibbles #5048 / feat(utils): addevenoption toisHex#5283 concernisHexvalidation; fix: handle odd-length hex in signed hexToBigInt / hexToNumber #4913 concerns signed numeric decoding, not byte padding.
Viem Version
2.57.16; also reproduced against current main at 5628e6440f1d7605001fc655158102ebedcbbb6f (Node.js 24.20.0, Windows x64).
Current Behavior
Adding size changes bytes already represented by an accepted odd-length hex value:
import { hexToBytes, toBytes } from 'viem'
Array.from(hexToBytes('0x1')) // [1]
Array.from(hexToBytes('0x1', { size: 1 })) // [16]
Array.from(toBytes('0x1', { size: 2 })) // [16, 0]
Array.from(hexToBytes('0xabc')) // [10, 188]
Array.from(hexToBytes('0xabc', { size: 2 })) // [171, 192]These are observed offline outputs from the actual current source module, not a reimplementation. The toBytes public dispatch path is affected too. Even-length control hexToBytes('0x12', { size: 2 }) returns [18, 0].
Expected Behavior
Normalize accepted odd-length input as the unsized conversion already does, then append zero bytes for size: [1], [1, 0], and [10, 188] for the sized examples above. This does not propose rejecting odd-length hex or changing isHex.
Steps To Reproduce
Run the snippet above with Viem 2.57.16. For a dependency-free reproduction from the stated source commit, Node 24 can execute the original TypeScript using a resolver hook for the repository's .js source specifiers:
node --input-type=module <<'JS'
import { registerHooks } from 'node:module'
import { existsSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier.startsWith('.') && specifier.endsWith('.js') && context.parentURL) {
const url = new URL(specifier.slice(0, -3) + '.ts', context.parentURL)
if (existsSync(fileURLToPath(url))) return nextResolve(url.href, context)
}
return nextResolve(specifier, context)
},
})
const { hexToBytes, toBytes } = await import('./src/utils/encoding/toBytes.ts')
for (const hex of ['0x1', '0xabc', '0x12'])
console.log(hex, [...hexToBytes(hex)], [...hexToBytes(hex, { size: 2 })], [...toBytes(hex, { size: 2 })])
JSLink to Minimal Reproducible Example
Exact implementation at the reproduced commit; standalone reproducer is included above and requires no RPC.
Anything else?
The implementation currently right-pads before normalizing odd nibble counts. A small local patch normalizes the leading nibble first, with colocated regression coverage for both hexToBytes and toBytes, a documentation example, and a patch changeset. The full existing toBytes.test.ts file passes offline (17 tests) using an isolated configuration and the already available Vitest 3.2.4; the repository requests Vitest 4.1.12, so this is not a claim that the standard project harness or full CI passed. A separate real-source Node smoke passed 18 assertions including fromHex(..., { to: 'bytes' }), overflow and invalid-input controls. Full typecheck/lint were not run; an isolated dependency install failed due to disk exhaustion and was removed.
Per AGENTS.md's maintainer-gated commit rule, the patch is local and unstaged: no commit, push, or PR has been made. Would a maintainer approve staging/committing this focused fix and opening a PR, or prefer different odd-input behavior?
Disclosure: this investigation and local patch were AI-assisted, using the authorized human contributor account omerbek.
Activity