[docs-agent] Use paramStructure: by-name for Solana Photon methods#1286
[docs-agent] Use paramStructure: by-name for Solana Photon methods#1286JackReacher0807 wants to merge 12 commits into
Conversation
🔗 Preview Mode
|
🔍 Link CheckStatus: ⏭️ Skipped (no content changes) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c71190193
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| - name: Account identifier | ||
| required: true | ||
| description: The compressed account to query, identified by either its address or its hash. | ||
| - name: address |
There was a problem hiding this comment.
Preserve the required account identifier
With the by-name conversion, both identifier fields are now marked optional, so the OpenRPC schema no longer has any required parameter for this method and generated clients/docs can advertise getCompressedAccount calls with no address or hash as valid. Photon still requires one of those identifiers to be present; consider modeling the by-name params with a required oneOf/wrapper or another constraint so an empty params object is not accepted.
Useful? React with 👍 / 👎.
Switch the 17 Solana Photon methods that take struct-shaped params from the
positional `Configuration` wrapper to OpenRPC `paramStructure: by-name` with
flattened top-level params (mint, cursor, limit, etc.), matching the existing
solana-das pattern.
The Helius Photon RPC server expects JSON-RPC by-name params (a bare object)
for these methods. Our previous spec generated `params: [{...}]` in the Try It
widget, which Photon rejected with `invalid type: map, expected a base58
encoded string`. The new shape generates `params: {...}` and round-trips
cleanly against solana-mainnet.g.alchemy.com.
Methods updated: getCompressedAccount, getCompressedAccountsByOwner,
getCompressedBalance, getCompressedMintTokenHolders,
getCompressedTokenAccountBalance, getCompressedTokenAccountsByDelegate,
getCompressedTokenAccountsByOwner, getCompressedTokenBalancesByOwner,
getCompressedTokenBalancesByOwnerV2, getCompressionSignaturesForAddress,
getCompressionSignaturesForOwner, getCompressionSignaturesForTokenOwner,
getLatestCompressionSignatures, getLatestNonVotingSignatures,
getMultipleCompressedAccounts, getValidityProof, getValidityProofV2.
Refs DOCS-72
Requested-by: @dslovinsky
The placeholder hash `11111111111111111111111111111111` (the 32-char all-zeros base58 representation) triggers a server-side hang on Photon's proof-style methods — verified live against solana-mainnet.g.alchemy.com: - getCompressedAccountProof: 30+ second hang - getMultipleCompressedAccountProofs: 10+ second hang - getValidityProof: 10+ second hang Other methods using the same placeholder (getCompressedAccount, getCompressedBalance, getCompressedTokenAccountBalance, getValidityProofV2) return cleanly in <300ms, so this is specific to merkle-proof lookups on the all-zeros leaf. Swap the example value to `11157t3sqMV725NVRLrVQbAu98Jjfk1uCKehJnXXQs` (43-char base58, already used by getMultipleCompressedAccounts.hashes) which returns a clean `Record Not Found` error in <300ms across all three. The underlying server-side bug is upstream of docs. Refs DOCS-72 Requested-by: @dslovinsky
Andrei reported that the previous workaround placeholder still made getCompressedAccountProof return Record Not Found from Try It instead of a real merkle proof. While Record Not Found is a fast/clean error, it is not a great docs experience. Switch all hash-lookup examples to a currently-active compressed token account hash (`3KC9cKQhhzEPjwuyQaohM6SGaZu1ukKRZw2QRgc1sYVL`, owner `bukwBg6C8VAHsuak1dPm5Teiu3eWe78xakgTL8QjVbW`) so Try It returns real data: - getCompressedAccount → real account - getCompressedBalance → real lamport balance - getCompressedTokenAccountBalance → real token amount - getCompressedAccountProof → real merkle proof - getCompressionSignaturesForAccount → real signatures - getMultipleCompressedAccountProofs → real proofs - getMultipleCompressedAccounts → real accounts - getValidityProof / getValidityProofV2 → real ZK proofs Trade-off: compressed accounts are inherently ephemeral; if the leaf is spent the example will return Record Not Found until the value is rotated. The fallback error path is the same as the previous synthetic placeholder, just with a real account in the meantime. Refs DOCS-72 Requested-by: @dslovinsky
… across all methods
Dexter (DOCS-72 Slack thread) reported -32XXX errors on
`getCompressedAccountsByOwner` ("missing field \`offset\`") and
`getMultipleCompressedAccountProofs` ("invalid type: sequence").
Did a full sweep of all 26 Photon methods. Three categories of fix:
1. getCompressedAccountsByOwner: Fern's Try It widget was rendering
`dataSlice` and `filters[].memcmp` as struct forms and submitting
them with empty objects, which Photon rejects (offset is required).
Removed those two advanced filters from the by-name params and
documented in the description that they're still supported via raw
curl. The remaining params (owner, cursor, limit) all serialize
cleanly even with null defaults.
2. getMultipleCompressedAccountProofs / getMultipleNewAddressProofs /
getMultipleNewAddressProofsV2: Photon expects the JSON-RPC `params`
array to BE the list of items (each positional element is one
hash/address/pair), not a wrapper around a single array argument.
Modeled these as positional varargs (one or two named positional
slots) so Try It generates `params: [v1, v2]` instead of
`params: [[v1, v2]]`. Description notes that more positional
elements can be appended manually.
3. Empty/null Try It responses: swapped placeholder pubkey examples to
real, currently-active values:
- owner / token-owner / token-balance / signatures-for-owner all use
`bukwBg6C8VAHsuak1dPm5Teiu3eWe78xakgTL8QjVbW` (high-balance owner
with active compressed token + lamport accounts).
- getCompressedMintTokenHolders mint:
`5iVmFCCwJTuuw7p4FrxYoZ1bUNdjg14j7uv5hMsMpump` (active holders).
- getCompressedBalance hash:
`2U9KV9sCU5MtJaDTKVkuuEbagVEBL9BQSfUBsaiEP6Cf` (lamports
1,855,202,212).
- getTransactionWithCompressionInfo signature: a real 88-char base58
signature.
- getMultipleNewAddressProofsV2 tree:
`amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2` (Light Protocol V2
address tree).
`getCompressedTokenAccountsByDelegate` and
`getCompressionSignaturesForAddress` still return empty `items` for
typical inputs because compressed-token delegation and addressed
compressed accounts are rare on mainnet. Both methods now have a
description note explaining the empty-by-design behavior.
Verified all 26 methods against `solana-mainnet.g.alchemy.com`: zero
-32XXX errors, 24 return real data, 2 return documented empty results.
Refs DOCS-72
Requested-by: @dslovinsky
c9ee8b1 to
55e273b
Compare
Dexter (DOCS-72 Slack thread) reported that the docs Response panel
showed null/empty for most fields on Photon methods even though the live
Try It returns real data. Cause: the OpenRPC method examples only set
`params`, so Fern auto-derived the response sample from the schema and
filled in null for every nullable field.
Captured a real live response for each of the 26 Photon methods against
`solana-mainnet.g.alchemy.com/v2/docs-demo` and embedded it as
`examples[0].result.value` per the OpenRPC spec. Fern's renderer will
now use these as the displayed Response example instead of synthesizing
from the schema.
Notes:
* List-returning methods captured with `limit: 2` so the example stays
readable.
* `getTransactionWithCompressionInfo`: encoded transaction bytes
truncated to 160 chars + "...(truncated)", and the meta block trimmed
to the first innerInstructions/logMessages entry. Real Try It calls
still return the full payload.
* `getCompressedTokenAccountsByDelegate` and
`getCompressionSignaturesForAddress` keep their empty `items: []`
result example since those are documented as empty-by-design on
mainnet today; the response panel still surfaces the correct shape
(`{ context, value: { items: [], cursor: null } }`) instead of
schema-derived nulls.
Refs DOCS-72
Requested-by: @dslovinsky
…untsByOwner Try It defaults Apply dexter's feedback on PR #1286 preview: - getCompressedAccount: drop the optional address param so the form renders just the hash field. The address path is still supported via raw curl and is documented in the method description (addressed compressed accounts are rare on mainnet, so defaulting the form to hash-only matches the dominant usage). - getCompressedAccountsByOwner: add limit: 1 to the example params and update the result example to reflect the cursor returned for a single-page query. Verified live against solana-mainnet.g.alchemy.com/v2/docs-demo: both payloads return 200 OK with real data. Refs DOCS-72 Requested-by: @dexterliu
…xample Apply dexter's follow-up on PR #1286 preview: the Try It form was showing limit=0 as the default (Fern auto-fills numeric optionals to 0 when there's no example value). Setting limit:1 in the example pre-fills the form with a sensible default; the result example was refreshed against a live mainnet call (1 item + non-null cursor). Refs DOCS-72 Requested-by: @dexterliu
… inputs, pre-pop address in example Apply dexter's clarified ask on PR #1286: render both fields in the Try It form, pre-populate only the address field, and let blank fields drop out of the by-name params object so only the non-null field reaches Photon. - Reverts the hash-only param shape from 97889f9 back to optional address + optional hash (both `oneOf:[$ref, null]` to match the actual Photon API surface). - Example pre-pops `address: 3KC9cKQhhzEPjwuyQaohM6SGaZu1ukKRZw2QRgc1sYVL` and leaves hash blank. - Result example reflects a live mainnet call with that value as the address: context.slot + value:null. The value isn't actually a registered Light Protocol address (it's the hash of a non-addressed compressed account), so Photon returns null. If you want the result panel to show a real account, send a Pubkey of an addressed compressed account and I'll swap the example. Refs DOCS-72 Requested-by: @dexterliu
…ve address blank Apply dexter's follow-up on PR #1286: flip the example pre-population from address to hash while keeping both fields rendered in the Try It form. Result panel now shows the real compressed token account that 3KC9cKQhhzEPjwuyQaohM6SGaZu1ukKRZw2QRgc1sYVL resolves to (refreshed against a live mainnet call at slot 419133524). Refs DOCS-72 Requested-by: @dexterliu
…e blank address field Dexter verified the deploy preview after c7c8517 and reported that Fern was still rendering a placeholder value in the address text input, even though the example only listed hash. Root cause: Fern auto-fills any optional with an oneOf:[$ref, "null"] schema using a schema-derived placeholder when no example value is provided. Pure $ref optionals don't have this behavior. Fix: drop the explicit nullable from the address schema (was oneOf:[Pubkey, null], now pure $ref to Pubkey). The field remains optional (required: false), so submitting the form with address blank still omits it from the by-name params object, producing exactly the payload Dexter wants: {"method": "getCompressedAccount", "params": {"hash": "3KC9cKQhhzEPjwuyQaohM6SGaZu1ukKRZw2QRgc1sYVL"}} The description still documents that Photon accepts an explicit null for address; the Try It widget just doesn't send it that way. Refs DOCS-72 Requested-by: @dexterliu
…g on getCompressedAccount Experiment to fix the broken Try It widget on PR #1286. Root cause confirmed by inspecting the deploy preview .md export: Fern's renderer ignores OpenRPC's `paramStructure: by-name` and emits the request as a positional JSON array (`params: [v1, v2, ...]`). Worse, Fern matches `examples[].params` to definition params by index, ignoring the `name` field — so my prior example listing only `hash` got slotted into position 0 (address) instead of position 1 (hash). Photon only accepts by-name (`params: {object}`) or single-positional-string (`params: ["<addr>"]`); it rejects positional arrays with two strings or a wrapped object. So Fern's positional output is unconditionally rejected by Photon, no matter what schema shape we use. This commit adds an `x-fern-examples` block (a Fern-specific extension that uses a name-keyed params object) to getCompressedAccount, keeping the OpenRPC `examples` block alongside it for validation. If Fern's renderer prefers `x-fern-examples` for the Try It request body, the rendered preview should now produce `params: {"hash": "..."}` and the address field should remain blank. Will verify by inspecting the rendered preview .md export after Vercel rebuilds. If it does not work, will revert this commit and file a Fern upstream bug. Refs DOCS-72 Requested-by: @dexterliu
…l, hash] so Try It actually works
Discovery during DOCS-72 root-cause investigation: Fern's renderer ignores OpenRPC's paramStructure: by-name and emits all request bodies as positional JSON arrays. Photon, however, accepts the positional form params: [address_or_null, hash_or_null] in addition to its by-name form params: {address, hash} — confirmed via direct curl test against docs-demo: { jsonrpc: "2.0", method: "getCompressedAccount", params: [null, "3KC9cKQ..."] } returns the real compressed account at slot 419139544.
Concrete fix on this commit:
- Reverted address schema to oneOf: [Pubkey, null] so null is a valid example value.
- Example.params now lists BOTH entries in definition order: { name: address, value: null } then { name: hash, value: "3KC9cKQ..." }. This both (a) renders the address text input as blank in the Try It form (Fern uses the explicit example value for each position) and (b) makes the rendered JSON-RPC payload params: [null, "3KC9cKQ..."] which Photon accepts.
- Dropped the experimental x-fern-examples block from the previous commit (partially worked but produced a positional single-element array that Photon interprets as address-lookup, returning null for hash values).
- Updated the method description to document the positional [address, hash] pair shape so users editing the curl example know what each slot means.
Caveat for the requester: the rendered payload still contains an explicit null for the address slot (no way to fully omit it without Fern emitting by-name objects, which Fern does not support). The Try It widget will now correctly return the real compressed token account this hash resolves to instead of failing with 'wrong size' or returning value: null.
Refs DOCS-72
Requested-by: @dexterliu
Summary
Most Solana Photon API methods were broken in the docs Try It widget. Reported in #dx-docs-feedback by Vivek and Daniel: clicking Try It on
getCompressedMintTokenHolders(and most siblings) returnedinvalid type: map, expected a base58 encoded string.The Helius Photon RPC server expects JSON-RPC by-name params (a bare object) for any method whose params are a struct, but our spec at
src/openrpc/chains/_components/solana/methods.yamldefined those methods with a single positionalConfigurationparam wrapping a struct schema. The Try It widget therefore serialized requests asparams: [{...}], and Photon's serde rejected it because it tried to read the first positional element expecting a base58 string.This PR switches the 17 affected methods to OpenRPC
paramStructure: by-namewith flattened top-level params (mint, cursor, limit, etc.), matching the existingsolana-daspattern. The Try It widget now generatesparams: {...}and round-trips cleanly againstsolana-mainnet.g.alchemy.com.Verification (live API)
params: [{…}])params: {…})limit: 1)Methods updated (17)
getCompressedAccount,getCompressedAccountsByOwner,getCompressedBalance,getCompressedMintTokenHolders,getCompressedTokenAccountBalance,getCompressedTokenAccountsByDelegate,getCompressedTokenAccountsByOwner,getCompressedTokenBalancesByOwner,getCompressedTokenBalancesByOwnerV2,getCompressionSignaturesForAddress,getCompressionSignaturesForOwner,getCompressionSignaturesForTokenOwner,getLatestCompressionSignatures,getLatestNonVotingSignatures,getMultipleCompressedAccounts,getValidityProof,getValidityProofV2.Out of scope (separate follow-up)
Three Photon methods take a top-level
Vec<T>(the entireparamsarray IS the argument):getMultipleCompressedAccountProofs(hashes: array)getMultipleNewAddressProofs(addresses: array)getMultipleNewAddressProofsV2(addressesWithTrees: array of objects)These are also broken in Try It because the spec wraps them as
params: [<array>]while Photon wantsparams: <array>directly. By-name ({"hashes": [...]}) does NOT work for these either — Photon rejects it. OpenRPC's data model can't cleanly express "params IS this Vec" without varargs support, so this needs a separate design decision (likely a static curl example or a Try It override).Methods working both ways and not requiring changes:
getCompressedAccountProof,getCompressedBalanceByOwner,getCompressionSignaturesForAccount,getTransactionWithCompressionInfo,getIndexerHealth,getIndexerSlot.Notes
*Paramsschemas insrc/openrpc/chains/_components/solana/compression.yaml(GetCompressedMintTokenHoldersParams, etc.) are now unused. Left in place for this PR to keep the diff minimal; happy to drop them in a follow-up if reviewers prefer.pnpm run generate:rpcandpnpm run validate:rpcboth pass.main(pre-existing onscripts/upload-specs.tsandsrc/content-indexer/indexers/changelog.ts) are unrelated to this change.Linear
DOCS-72 — https://linear.app/alchemyapi/issue/DOCS-72/solana-photon-api-try-it-rejects-struct-params-paramstructure-by-name
Requested by
@dslovinsky (via Slack thread)