Skip to content

Type safety issue: Generic chain constraints bypass null safety for transaction.to #3783

@jeanregisser

Description

@jeanregisser

Check existing issues

Viem Version

2.31.7

Current Behavior

When using PublicClient<Transport, TChain extends Chain>, TypeScript fails to properly enforce that transaction.to can be null, even though viem correctly declares it as Address | null.

The following code compiles without TypeScript errors when using generic chain constraints:

async function problematicGeneric<TChain extends Chain>(
  client: PublicClient<Transport, TChain>
) {
  const transaction = await client.getTransaction({ hash: '0x123...' })
  
  // These should be TypeScript errors but aren't:
  const unsafeAssignment: Address = transaction.to  // Address | null → Address
  const result = processAddress(transaction.to)     // Passing null to non-null function
}

This bypasses TypeScript's strict null checks and can lead to runtime errors when transaction.to is actually null (contract creation transactions).

Expected Behavior

TypeScript should show compile-time errors for:

  1. const unsafeAssignment: Address = transaction.to - Error: Type 'Address | null' is not assignable to type 'Address'
  2. processAddress(transaction.to) - Error: Argument of type 'Address | null' is not assignable to parameter of type 'Address'

The same code with concrete types (PublicClient<Transport, Chain>) correctly shows these TypeScript errors, so the null safety should work consistently regardless of whether generics are used.

Steps To Reproduce

  1. Create a TypeScript project with strict null checks enabled
  2. Install viem ^2.31.7
  3. Create a generic function using PublicClient<Transport, TChain extends Chain>
  4. Try to assign transaction.to directly to an Address type
  5. Run tsc --noEmit --strict
  6. Observe that no TypeScript errors are shown (but there should be)

Compare with the same code using PublicClient<Transport, Chain> (concrete types) which correctly shows TypeScript errors.

Link to Minimal Reproducible Example

See this TypeScript Playground

Anything else?

This may be a fundamental limitation in TypeScript's type inference system when dealing with complex generic constraints, rather than an issue that can be resolved at the viem library level.

Workarounds that work:

  • Use concrete types: PublicClient<Transport, Chain>
  • Remove generic entirely: PublicClient
  • Explicit type assertion: const to: Address | null = transaction.to

Impact: This affects any generic function using viem clients with chain constraints and can lead to runtime errors that are preventable with proper type checking.

Environment:

  • TypeScript: 5.x
  • Strict mode: enabled
  • strictNullChecks: enabled

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions