Skip to content

Add fetchAllMixedAccounts helper for heterogeneous multi-account fetching#248

Open
blockiosaurus wants to merge 3 commits intov1.0from
claude/multi-account-fetch-helper-L4obG
Open

Add fetchAllMixedAccounts helper for heterogeneous multi-account fetching#248
blockiosaurus wants to merge 3 commits intov1.0from
claude/multi-account-fetch-helper-L4obG

Conversation

@blockiosaurus
Copy link
Contributor

Generate a new fetchHelpers.ts in the JS accounts output that provides
fetchAllMixedAccounts and safeFetchAllMixedAccounts. These helpers
take an array of { publicKey, deserialize } inputs of different account
types, batch them into a single rpc.getAccounts() call, and return a
fully-typed tuple where each position matches the corresponding
deserializer's output type. No new Umi primitives are needed — the
existing rpc.getAccounts() already supports multi-account fetching.

https://claude.ai/code/session_01Dddxti9yQJBCaoBqKPjye1

@changeset-bot
Copy link

changeset-bot bot commented Feb 21, 2026

⚠️ No Changeset found

Latest commit: c14de26

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

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces fixed-size option type support across IDL, node system, and renderers; adds BigInt value handling; implements padding attributes for instruction arguments and struct fields; enables nested account structures; performs a major Rust renderer overhaul with Anchor trait support; and updates TypeScript compilation target to es2020.

Changes

Cohort / File(s) Summary
IDL Type Extensions
.changeset/orange-pans-happen.md, .changeset/rich-bats-invite.md, src/idl/IdlInstruction.ts, src/idl/IdlType.ts
Added changeset entries for patch releases; extended IdlInstruction to support nested account structures via IdlInstructionNestedAccounts; added IdlTypeFixedSizeOption to IdlType union with sentinel support; added optional attrs field to instruction args and struct fields for metadata like padding.
Node System: Type & Value Nodes
src/nodes/typeNodes/FixedSizeOptionTypeNode.ts, src/nodes/typeNodes/TypeNode.ts, src/nodes/typeNodes/StructFieldTypeNode.ts, src/nodes/typeNodes/...index.ts, src/nodes/valueNodes/BigIntValueNode.ts, src/nodes/valueNodes/ValueNode.ts, src/nodes/valueNodes/...index.ts
Introduced FixedSizeOptionTypeNode type and factory functions with IDL conversion; added BigIntValueNode for large integer support; registered both in STANDALONE node registries; added padding detection and default value generation via createPaddingDefaultValue helper.
Node System: Instruction & Account Nodes
src/nodes/InstructionAccountNode.ts, src/nodes/InstructionNode.ts, src/nodes/InstructionArgumentNode.ts, src/nodes/InstructionByteDeltaNode.ts
Added instructionAccountNodesFromIdl to recursively flatten nested account structures; refactored InstructionNode to use new bulk account converter; implemented padding attribute detection with defaultValue and strategy assignment; extended InstructionByteDeltaNode.value to support BigIntValueNode.
Visitor Extensions
src/visitors/getByteSizeVisitor.ts, src/visitors/getDebugStringVisitor.ts, src/visitors/identityVisitor.ts, src/visitors/mergeVisitor.ts, src/visitors/setInstructionAccountDefaultValuesVisitor.ts
Added handlers for fixedSizeOptionTypeNode across all visitors; added bigIntValueNode support to identityVisitor and debug string formatting; refactored regex patterns in account default values for precision; updated merge logic for fixed-size option nodes.
Shared Utilities
src/shared/GpaField.ts
Added NestedGpaField type and getNestedGpaFieldsFromAccount function to extract and describe nested struct fields from GPA field definitions.
JS Renderer: Core
src/renderers/js/getRenderMapVisitor.ts, src/renderers/js/getTypeManifestVisitor.ts, src/renderers/js/renderValueNodeVisitor.ts
Added visitFixedSizeOptionType handler with Option-wrapped types and sentinel-based encoding; integrated nested GPA field resolution and propagation; added discriminator computation and propagation for accounts and instructions; added BigInt value rendering support.
JS Renderer: Templates
src/renderers/js/templates/accountsFetchHelpers.njk, src/renderers/js/templates/accountsIndex.njk, src/renderers/js/templates/accountsPage.njk, src/renderers/js/templates/instructionsPage.njk
Introduced accountsFetchHelpers template with fetchAllMixedAccounts and safeFetchAllMixedAccounts for type-safe batch fetching; added nested GPA field registration in GPA builder; added instruction discriminator constant exports; extended bytesCreatedOnChain calculation for bigIntValueNode.
Experimental JS Renderer
src/renderers/js-experimental/fragments/instructionByteDelta.ts, src/renderers/js-experimental/getTypeManifestVisitor.ts, src/renderers/js-experimental/renderValueNodeVisitor.ts
Added bigIntValueNode fragment handler; implemented visitFixedSizeOptionType with custom sentinel-based serialization; added BigInt visitor method for value rendering.
Rust Renderer: Core (High Complexity)
src/renderers/rust/getRenderMapVisitor.ts, src/renderers/rust/getTypeManifestVisitor.ts, src/renderers/rust/renderValueNodeVisitor.ts
Major refactor introducing FixedSizeOptionMetadata and field maps; added visitFixedSizeOptionType with sentinel validation and Option type wrapping; implemented conditional Anchor/Borsh trait derivations; added Pubkey and Vec\<Pubkey\> serde attribute handling; introduced renderStructDefinition and sentinel constant generation; extended merge logic for fixed-size option metadata.
Rust Renderer: Templates
src/renderers/rust/templates/accountsPage.njk, src/renderers/rust/templates/definedTypesPage.njk, src/renderers/rust/templates/errorsPage.njk, src/renderers/rust/templates/instructionsCpiPage.njk, src/renderers/rust/templates/instructionsPage.njk
Added conditional feature-gated imports for anchor vs. borsh serialization; updated error handling with From/TryFrom/ToStr trait impls; made InstructionData and InstructionArgs public with conditional derives; switched from try_to_vec() to borsh::to_vec pattern; fixed is_signer/is_writable field mapping in CPI page.
Test Fixtures
test/option_tests.json, test/padding_tests.json, test/pubkey_tests.json, test/testFile.cjs
Added comprehensive test fixtures for fixed-size option types (with sentinels), padding attributes on instruction args/struct fields, and pubkey vector types; registered new fixtures in test harness.
Test Suites
test/nodes/paddingFromIdl.test.ts, test/renderers/js/instructionsPage.test.ts, test/renderers/rust/instructionsPage.test.ts, test/renderers/js/_setup.ts, test/renderers/rust/accountsPage.test.ts, test/visitors/setStructDefaultValuesVisitor.test.ts
Added comprehensive padding validation tests; new discriminator export tests for JS renderer; new public struct and instruction rendering tests for Rust; introduced renderMapContains and codeContains test helpers; updated struct default value tests for BigInt support.
Package & Config Updates
package.json, Cargo.toml, tsconfig.json, test/packages/js/tsconfig.json, test/packages/rust/Cargo.toml, .prettierignore, CHANGELOG.md, CLAUDE.md
Bumped version to 1.0.0-alpha.3; added test:rust and format scripts; added Rust workspace config; added Cargo anchor feature flag; updated TypeScript target es2020; expanded prettier ignore patterns; added release notes and comprehensive Claude documentation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • danenbm
  • brandontulsi
  • nhanphan
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main addition: a fetchAllMixedAccounts helper for heterogeneous multi-account fetching, which aligns with the PR's primary purpose.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the new fetchHelpers.ts with fetchAllMixedAccounts and safeFetchAllMixedAccounts helpers and their typed tuple return values.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/multi-account-fetch-helper-L4obG

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

