Skip to content

[2.x] Port the dynamic client to Codama v2 - #1209

Draft
lorisleiva wants to merge 1 commit into
10-05-reject_duplicate_set_items_in_dynamic_codecsfrom
10-05-port_the_dynamic_client_to_codama_v2
Draft

lorisleiva wants to merge 1 commit into
10-05-reject_duplicate_set_items_in_dynamic_codecsfrom
10-05-port_the_dynamic_client_to_codama_v2

Conversation

@lorisleiva

@lorisleiva lorisleiva commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

This PR ports @codama/dynamic-client to Codama v2 IDLs.

  • Latest major only. Older IDLs throw VERSION_MISMATCH. Upgrade them first with createProgramClient(upgrade(v1Idl)), which keeps the client decoupled from @codama/upgrade.
  • .resolvers() removed. Inputs carrying a codama.resolver plugin have no default, so they must be provided (or set to null for optional accounts).
  • Data instead of arguments. Methods take the instruction data, remaining accounts are passed as Address[] in .accounts(), and PDAs are derived from their node paths.
  • PDAs from another program. client.pdas.x(seeds, { programId }) derives a PDA from the given program, like an instruction's pdaValueNode.programId. It's backed by a new optional programId on resolveStandalonePda in @codama/dynamic-address-resolution.
  • Codegen emits ${Name}InstructionDataArgs and ${Pda}Seeds, and no longer emits ${Name}Resolvers.
  • Dependencies: superstruct is dropped, and @codama/dynamic-codecs and @solana/codecs move to devDependencies, since src hasn't used them since the instruction builder was extracted.
  • Test IDLs are committed as upgraded v2 JSON.

@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 88b6bfc

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@lorisleiva

Copy link
Copy Markdown
Member Author

@trevor-cortex

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Ports @codama/dynamic-client to v2 IDLs on top of the already-ported @codama/dynamic-instructions / @codama/dynamic-address-resolution. The runtime surface shrinks accordingly: MethodsBuilder now takes a NodePath<InstructionNode> and forwards { accounts, data, signers } to createInstructionsBuilder(path); .resolvers() is gone; collectPdaNodes becomes collectPdaPaths so resolveStandalonePda({ path, seedsInput }) gets a real path; codegen emits ${Name}InstructionDataArgs / ${Pda}Seeds and drops ${Name}Resolvers. Deps are trimmed (superstruct out, @codama/dynamic-codecs + @solana/codecs to dev) — I checked src/ and nothing there imports either, so that's correct. Test IDLs are committed as upgrade() output and the tests were rewritten to assert on CodamaError codes/context instead of message regexes, which is a nice robustness win.

The source changes are small and read correctly against the downstream packages' current signatures (getResolutionRefs(ix, definedTypes), resolveStandalonePda({ path, seedsInput }), InstructionInput). Approving — the points below are doc/edge-case notes rather than blockers.

Things to watch

  • Inline PDAs with a pdaValueNode.programId (inline comment on collect-pdas.ts): client.pdas.<name>() for a PDA collected from an account default will derive against the enclosing program, since resolveStandalonePda has no context to resolve pdaValueNode.programId. v1 pdaValueNode didn't carry programId, so this is a v2-specific gap. Probably worth either skipping those or documenting.
  • Two PDA collectors: collectPdaPaths here and collectPdaNodesFromIdl in @codama/dynamic-address-resolution/codegen implement the same scan independently. The generated ${Program}Pdas type comes from the latter, the runtime proxy from the former — if they ever diverge (e.g. one starts skipping programId inline PDAs), types and runtime will disagree. Not new to this PR, just flagging since both were touched by the port.
  • README "Resolved inputs" example (inline comment): I believe masterEdition is an optional account without a default on create, so the snippet as written would throw ACCOUNT_MISSING under the new "optional-without-default requires explicit null" rule.
  • Changeset: I don't see a .changeset/*.md in the file list. This removes .resolvers(), renames every generated type, and changes dependencies — all user-facing for @codama/dynamic-client. If the seeded 2.x major changeset is meant to cover the port for every package that's fine, but if per-package changelog entries are expected it'd need one (npx changeset add --empty).

Notes for subsequent reviewers

  • The semantics change for optional accounts that used to be auto-resolved by a resolver (e.g. splTokenProgram on MPL create): omitting them now throws ACCOUNT_MISSING; null applies the optionalAccountStrategy. That's enforced by resolveInstructionAccountAddress downstream and the README documents it — just be aware when reading the rewritten create-with-resolvers.test.ts.
  • generateClientTypes resolves definedTypeLinkNode data only against root.program.definedTypes; links into additionalPrograms fall back to unknown. Fine for now, but if anyone hits that case it's where to look.
  • Error-assertion tests were mostly fetched in full (nested-example-ix.test.ts tail is truncated in the diff view) — the expectations line up with the new codec error shapes.

Comment thread packages/dynamic-client/src/program-client/collect-pdas.ts
Comment thread packages/dynamic-client/README.md Outdated
@lorisleiva
lorisleiva force-pushed the 10-05-port_the_dynamic_client_to_codama_v2 branch from 105952b to 88b6bfc Compare October 5, 2026 17:21
@lorisleiva
lorisleiva force-pushed the 10-05-reject_duplicate_set_items_in_dynamic_codecs branch from a1a4ae3 to 7c1d78c Compare October 5, 2026 17:21

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

Re-fetched everything that changed since my last pass. The update adds an explicit programId escape hatch for standalone PDA derivation, which addresses the pdaValueNode.programId gap I raised, and fixes the README snippet (masterEdition: null). Still approving.

What changed

  • resolveStandalonePda gains an optional programId?: AddressInput with precedence given programId → pdaNode.programId → defining program. The implementation is a one-liner on top of the existing getPdaProgramAddress(path) / derivePda split, and toAddress means it accepts Address, base58 string, or legacy PublicKey-like. Tests cover all three fallback levels plus the string form.
  • client.pdas.x(seeds, { programId }) threads it through; PdaOptions is exported from the package root. The pdas programId option unit test is a good one: it builds the inline PDA via the instruction (programId: accountValueNode('otherProgram')) and via client.pdas.vault(..., { programId: otherProgram }) and asserts both agree. That's exactly the equivalence the feature promises.
  • Codegen: ${Program}Pdas functions now take options?: { programId?: Address }. Address is already in the generated header imports (getTypesFileHeader), so no missing-import risk. The generated type is narrower than the runtime (Address vs AddressInput), which matches how seeds are typed — fine, just noting it's deliberate.
  • New IDL versions tests pin the VERSION_MISMATCH behaviour for 1.5.0 and 3.0.0, and the createProgramClient error tests now assert on error codes/context rather than messages.

Verified

  • Both PDA collectors (collectPdaPaths here, collectPdaNodesFromIdl in codegen) still produce the same key set — the type/runtime sync concern from my previous review isn't affected by this change since neither started filtering on programId.
  • pdaOptions: PdaOptions = {} default means client.pdas.x(seeds) and client.pdas.x(seeds, undefined) behave identically.
  • resolveStandalonePda test setup (programIdValueNode constant seed) confirms the override also flows into programIdValueNode seeds via derivePda's programAddress parameter, so a PDA seeded with its own program id derives correctly under the override.

Remaining notes (non-blocking, unchanged from last time)

  • No .changeset/*.md in the diff. @codama/dynamic-address-resolution now has a public API addition on top of the dynamic-client breaking changes, so if per-package changelog entries are expected on the 2.x branch this PR would want one for both. If the branch-level major changeset covers it, ignore.
  • generateClientTypes still resolves definedTypeLinkNode only against root.program.definedTypes — same as before, just flagging for anyone hitting unknown in generated data types.

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