Skip to content

Resolve nested seed paths in v01 PDA extraction#990

Open
ioxde wants to merge 7 commits intocodama-idl:mainfrom
ioxde:nested-pda-seeds
Open

Resolve nested seed paths in v01 PDA extraction#990
ioxde wants to merge 7 commits intocodama-idl:mainfrom
ioxde:nested-pda-seeds

Conversation

@ioxde
Copy link
Copy Markdown

@ioxde ioxde commented Apr 9, 2026

This is a direct follow-up to #984 which left the nested path handling as a TODO.

PDA seeds with nested paths like mint.authority or args.owner were skipped. This PR resolves them by looking up the field type from IDL type definitions.

{ kind: 'account', path: 'mint.authority' } + a Mint type def with authority: pubkey -> variablePdaSeedNode('mintAuthority', publicKeyTypeNode())

{ kind: 'arg', path: 'args.owner' } where args is a struct with owner: pubkey -> variablePdaSeedNode('owner', publicKeyTypeNode())

Program seeds with nested paths still skip (with a warning). pdaSeedNodeFromAnchorV01 can now return undefined.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

🦋 Changeset detected

Latest commit: 48b8986

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@codama/nodes-from-anchor Minor

Not sure what this means? Click here to learn what changesets are.

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

@ioxde ioxde force-pushed the nested-pda-seeds branch 2 times, most recently from 4902b1e to 60bfce9 Compare April 12, 2026 23:31
@lorisleiva
Copy link
Copy Markdown
Member

@trevor-cortex

Copy link
Copy Markdown

@trevor-cortex trevor-cortex left a comment

Choose a reason for hiding this comment

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

Summary

This PR resolves nested seed paths (e.g. mint.authority, args.owner) in v01 PDA extraction, which were previously left as a TODO and silently skipped. It introduces a resolveNestedFieldType helper that walks struct fields (inline or via IDL type definitions) to determine the leaf type. The function pdaSeedNodeFromAnchorV01 can now return undefined when resolution fails, and callers handle this gracefully by skipping the PDA default value.

Additionally, the PR adds self-referential seed detection — if a seed references the account being derived, the PDA default is skipped with a warning.

Key things to watch

  1. Breaking return type change: pdaSeedNodeFromAnchorV01 now returns | undefined. This is a well-handled breaking change internally — all call sites check for undefined — but any external consumers of this export would need updating.

  2. Self-referential detection: The new self-referential check (lines 131–138 in InstructionAccountNode.ts) correctly catches cases like vault referencing itself, and nested paths like guard.mint where the account name matches the seed's account reference. The test for my_guard with path: 'my_guard.mint' is a good edge case.

  3. Nested program paths: Program seeds with nested paths are still explicitly skipped with a warning — this is a reasonable incremental choice since program seeds with nested paths are rare.

Notes for subsequent reviewers

  • The resolveNestedFieldType helper only handles structTypeNode and definedTypeLinkNode. Other wrappers (e.g. optionTypeNode) would fall through and return undefined. This seems fine for now — real-world IDL seeds shouldn't reference optional fields — but worth keeping in mind.
  • The InstructionNode.test.ts change for the distribution account is interesting: it confirms the self-referential detection works at the integration level (the account distribution has a seed path: 'distribution.group_mint' referencing itself, so the PDA default is correctly omitted).
  • Test coverage is thorough: nested account paths, nested arg paths, unresolvable types, self-referential accounts, prefixed nested groups, and program seed edge cases are all covered.

Comment thread packages/nodes-from-anchor/src/v01/PdaSeedNode.ts
Comment thread packages/nodes-from-anchor/src/v01/InstructionAccountNode.ts Outdated
Comment on lines -296 to -304
// [
// accountNode({
// data: sizePrefixTypeNode(
// structTypeNode([structFieldTypeNode({ name: 'authority', type: publicKeyTypeNode() })]),
// numberTypeNode('u32'),
// ),
// name: 'mint',
// }),
// ],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The idea was that these would be uncommented when nested arguments/accounts are supported. Is the idea that the new tests are taking care of that deleted code now?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The commented code assumed account data would be passed directly via accountNode, but nested types are now resolved through idlTypes instead.

Comment on lines -411 to -419
// [
// accountNode({
// data: sizePrefixTypeNode(
// structTypeNode([structFieldTypeNode({ name: 'authority', type: publicKeyTypeNode() })]),
// numberTypeNode('u32'),
// ),
// name: 'mint',
// }),
// ],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above

Comment thread packages/nodes-from-anchor/src/v01/ProgramNode.ts Outdated
Comment thread packages/nodes-from-anchor/src/v01/InstructionAccountNode.ts
Comment on lines +51 to +54
if (!resolved) {
logWarn(`Could not resolve nested account path "${seed.path}" for PDA seed.`);
return undefined;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wonder if here we should just throw? Before we were silently ignoring nested paths because that was a valid scenario we were not able to support. Now that we are, if the nested seed don't resolve properly than it is a malformation on the IDL itself right?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Honestly not sure. I know Anchor PDAs aren't perfect. I think the best path would be to throw by default (with a detailed message pointing to docs/what to fix) but allowing a user to override per error. I am not sure that whole change fits in this PR though. Seems more like a project-level standard / convention?

I personally do like the fail-first override behavior because it forces the user to understand their decisions. Console warnings don't always tell the real severity of the issue / easy to accidentally ignore.

I'll wait for more direction to make changes here.

Comment thread packages/nodes-from-anchor/src/v01/PdaSeedNode.ts
}

function resolveNestedFieldType(
rootType: TypeNode,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wouldn't call that rootType since there is a RootNode which makes this confusing.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

parentType okay?

Comment thread packages/nodes-from-anchor/src/v01/PdaSeedNode.ts Outdated
Comment on lines -43 to -53
// TODO: Handle seeds with nested paths. (Needs a path in the IDL but should we?)
// defaultValue: pdaValueNode(
// pdaNode({
// name: 'distribution',
// seeds: [
// constantPdaSeedNodeFromBytes('base58', 'F9bS'),
// variablePdaSeedNode('distributionGroupMint', publicKeyTypeNode()),
// ],
// }),
// [pdaSeedValueNode("distributionGroupMint", accountValueNode('distribution', 'group_mint'))],
// ),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same feedback about using idlTypes as above.

Comment on lines +20 to +24
expect(nodes.definition).toEqual(constantPdaSeedNodeFromBytes('base58', 'HeLLo'));
expect(nodes.value).toBeUndefined();
expect(nodes!.definition).toEqual(constantPdaSeedNodeFromBytes('base58', 'HeLLo'));
expect(nodes!.value).toBeUndefined();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we throw instead of returning undefined, we remove the need for these casts.

Copy link
Copy Markdown
Author

@ioxde ioxde Apr 15, 2026

Choose a reason for hiding this comment

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

I switched all tests to ?.. Unsure about the throw. See above comment about throw vs log.

@ioxde ioxde force-pushed the nested-pda-seeds branch from 60bfce9 to 2702c2c Compare April 14, 2026 23:58
* found while rebasing onto main
@ioxde ioxde force-pushed the nested-pda-seeds branch from 2702c2c to a37264c Compare April 15, 2026 00:04
@ioxde
Copy link
Copy Markdown
Author

ioxde commented Apr 15, 2026

I think I addressed all comments outside of the logWarn vs throw question. Let me know if you have any other questions / concerns.

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.

3 participants