Summary by CodeRabbit

  • New Features
    • Added batch account fetching utilities for efficiently fetching multiple accounts of different types in a single RPC call
    • Introduced two fetch options: strict validation (fails if account missing) and safe mode (returns null for missing accounts)
    • Added strong type support for mixed account types

Walkthrough

The pull request introduces batch-fetch utilities for multiple accounts of different types in a single RPC call. A new template emits helper functions and types, the render map is updated to emit this template when accounts exist, and the main accounts index re-exports the new utilities.

Changes

Cohort / File(s) Summary
Render Infrastructure
src/renderers/js/getRenderMapVisitor.ts
Modified to emit accounts/fetchHelpers.ts by rendering accountsFetchHelpers.njk when accountsToExport is non-empty, in addition to existing accounts/index.ts emission.
Batch Fetch Utilities
src/renderers/js/templates/accountsFetchHelpers.njk
New template introducing FetchAccountInput<T> type, fetchAllMixedAccounts function for batch-fetching with existence assertions, and safeFetchAllMixedAccounts function for nullable handling. Includes internal type utilities for deserialized account tuples.
Index Re-export
src/renderers/js/templates/accountsIndex.njk
Added re-export of batch-fetch utilities via export * from './fetchHelpers';

Sequence Diagram(s)

sequenceDiagram
    participant Consumer
    participant Context as Context/RPC
    participant Deserializer as Deserialization Logic

    rect rgba(200, 150, 100, 0.5)
    Note over Consumer,Deserializer: fetchAllMixedAccounts Flow
    Consumer->>Consumer: Extract publicKeys from inputs
    Consumer->>Context: context.rpc.getAccounts(publicKeys, options)
    Context-->>Consumer: RPC accounts[]
    Consumer->>Consumer: assertAccountExists for each account
    Consumer->>Deserializer: Deserialize each account using input.deserialize
    Deserializer-->>Consumer: Deserialized typed tuple
    Consumer-->>Consumer: Return DeserializedAccounts<T>
    end

    rect rgba(100, 150, 200, 0.5)
    Note over Consumer,Deserializer: safeFetchAllMixedAccounts Flow
    Consumer->>Consumer: Extract publicKeys from inputs
    Consumer->>Context: context.rpc.getAccounts(publicKeys, options)
    Context-->>Consumer: RPC accounts[] (may include nulls)
    Consumer->>Deserializer: Conditionally deserialize if account exists
    Deserializer-->>Consumer: Deserialized value or null
    Consumer-->>Consumer: Return MaybeDeserializedAccounts<T>
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a new helper function for heterogeneous multi-account fetching, which is the primary focus of the changeset.
Description check ✅ Passed The description clearly explains the new fetchHelpers.ts generation and the two helper functions, their purpose, and how they work, directly related to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/multi-account-fetch-helper-L4obG

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderers/js/templates/accountsFetchHelpers.njk`:
- Around line 36-54: Update the JSDoc for fetchAllMixedAccounts to explicitly
state the 100-account RPC limit and the function's behavior when that limit is
exceeded: note that the implementation does not enforce or perform client-side
chunking, that the underlying RPC (getMultipleAccounts / getAccounts) may reject
requests over 100 accounts, and recommend either chunking client-side or calling
a helper that does so (e.g., safeFetchAllMixedAccounts) if callers need to fetch
more than 100 accounts in one logical operation.
- Around line 100-104: The explicit cast "(maybeAccount as RpcAccount)" is
unnecessary because MaybeRpcAccount is a discriminated union and checking
maybeAccount.exists should narrow the type; remove the cast in the return
mapping (the block using maybeAccounts, maybeAccount.exists and
inputs[index].deserialize) so you call inputs[index].deserialize(maybeAccount)
directly, and if TypeScript still complains ensure the source type of
maybeAccounts is declared/returned as MaybeRpcAccount so narrowing works (or add
a short type guard function isRpcAccount(m: MaybeRpcAccount): m is RpcAccount
that checks m.exists and use it before calling inputs[index].deserialize).

Comment on lines +36 to +54
/**
* Fetches multiple accounts of potentially different types in a single RPC
* call and deserializes each one using its provided deserializer.
*
* This is useful in frontends and other scenarios where you want to minimize
* the number of RPC round-trips by batching up to 100 accounts into a single
* `getMultipleAccounts` call while still getting fully typed results.
*
* All accounts must exist, otherwise an error is thrown. Use
* {@link safeFetchAllMixedAccounts} if some accounts may not exist.
*
* @example
* ```ts
* const [metadata, edition] = await fetchAllMixedAccounts(context, [
* { publicKey: metadataAddr, deserialize: deserializeMetadata },
* { publicKey: editionAddr, deserialize: deserializeEdition },
* ]);
* // metadata: Metadata, edition: Edition — fully typed!
* ```
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding a note about the account limit.

The JSDoc mentions "batching up to 100 accounts" but the implementation doesn't enforce this limit. This is likely intentional (leaving enforcement to the RPC layer), but it may be worth clarifying whether exceeding 100 accounts will cause the RPC call to fail or if getAccounts handles chunking internally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderers/js/templates/accountsFetchHelpers.njk` around lines 36 - 54,
Update the JSDoc for fetchAllMixedAccounts to explicitly state the 100-account
RPC limit and the function's behavior when that limit is exceeded: note that the
implementation does not enforce or perform client-side chunking, that the
underlying RPC (getMultipleAccounts / getAccounts) may reject requests over 100
accounts, and recommend either chunking client-side or calling a helper that
does so (e.g., safeFetchAllMixedAccounts) if callers need to fetch more than 100
accounts in one logical operation.

Comment on lines +100 to +104
return maybeAccounts.map((maybeAccount, index) => {
return maybeAccount.exists
? inputs[index].deserialize(maybeAccount as RpcAccount)
: null;
}) as MaybeDeserializedAccounts<T>;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the file
find . -name "accountsFetchHelpers.njk" -type f

Repository: metaplex-foundation/kinobi

Length of output: 123


🏁 Script executed:

# Also search for imports and usage of MaybeRpcAccount in the codebase
rg "MaybeRpcAccount|MaybeDeserializedAccounts" -A 2 -B 2

Repository: metaplex-foundation/kinobi

Length of output: 2665


🌐 Web query:

Umi MaybeRpcAccount type definition discriminated union RpcAccount

💡 Result:

In Umi, MaybeRpcAccount is a discriminated union over the exists flag, where the “exists” branch is an RpcAccount:

type RpcAccount = AccountHeader & {
  publicKey: PublicKey;
  data: Uint8Array;
};

type MaybeRpcAccount =
  | ({ exists: true } & RpcAccount)
  | { exists: false; publicKey: PublicKey };

[1]


🏁 Script executed:

# View the actual template file around the lines in question
sed -n '90,110p' src/renderers/js/templates/accountsFetchHelpers.njk

Repository: metaplex-foundation/kinobi

