Skip to content

IDL does not express non-standard PDA seed encodings (e.g. i32 as decimal string) #4718

Description

@LongShao007

Anchor’s IDL describes PDA seeds derived from instruction arguments by name and type, but not by how the value is encoded into bytes. For programs that use non-standard encodings (notably i32.to_string().as_bytes()), client generators infer the wrong seed format and derive incorrect PDAs.

This affects real production programs such as Orca Whirlpools.

On-chain behavior (Orca example)
In Orca Whirlpools, the tick array PDA is derived with a decimal string seed, not raw i32 bytes:

// https://github.com/orca-so/whirlpools/blob/main/programs/whirlpool/src/instructions/initialize_tick_array.rs#L16
#[account(
  init,
  payer = funder,
  seeds = [
    b"tick_array",
    whirlpool.key().as_ref(),
    start_tick_index.to_string().as_bytes(),  // UTF-8 decimal string, e.g. "-443520"
  ],
  bump,
  space = FixedTickArray::LEN,
)]
pub tick_array: AccountLoader<'info, FixedTickArray>,

For start_tick_index = -443520, the third seed is the bytes of "‑443520" (variable length), not a 4-byte little- or big-endian integer.

Correct client derivation (as used in Orca’s ecosystem and tests):

PublicKey.findProgramAddressSync(
  [
    Buffer.from("tick_array"),
    whirlpool.toBuffer(),
    Buffer.from(startTickIndex.toString()),
  ],
  programId,
);

What the Anchor IDL expresses today
When Anchor exports the IDL for this instruction, the PDA seed is typically represented only as:

  • an instruction argument reference (e.g. start_tick_index)
  • with type i32

The IDL does not indicate that the seed must be encoded as a UTF-8 decimal string rather than binary i32.

Downstream impact
Client code generators (e.g. Codama) read the IDL and map i32 → binary integer encoding (getI32Encoder() / 4 fixed bytes). That produces PDAs that do not match the on-chain program.

This is not limited to one generator — any tool that trusts “argument type = seed encoding” will fail for this pattern.

Orca-style programs are widespread enough that SDKs commonly ship hand-written PDA helpers instead of relying on generated ones.

Why this matters
Many CLMM programs follow the Orca pattern (to_string().as_bytes() for tick array start indices). Other DEXes use different encodings for the same logical type (e.g. Raydium CLMM uses i32 big-endian bytes). The same Rust type (i32) can map to incompatible seed bytes, but the IDL gives clients no way to distinguish them.

Suggested direction
Consider extending the IDL so PDA seeds can declare encoding explicitly, for example:

{
  "kind": "arg",
  "path": "start_tick_index",
  "type": "i32",
  "encoding": "utf8_decimal_string"
}

Or another machine-readable form that covers:

  • raw fixed-size integers (le / be)
  • UTF-8 decimal strings (Orca-style)
  • other common patterns

Without this, Anchor IDL is incomplete for a valid and common Anchor pattern (seeds = [..., arg.to_string().as_bytes()]), and client generators cannot produce correct PDA helpers from the IDL alone.

Environment

  • Pattern observed in: Orca Whirlpools initialize_tick_array (source)
  • Impact: incorrect PDA derivation in Codama-generated clients unless overridden manually

Metadata

Metadata

Assignees

No one assigned

    Labels

    blockedidlrelated to the IDL, either program or client side

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions