| Audited | helius.dev documentation, changed and added pages, pages fetched 11 October 2026 |
|---|---|
| Date | 11 October 2026 |
| How it ran | cloud session, full audit, Standard review |
| Verdict after review | Pass with notes (rule: Fail if a High finding remains after review, otherwise Pass with notes) |
Related: Helius API documentation (May 2026) · Helius Gatekeeper documentation (June 2026)
Each finding keeps the number it has in the audit report. The rating shown first is the one after review; the first automated rating is listed with it. 67 findings were first rated Medium or High; 9 of them are Medium or High after review. Text marked "From the report" is quoted from the audit report; fixes are suggestions and were not tested. Locations are paths inside the fetched copy of the documentation.
Medium after review (9)
laserstream/clients.md:115From the report
helius-laserstream = "0.2"
Line 111 says to use 0.6.3 or later, because older releases mishandle transaction v1. Cargo's "0.2" resolves to versions >=0.2.0, <0.3.0, so it can never reach 0.6.3.
Change it to "0.6" (at least 0.6.3), or use cargo add. Pin the Go snippet at line 171 the same way.
Review: Line 111: "Use helius-laserstream 0.6.3 or later. Earlier releases ship a proto that predates transaction v1". Line 115: helius-laserstream = "0.2", which Cargo reads as ^0.2, so a copy-paste resolves to 0.2.x. The defect is silent: v1 transactions arrive without their transactionConfig fields. Contradiction sits four lines apart on one page. (cuckoo-filters.md:48 also says "0.2", but there 0.2.0 is the feature minimum. The Go snippet at 171 has no version at all, so "pin it the same way" is a minor misstatement.)
rpc/websocket.md:341 (with lines 378 and 392-394)From the report
if (this.isReconnecting) return;…this.isReconnecting = true;…setTimeout(() => { this.connect(); }, jitteredDelay);
scheduleReconnect() sets the flag and then calls connect(), which returns at once because the flag is set. The flag is only cleared in onopen, which is never reached. After the first disconnect the client stays dead and its subscriptions are lost.
Clear the flag just before calling connect() in the timer, or remove the guard from connect().
Review: connect() begins if (this.isReconnecting) return;. scheduleReconnect() sets isReconnecting = true (378) and its timer then calls this.connect() (393), which returns immediately. The flag is cleared only in onopen (356), which is never reached. After the first drop (and onclose itself routes through scheduleReconnect, 360-364) the client stays dead with no error. The section exists to teach reconnection, so this is a silent core-path failure. Unchanged since May (not in the diff).
sending-transactions/sender.md:128 (kit tab: line 229)From the report
lamports: 1 * LAMPORTS_PER_SOL,/amount: lamports(1_000_000_000n), // 1 SOL
The diff shows the amount was changed from 0.001 SOL. A developer who runs the "simple transfer" sample on mainnet with a real key moves 1 SOL, and the transfer cannot be undone.
Restore 0.001 SOL in both tabs.
Review: Diff lines 107-108 and 140-141 show 0.001 * LAMPORTS_PER_SOL and lamports(1_000_000n), // 0.001 SOL replaced by 1 * LAMPORTS_PER_SOL and lamports(1_000_000_000n), // 1 SOL. The sibling pages (sender-max.md:178, sender-swqos-only.md:144) still use 0.001. Looks like a find/replace slip when the tip changed; the 0.001 SOL tip sits right beside it (133, 238). Real money, irreversible, new in this delta.
sending-transactions/sender.md:514From the report
Tab "Frontend/Browser Applications":https://sender.helius-rpc.com/fast?api-key=YOUR_API_KEY
Anyone visiting the site can read the key and spend the project's credits and quota. agents/skills/phantom.md:42 says "Never expose Helius API keys in client code".
Recommend a server-side relay or a restricted, separate browser key, and align the Phantom page.
Review: Tab "Frontend/Browser Applications" shows https://sender.helius-rpc.com/fast?api-key=YOUR_API_KEY; the browser samples at diff lines 123 and 159 do the same. agents/skills/phantom.md:42 says "Never expose Helius API keys in client code - only https://sender.helius-rpc.com/fast is browser-safe without an API key", and api-reference/authentication.md:12 says the same. The Sender page never mentions domain restriction (waas/securing-your-key.md:36 describes it). The project key also works for credit-metered RPC, and a keyless option exists, so MEDIUM stands.
dedicated-nodes/getting-started.mdA live-looking API key and token are printed on this page.
webhooks/transaction-types.md:993From the report
const event = req.body;/if (event.type === 'NFT_SALE') {
Webhooks deliver an array, as data-streaming/quickstart.md:166 shows. event.type is always undefined, so no sale is ever processed.
Iterate over req.body.
Review: const event = req.body; if (event.type === 'NFT_SALE'). webhooks.md:49 shows the delivered payload as a JSON array, and data-streaming/quickstart.md:166 iterates req.body.forEach. The branch never runs and nothing is logged: silent. The fields read (event.seller, event.buyer, event.amount) are also not top-level in the documented payload.
parsed-streams/guides/handling-reconnects.md:56From the report
const ws = new WebSocket(URL);(handlers registered:open,messageandcloseonly)
With ws, an error event with no listener is thrown. DNS failures, refused connections and 401/429 handshake rejections kill the process before the backoff runs.
Add an error handler and let close own the retry.
Review: Handlers registered: open, message, close. Contrast track-pumpfun-mints.md:89, which does register error. One-line fix, but the guide's whole purpose is this path.
Date.now() colliderpc/websocket/quickstart.md:350From the report
const requestId = Date.now();
Back-to-back subscribe() calls (lines 410 and 497) in the same millisecond overwrite each other in the map. Subscriptions are silently dropped, including on resubscribe.
Use an incrementing counter.
Review: requestId = Date.now() is the Map key. The page's own use cases call subscribe three times back to back (412, 419, 425) and twice for the portfolio tracker (503, 510); calls within one millisecond overwrite each other in the Map, so subscriptions are silently dropped (and resubscribeAll, 366-375, re-sends only the survivors). Contrast rpc/websocket.md:424, which adds Math.random(). Pre-existing.
sending-transactions/send-manually.md:213From the report
if (status && (status.confirmationStatus === 'confirmed' || status.confirmationStatus === 'finalized')) {
status.err is never checked, so a transaction that landed but failed is treated as success.
Check status.err first.
Review: The sample sends with skipPreflight: true (204), which lets failing transactions land, then logs "Transaction confirmed!" on confirmationStatus alone; status.err is never read. Line 230 concedes the loop is basic but does not mention errors. A landed-but-failed transaction is reported as success.
Low after review (81)
sending-transactions/sender.md:496From the report
* **Keyless**: 1 request per second per egress IP, per region. ... Rejected 429 requests count toward the limit
The previous docs said standard users need no key and get 50 TPS. The new limit has no announcement, cut-over date or migration path. Keyless samples are still published (for example the cURL samples on the sender-max and sender-swqos-only pages), faqs/sender.md:18 still says "standard limit is 50 TPS", and api-reference/sender/llms.txt:70 says "Default 50 TPS". Existing keyless integrations will be throttled without warning.
Publish a dated change notice and a migration path. Update every keyless sample and every "50 TPS default" statement.
Review: Limit is stated plainly on the page; "no announcement" is vendor policy, not a doc defect. Real residue: faqs/sender.md:18 and api-reference/sender/llms.txt:70,77 still say 50 TPS default.
privacy/guides/deposit.md:443 (also line 427)From the report
tokenProgram: TOKEN_PROGRAM_ADDRESS,
Line 365 says DepositAsset.spl covers "SPL and Token 2022 assets". With a Token-2022 mint, this sample builds the interface and the deposit against the wrong program, so the transaction fails.
Read the mint account's owner and require it to be either the classic token program or Token-2022. Pass that program ID in both places, and add a Token-2022 variant.
Review: Real (line 365 says SPL and Token 2022) but the sample is the SPL path; a Token-2022 user fails loudly and edits one constant.
waas/using-the-wallet.md:50From the report
const signature = await signMessage("Sign in to Acme");
A signature over a fixed string can be replayed. Readers copy this as their login proof.
Show a challenge flow: the server issues a nonce with a domain and an expiry, the wallet signs it, and the server verifies the signature and consumes the nonce.
Review: exists
waas/using-the-wallet.md:94From the report
const signature = await signAndSendTransaction(serialized);
Wrap the call in try/catch to handle a cancelled prompt. Confirm with getSignatureStatuses before treating the action as done.
Review: exists
api-reference/endpoints.md:55From the report
The staked endpoint (staked.helius-rpc.com) is deprecated. ... * **Higher landing rates**: Guaranteed access to staked connections
Give a sunset date, or state that there is none, and present the benefits as legacy behaviour.
Review: exists (benefit bullets 58-61)
parsed-events/guides/migrate-from-enhanced-transactions.md:183From the report
Enhanced Transactions keeps working while you migrate — there is no forced cutoff.
The llms-full FAQ says the API "will be deprecated", and other pages call it maintenance mode. Readers cannot plan a migration from that.
Publish one message: the current phase, the minimum support window and the target date.
Review: exists
src/diffs/HeliusAPI_full/_llms.txt.diff:153From the report
-| mintCompressedNft | Use Metaplex Bubblegum SDK directly | Helius mint API is deprecated |
Both pages now only redirect, and nothing tells existing callers what to use instead.
Keep a short "removed — use X" notice for at least one release.
Review: removed section starts at 153, mint row at 156
api-reference.md:32From the report
<Card title="Enhanced Transactions" ... Retrieve pre-parsed transaction data in human-readable format.
Mark it as legacy and link to Parsed Events and the migration guide.
Review: exists
parsed-streams/guides/handling-reconnects.md:76From the report
if (lastSlotSeen !== null && slot > lastSlotSeen) {
This condition is also true in normal operation. On a quiet filter, the idle case the guide covers, no notification may arrive after reconnect, so the missed window is never backfilled.
On open after a reconnect, read the current slot and backfill explicitly from lastSlotSeen to that slot.
Review: Block body is a one-line stub comment. Gap is found late (on next notification), not "never backfilled".
rpc/websocket/stream-pump-amm-data.md:86From the report
function startPing(ws: WebSocket): void {/setInterval(() => {
Each reconnect adds another 30 s interval that keeps old sockets alive. Timers pile up for as long as the process runs.
Keep the interval ID, clear it in the close handler, and start only one timer per connection.
Review: Leak is real but tiny (max 5 retries per outage); the interval gates on the old socket's readyState === OPEN, so it does not keep old sockets alive as claimed.
disconnect() method that does not existrpc/websocket/quickstart.md:714From the report
if (manager) manager.disconnect();
WebSocketManager (lines 306-377) defines no disconnect(). Unmount throws, and every mount leaks a metered connection. The "production-ready" class also leaves reconnection as a comment (line 345).
Add disconnect() that sets a closing flag and calls ws.close(), and implement the reconnect.
Review: Confirmed: class (306-377) has no disconnect; unmount throws TypeError (loud). Reconnect left as a comment at 345. Borderline LOW/MEDIUM.
rpc/optimization-techniques.md:416From the report
while (true) {
A key that is exhausted or blocked keeps hammering the API forever. billing/rate-limits.md prescribes at most 5 attempts with delays up to 30 s.
Limit the number of attempts, honour Retry-After, and rethrow after the last attempt.
parsed-streams/guides/handling-reconnects.md:60From the report
filters.forEach((filter, i) => {/ws.send(JSON.stringify({
Pace the resubscriptions and check each response for rate-limit errors.
Review: exists
rpc/websocket.md:499From the report
setInterval(() => {
Store the interval ID and clear it on shutdown.
Review: exists
rpc/websocket/transaction-subscribe.md:201From the report
setInterval(() => ws.ping(), 30_000);
Add a close handler that clears the interval and then reconnects or exits.
Review: exists
laserstream/historical-replay.md:157From the report
let lastProcessedSlot = Number(process.env.LAST_PROCESSED_SLOT ?? 0);
The slot is written to one place and read from another. After a restart the value is stale, and a missing value is silently turned into a 48-hour replay.
Read and write the resume slot through the same store.
Review: exists; code comment says load from your own store
preprocessed-transactions/guides/trade-on-preprocessed.md:99From the report
if (!item) { await new Promise(r => setTimeout(r, 1)); continue; }
Have the producer signal when an item arrives instead of polling.
Review: exists
quickstart/portfolio-tracker.md:429From the report
setLive((prev) => [JSON.parse(e.data), ...prev].slice(0, 10));
On a live event and on stream reconnect, refetch the balances and history, with a debounce.
parseIntwallet-api/transfers.md:200, wallet-api/overview.md:132From the report
Serialized as a string to avoid floating-point precision loss. ... amount = parseInt(amountRaw) / 10**decimals
parseInt brings back the precision loss that the string format exists to avoid. Raw amounts above 2^53 are silently rounded. wallet-api/balance-at.md already advises BigInt.
Use BigInt(amountRaw) and decimal-string formatting.
Review: Formula yields a float display amount (and amount is already supplied); precision matters only above 2^53 raw. balance-at.md:207 shows BigInt, so this is doc consistency.
agents/rust-sdk/best-practices.md:203From the report
Minimum 0.0002 SOL (Dual mode) or 0.000005 SOL (SWQOS-only).
The current Sender pages set a 0.001 SOL minimum for Sender Max, and a smaller tip misses the priority buffer. Line 69 still mentions "Dual".
Update the figure and the mode name.
Review: Stale figure confirmed (0.0002 vs 0.001 Max), but the same line says the SDK determines and appends the tip itself. Unchanged text, informational.
parseIntlaserstream/guides/decoding-transaction-data.md:621From the report
const preAmount = preBalance ? parseInt(preBalance.uiTokenAmount.amount) : 0;
Use BigInt arithmetic (also at lines 734-735).
Review: exists (also 734-735)
api-reference/rpc/http/gettransfersbyaddress.md:467From the report
gt:/type: number
Accept string-encoded raw amounts so values above 2^53 work in both directions.
Review: exists
wallet-api/balances.md:238From the report
derive it asMath.round(balance * 10 ** decimals).
Expose a raw string field, or state that the derived value is approximate.
Review: exists
Number before summing itlaserstream/guides/stream-pump-amm-data.md:169From the report
Wrap it inNumber(...)before doing arithmetic.
Accumulate with BigInt and convert only for display.
Review: exists
laserstream/historical-replay.md:162From the report
const maxReplaySlot = currentSlot - 691_200;
Treat the window as a time value set by the server, or derive the earliest slot from the server's response.
Review: exists
quickstart/portfolio-tracker.md:395From the report
const load = async () => {
Ignore clicks while a load is in flight, or disable the button.
Review: exists
AGENTS.md:11 (delivered as AGENTS.md.nxdata)From the report
npx helius-cli signup --json
agents/cli.md says signup now needs --email, --first-name and --last-name, and by default it returns a payment link. The key only comes from --resume or --pay. An autonomous agent following this file gets no key.
Show the three-step flow from _docs_AGENTS.md.
Review: Stale (agents/cli.md:34,63 need --email/--first-name/--last-name; default is a payment link) but unchanged since May and the failure is a loud CLI error naming the missing flags.
agents/llms.txt:114From the report
helius signup --email you@example.com --first-name Jane --last-name Doe --json, followed by"apiKey": "your-api-key-here"
State that the apiKey payload comes only from --resume or --pay.
Review: Same root cause as 30; example output mismatch (cli.md:62-66).
AGENTS.md:55 (also line 76, and _www_llms.txt:78)From the report
/plugin marketplace add helius-labs/core-ai && /plugin install helius@helius-labs
agents/claude-code-plugin.md:40 says to run the two commands separately and that pasting both at once fails.
Show two separate commands.
Review: Contradiction real (agents/claude-code-plugin.md:40); failure is loud, retry trivial.
AGENTS.md:40From the report
Auth: every surface requires anapi-keyquery parameter
The same file lists plain-HTTP regional Sender hosts (line 30) and an x-token header for LaserStream (line 36). An agent following this line will attach the key to unencrypted URLs.
Describe authentication per surface, never put the key on http:// URLs, and prefer the header where it is supported.
Review: Overgeneralisation; the same file lists the x-token header (36). Plain-HTTP regional hosts are vendor design (see 51).
api-reference/laserstream/grpc/llms.txt:38 (also lines 13, 46, 241, 301)From the report
- 24-hour historical replay (216,000 slots)/Choose your plan (Devnet: Developer/Business, Mainnet: Professional)
The source pages say about 48 h (about 691,200 slots), and that Business also gets mainnet. Line 53 of the same file lists Business with mainnet.
Regenerate the file from the current pages.
Review: Real: 24 h / 216,000 vs 48 h / 691,200 (historical-replay.md:19); line 13 says mainnet = Professional only, line 53 lists Business too. Understates, conservative.
shred-delivery/llms.txt:76 (and 195)From the report
| Historical replay | No | No | 24 hours |
Change the replay window to 48 hours, and link /docs/preprocessed-transactions/overview.
Review: Real; DELTA_MANIFEST.md:58 confirms shred-delivery/preprocessed-transactions.md redirects away.
dedicated-nodes/llms.txt:17From the report
- 24-hour historical replay
Change it to 48-hour, matching dedicated-nodes/getting-started.md:82.
Review: Real; getting-started.md:82 says 48 h.
agents/llms.txt:49From the report
heliusTransaction(parsing, history, priority fees)
agents/mcp/tools.md:112 lists getPriorityFeeEstimate under heliusChain, so agents will call the wrong tool.
Update the summary.
Review: Real (heliusTransaction vs tools.md:112 heliusChain); one summary phrase, the catalog page is right.
api-reference/das/llms.txt:596 (also the webhooks, enhanced-transactions, priority-fee, rpc/http, wallet-api and sender indexes)From the report
import Helius from 'helius-sdk';/const helius = new Helius('YOUR_API_KEY');
The current TypeScript SDK pages document createHelius({ apiKey }) with methods directly on the client. The current Rust page requires Helius::new(key, Cluster)?.
Regenerate the snippets against the current SDKs.
Review: Real in 7 files (new Helius(key) vs createHelius, agents/typescript-sdk.md:26) but fails loudly at construction.
api-reference/sender/llms.txt:296From the report
// Build transaction with tip and priority fee, then send via Sender/const signature = await helius.rpc.sendTransaction(serializedTransaction, {
The tip is paid but Sender routing is not used.
Use the SDK's Sender method (sendTransactionWithSender).
Review: Real: comment says "via Sender", call is helius.rpc.sendTransaction; correct is helius.tx.sendTransactionWithSender (agents/typescript-sdk/api-reference.md:60).
billing/llms.txt:29 vs :187; :91From the report
| **Enhanced WebSockets** | No | Yes | Yes | Yes |vs| WebSocket Types | Standard | Standard | Standard + Enhanced |
Developer-plan access to Enhanced WebSockets and LaserStream mainnet differs within the file. Line 91 keeps a "Developer+ only" restriction on getTransactionsForAddress that the prose pages dropped.
Reconcile the file with the plans, credits and rate-limit pages.
Review: Real internal contradiction (Developer: Enhanced WS Yes vs Standard); rpc/endpoints.md:48 supports line 29. Index inconsistency only.
src/diffs/HeliusAPI_full/_llms-full.txt.diff:908From the report
batches of up to 100 full transactions or 1,000 signatures.
rpc/gettransactionsforaddress.md:15 says up to 1,000 full transactions per call.
Correct the figure, and re-check the other figures in the bundle against their source pages.
Review: Real ("up to 100 full transactions" vs rpc/gettransactionsforaddress.md:15 "1,000"); stale low figure in an FAQ.
waas/migrating-from-privy.md:9From the report
the mental model (embedded, non-custodial, social/passkey login) is the same.
waas/configuration.md:56-63 lists Google, Apple, Discord and X as "Coming soon". Apps that rely on social login cannot migrate their users yet.
State the gap in the guide and in its mapping table.
Review: configuration.md:56-63 does say Google/Apple/Discord/X are "Coming soon"; the guide's phrase is one clause in a mental-model sentence.
_www_llms.txt:84From the report
The autonomoushelius signup --jsonflow requires a 1 USDC payment ... plus ~0.001 SOL
Say that this applies only to --pay.
Review: exists
AGENTS.md:28 (and 33)From the report
[/docs/rpc/overview](https://www.helius.dev/docs/rpc/overview)
Link the current RPC and ZK Compression pages.
Review: exists; DELTA_MANIFEST.md:56,62 lists rpc/overview and zk-compression/introduction as REMOVED (307)
agents/mcp.md:7 vs agents/overview.md:11From the report
9 routed tools plus expandResultvs10 routed tools
Use one wording everywhere.
Review: exists
waas/troubleshooting.md:39From the report
### The wrong sign-in methods appear (e.g. Google shows, external wallet is missing)
Use a sign-in method that exists today as the example.
Review: exists
waas/migrating-from-privy.md:76From the report
Once users are on their Helius wallets, remove the Privy SDK.
Make removal a main step. Then verify in a browser network log that no requests go to Privy hosts, and list the leftovers to clean up (env vars, CSP entries, lockfile).
Review: exists
agents/skills/okx.md:55 (also line 90)From the report
curl -fsSL https://raw.githubusercontent.com/okx/onchainos-skills/main/install.sh | bash
Pin to a tag or commit and publish a checksum.
Review: exists
sending-transactions/sender.md:520-526; sending-transactions/guides/land-trades-with-sender.md:32,128From the report
http://slc-sender.helius-rpc.com/fast?api-key=YOUR_API_KEY/fetch(${SENDER}/fast?api-key=${HELIUS_API_KEY}, {
The key travels unencrypted in the query string and can be sniffed or logged on the network path.
Offer HTTPS regional endpoints or header authentication, and warn against sending keys over http://.
Review: True and unwarned, but regional endpoints are HTTP by vendor design (sender-max.md:108-114) and an HTTPS global host exists. Passive sniffing of a server-to-edge hop is low-likelihood.
pre-confirmations/guides/trade-on-preconfirmations.md:102From the report
await fetch('http://ewr-sender.helius-rpc.com/fast', {
Add the key on the server side and note the rate-limit dependency. Fix the same URL in jupiter-swap-api-via-sender.md:79.
Review: The Jupiter page (line 79) defines a key constant.
pre-confirmations/guides/trade-on-preconfirmations.md:78From the report
const tx = VersionedTransaction.deserialize(buf.subarray(18));
preconf-subscribe.md:269-272 says older VersionedTransaction.deserialize handles only legacy and v0. The first v1 frame throws in the message handler and kills the trading process.
Use a decoder that supports v1, wrap decode and handler in try/catch, and fix the "bincode" wording.
Review: preconf-subscribe.md:272 already warns to check the library version for v1; close exits for the supervisor by design (84).
preprocessed-transactions/guides/trade-on-preprocessed.md:100From the report
const tx = VersionedTransaction.deserialize(item.txBytes);
Same as finding 53, and catch errors per item inside the while (true) loop.
Review: Same as 53; preprocessed-subscribe.md:128 carries the caveat.
wallet-api/transfers.md:201 vs api-reference/wallet-api/transfers.md:158From the report
(So11111111111111111111111111111111111111111for native SOL)vs(So11111111111111111111111111111111111111112 for
Code that compares mint with the documented constant misses SOL if it followed the wrong page. wallet-api/balances.md:195/202 shows both values in one response.
State which value the API returns, and update every page to it.
Review: Inconsistency real (...111 pseudo-mint in guides and in the request mint param, ...112 in reference response descriptions). The request param is consistent everywhere (api-reference/wallet-api/balance-at.md:18). Which constant the API returns cannot be decided from the audited copy.
wallet-api/overview.md:116From the report
|GET /v1/wallet/{wallet}/identity|403— paid plans only |
billing/plans.md:148 lists the Wallet API as included on Free, and billing/rate-limits.md:257 gives Free a limit that covers these endpoints.
Reconcile the pages and mark the exception in the plans table.
waas/troubleshooting.md:29From the report
*NEXT_PUBLIC_HELIUS_API_KEYis set and valid.
The recommended setup in waas/quickstart.md:27-57 keeps the key on the server. This check pushes users to ship the key in the browser bundle.
Make the check depend on the setup mode.
Review: waas/quickstart.md:71-83 documents the prototyping key shortcut and domain restriction; the check serves that mode.
waas/migrating-from-privy.md:44From the report
ReplacePrivyProviderwithHeliusWalletProviderand pass your Helius API key
Follow the quickstart: leave the key out of the provider and use the server route handler.
Review: Wording contradicts quickstart.md:31 (omit key) but links to that Setup section.
quickstart/portfolio-tracker.md:316 (route starts at line 306)From the report
const ws = new WebSocket(wss://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY});— onlyopenandmessagehandlers are registered
In the ws library, an error event with no listener is thrown as an exception. A failed upstream connection, or closing the socket while it is still connecting, takes down the Next.js process. When the upstream closes, the feed dies silently. The route has no authentication or limits, so any caller can open metered upstream sockets.
Add error and close handlers that close the stream once. Validate address, and add per-IP limits.
Review: No error listener on ws is real; tutorial demo app, unauthenticated relay is demo-grade.
quickstart/deploy-program.md:82From the report
solana config set --url https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY
zsh, the default macOS shell, treats the unquoted ? as a glob and stops with "no matches found".
Quote the URL.
Review: Real (unquoted ?), loud, one-quote fix.
agents/mcp/tools.md:162From the report
|transferSol| Send SOL to another wallet (supportssendMaxto drain full balance) |
The MCP and plugin pages also describe autopay from a local keypair that is stored unencrypted. A search of agents/ for approval, confirmation and spending limits found such guidance only on the OKX page.
Require explicit human approval for transfers and autopay. Advise keeping only a minimal balance in that keypair and restricting the keypair file's permissions.
Review: Tool listed as claimed; the premise "keypair stored unencrypted" is not in the audited copy (grep found no such statement); approval is client-side. Missing-guidance finding.
src/pages/HeliusGatekeeper_full/blog/introducing-next-generation-enhanced-websockets.md:152-156From the report
const/RAYDIUM_LAUNCHPAD_PROG/= '(a string literal broken across lines)
The script does not parse. The post also uses atlas-* hosts and says Enhanced WebSockets need a Business plan (line 134), while current pages say Developer.
Fix the literal and the hosts, or mark the post as historical.
Review: Confirmed (' string broken across lines 154-156; atlas hosts 139-141; Business plan line 134). It is a dated blog post, not reference documentation.
laserstream/historical-replay.md:166From the report
lastProcessedSlot = maxReplaySlot;
The window keeps moving between computing the slot and subscribing. Line 136 says an out-of-window slot is rejected.
Add a safety margin, or retry with a newer slot when the request is rejected.
Review: Only on the already-lossy branch (disconnect over 48 h); plausible rejection by a few slots, a retry fixes it.
laserstream/historical-replay.md:177From the report
lastProcessedSlot = Number(data.transaction.slot);
Resuming with fromSlot set to this value replays transactions that were already handled, and the sample does not deduplicate.
Deduplicate by signature, or advance the watermark only when the slot is complete.
Review: Inclusive resume duplicates same-slot transactions; at-least-once is normal and benign for most consumers.
billing/credits.md:314 vs laserstream/clients.md:44From the report
LaserStream gRPC Devnet is available on all plans.vsDevnet is available on Developer and above.
Choose one policy and apply it on every page.
Review: Contradiction real (all plans vs Developer and above); laserstream.md:41 and grpc.md:115 say all plans, four other pages say Developer+.
data-streaming/quickstart.md:152-157From the report
-H "Authorization: Bearer YOUR_API_KEY"/"transactionTypes": ["Any"],
The webhook reference authenticates with ?api-key=. "Bearer" appears there only as the user's own authHeader value. Any matches no documented type (a search of webhooks/ found none).
Use ?api-key= and a documented type, or leave transactionTypes empty to match all types.
Review: Differs from the reference style (?api-key=, api-reference/webhooks/llms.txt:83) and "Any" is undocumented in the audited copy; actual server behaviour not verifiable, failure would be loud.
data-streaming/quickstart.md:165From the report
app.post("/webhook", (req, res) => {/req.body.forEach((event) => {
Anyone who knows the URL can post forged events. authHeader is listed only as an undescribed field on the create and update reference pages.
Document authHeader, check it in constant time, and reject mismatches with 401.
Review: Minimal quickstart handler; authHeader usage is shown in api-reference/webhooks/llms.txt:105,122,205, so "only an undescribed field" is partly wrong. Guidance gap, not a defect.
api-reference/rpc/http/getprogramaccountsv2.md:46 (same text in gettokenaccountsbyownerv2.md:57)From the report
End of pagination is only indicated when **no accounts are returned**. ... always continue pagination until paginationKey is null.
State one rule (presumably that paginationKey is null).
Review: Wording clumsy; the operative instruction ("continue until paginationKey is null") is unambiguous.
rpc/how-to-index-solana-data.md:347 (and 394)From the report
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',(two U+200B characters before the ID)
The pasted value is not a valid public key, so the filter errors or matches nothing.
Remove the characters.
Review: Confirmed: two U+200B before the ID at both lines (byte check). Pre-existing (not in diff). Error would be loud ("invalid pubkey") though hard to diagnose. Borderline.
rpc/how-to-index-solana-data.md:346From the report
accountsInclude: [
Every other sample uses accountInclude, and an unknown field means the program filter is not applied.
Rename the field to accountInclude.
Review: Only occurrence in the audited copy (others accountInclude, e.g. laserstream/grpc.md:129). Effect of an unknown field is not verifiable here.
data-streaming.md:39, 57 (line 140 says 48 h); rpc/how-to-index-solana-data.md:319From the report
<Card title="24-Hour Historical Replay"/✅ 24h
Use 48 h everywhere.
Review: Real; stale low figure.
parsed-streams/guides/handling-reconnects.md:22From the report
If you see nothing at all for over a minute ... assume the connection is dead and reconnect
A half-open socket never fires close, so the at-most-once stream stalls silently.
Track the time of the last activity, and terminate the socket when it is stale.
Review: Text/code gap real; the code is explicitly a skeleton (stub comment at 77).
parsed-streams/guides/handling-reconnects.md:17From the report
Protocol-level WebSocket pings that client libraries send automatically do **not** reset the idle timer.
Samples on other pages use ws.ping() as their only keepalive, for example trade-on-preconfirmations.md:63 and data-streaming/quickstart.md:130. If this page is right, those samples are disconnected after 10 quiet minutes.
State per endpoint what resets the idle timer, and use that in every sample.
Review: The "pings do not reset the idle timer" sentence is about Parsed Streams; data-streaming/quickstart.md:130 uses the standard mainnet WebSocket, where rpc/websocket.md:642 says a ping is right. Only the preconf sample (same beta host family) is arguably in conflict, and the audited copy does not say how preconf treats pings.
rpc/websocket.md:399From the report
this.subscriptions.set(id, { method, params, callback });
The class has no onmessage handler, so callbacks never fire. The migration example (line 751) calls an unsubscribe() that does not exist.
Add message routing and unsubscribe(), as in the quickstart manager.
Review: Confirmed: class (328-427) has no onmessage or unsubscribe; it is a reconnection-focused sketch that never claims completeness. Pre-existing.
rpc/websocket/stream-pump-amm-data.md:147From the report
if (retryCount >= MAX_RETRIES) {…return;
Cap the delay and keep retrying, or exit so a supervisor restarts the process.
Review: 1+2+4+8+16 = 31 s confirmed; an explicit logged give-up message is a design choice.
.result from a Promiserpc/optimization-techniques.md:72From the report
const { priorityFeeEstimate } = await response.json().result;
This throws a TypeError every time it runs.
Write const { result } = await response.json();.
Review: Confirmed (await response.json().result destructures undefined, TypeError every run), but loud with a one-line fix. Body also lacks jsonrpc/id (64-70).
rpc/optimization-techniques.md:182-183From the report
limit: 1000,/changedSinceSlot: lastProcessedSlot
Loop on paginationKey, and advance the watermark to a slot captured before the first request.
Review: Fragment demonstrating the parameter; paging is shown just above (160-164).
agents/typescript-sdk/best-practices.md:162From the report
const baseline = await helius.getProgramAccountsV2([programId, { limit: 10_000 }]);
Read the slot first, page through the whole baseline, then use the earlier slot as the watermark.
Review: Fragment; currentSlot undefined; comment says "all accounts" with one page. Misleading but small.
das/pagination.md:216From the report
let promise: Promise<number> = new Promise(async (resolve, reject) => {
A 429 or error response throws inside the executor. reject is never called and Promise.all hangs.
Use plain async functions and check the responses.
Review: Anti-pattern real; but a throw inside an async executor is an unhandled rejection that terminates Node (15+), so it fails loudly rather than hanging.
rpc/gettransactionsforaddress.md:97, 709From the report
console.log('Successful transactions in January:', data.result.data);/.flatMap(r => r.result.data)
Busy addresses are silently truncated at 1,000 results, and an error response makes the flatMap throw.
Loop on paginationToken and check r.error.
Review: Real; line 713 tells the reader to iterate windows, pagination documented elsewhere on the page.
pre-confirmations/guides/trade-on-preconfirmations.md:63 (prose at line 122)From the report
setInterval(() => ws.ping(), 30_000); // keep the connection alive
The text says the ping tells a quiet stream from a dead one, but nothing checks for a pong.
Track the time of the last pong, and terminate the socket when it is stale.
Review: Real; pong monitoring is a robustness extra, close then exit is the stated design.
parsed-streams/guides/track-pumpfun-mints.md:36 (code at line 95)From the report
keep the connection alive on a quiet filter, and reconnect automatically on close.
The code has no keepalive and no slot-gap backfill, and it retries forever on auth errors. Delivery is at-most-once, so tokens deployed during a gap are lost.
Add a backfill, bounded backoff and a stop on auth errors, or change the claim.
Review: Text claims keepalive (36); code has none (68-98). Tip at 104 points to the reconnect guide.
parsed-events/guides/fetch-pumpfun-mints.md:92From the report
if (result.parserStatus !== "OK") continue;
Collect and report failed signatures, and retry or fetch them.
Review: Deliberate filter in a demo; a nice-to-have at most.
billing/rate-limits.md:434From the report
const res = await request();
Catch thrown errors and apply the same backoff.
Review: Real vs table line 420; example only.
wallet-api/balances.md:302From the report
const token = balances.find(t => t.mint === tokenMint);
Pages hold at most 100 tokens sorted by value, so low-value tokens are reported as "not found".
Page through the results until the token is found.
Review: Real; the same page documents paging (352-361) and the 100-token cap (17).
sdks/rust.md:61From the report
let response: Result<Vec<EnhancedTransaction>, HeliusError> = helius.parse_transactions(request).await;
helius::error::Result (imported at line 44) takes one type parameter, and HeliusError is never imported.
Write let response = helius.parse_transactions(request).await;.
Review: Line 44 imports helius::error::Result and line 61 gives it two type arguments; whether that alias takes one parameter needs the crate source, which is not in the audited copy. Likely true; a compile error is loud either way.
Info after review (1)
src/diffs/HeliusAPI_full/api-reference__admin__get-project-usage.md.diff:75From the report
-<ParamField body="usage" type="object">…+<ParamField body="credits" type="object">
Clients that read usage.rpc, usage.stream or usage.websocket now get undefined values, with no deprecation note.
Keep usage as a deprecated alias for a stated period, and publish a mapping from old keys to new keys.
Review: The docs faithfully record an API change (usage to credits). Nothing in the audited copy shows the old fields still exist; "no deprecation note" is a product-policy complaint, not a doc defect.