Length of output: 617


🏁 Script executed:

# Check if there are any type imports or declarations in the template
head -50 src/renderers/js/templates/accountsFetchHelpers.njk

Repository: metaplex-foundation/kinobi

Length of output: 1608


🏁 Script executed:

# Search for RpcAccount definition in the codebase
rg "type RpcAccount|interface RpcAccount" -A 5

Repository: metaplex-foundation/kinobi

Length of output: 52


Type narrowing with maybeAccount.exists should eliminate the need for explicit RpcAccount cast.

The MaybeRpcAccount type from Umi is correctly structured as a discriminated union: { exists: true } & RpcAccount | { exists: false; publicKey: PublicKey }. TypeScript should automatically narrow maybeAccount to include all RpcAccount properties after the exists check, making the explicit as RpcAccount cast redundant. Consider removing the cast and relying on the discriminated union narrowing instead, or document why the cast is necessary if there are type inference limitations from the RPC call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderers/js/templates/accountsFetchHelpers.njk` around lines 100 - 104,
The explicit cast "(maybeAccount as RpcAccount)" is unnecessary because
MaybeRpcAccount is a discriminated union and checking maybeAccount.exists should
narrow the type; remove the cast in the return mapping (the block using
maybeAccounts, maybeAccount.exists and inputs[index].deserialize) so you call
inputs[index].deserialize(maybeAccount) directly, and if TypeScript still
complains ensure the source type of maybeAccounts is declared/returned as
MaybeRpcAccount so narrowing works (or add a short type guard function
isRpcAccount(m: MaybeRpcAccount): m is RpcAccount that checks m.exists and use
it before calling inputs[index].deserialize).

…hing

Generate a new `fetchHelpers.ts` in the JS accounts output that provides
`fetchAllMixedAccounts` and `safeFetchAllMixedAccounts`. These helpers
take an array of { publicKey, deserialize } inputs of different account
types, batch them into a single `rpc.getAccounts()` call, and return a
fully-typed tuple where each position matches the corresponding
deserializer's output type.

https://claude.ai/code/session_01Dddxti9yQJBCaoBqKPjye1
@blockiosaurus blockiosaurus force-pushed the claude/multi-account-fetch-helper-L4obG branch from 68fc59f to 1dc3100 Compare February 21, 2026 01:35
@blockiosaurus blockiosaurus changed the base branch from main to v1.0 February 21, 2026 01:38
…ount helpers

Instead of requiring callers to pass explicit deserializer functions,
generate per-program helpers that automatically identify account types
from their discriminator bytes. For each program with discriminated
accounts, generates:

- Union type (e.g. MplTokenMetadataAccount)
- deserialize{Program}Account() with discriminator matching
- fetchAll{Program}Accounts() for batch fetching
- safeFetchAll{Program}Accounts() for nullable batch fetching

Handles both Anchor-style byte discriminators (raw byte comparison)
and Shank-style field discriminators (runtime serialization + comparison),
mirroring the pattern already used by the js-experimental renderer.

https://claude.ai/code/session_01Dddxti9yQJBCaoBqKPjye1
* @see https://github.com/metaplex-foundation/kinobi
*/

import {

Choose a reason for hiding this comment

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

I think we still want this file because it allows you to fetch mixed types which we do (also i didn't even know this helper existed, i implemented something similar on the app side

});
}

export async function safeFetchAllMplCandyMachineCoreAccounts(

Choose a reason for hiding this comment

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

nice

return staticVisitor((node) => stringify(node)) as Visitor<string>;
}
return mapVisitor(removeDocsVisitor(), (node) => stringify(node));
return mapVisitor(removeDocsVisitor(), (node) => stringify(node)) as Visitor<string>;

Choose a reason for hiding this comment

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

we shouldn't ever need to cast if the types are correct. (we should make the types correct)

- Restore generic fetchHelpers.ts alongside per-program helpers per
  reviewer feedback that mixed-type fetching is valuable
- Clarify JSDoc about 100-account RPC limit and lack of client-side chunking
- Remove unnecessary `as RpcAccount` casts in templates, using
  discriminated union narrowing instead
- Fix getUniqueHashStringVisitor types properly with nullish coalescing
  instead of `as Visitor<string>` casts

https://claude.ai/code/session_01Dddxti9yQJBCaoBqKPjye1
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