| Audited | developers.jup.ag 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) |
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. 43 findings were first rated Medium or High; 4 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 (4)
src/pages/ultra/add-payer.md:30 (also src/pages/swap/advanced/gasless.md:115, :145)From the report
| **Requires backend signing** | Integrator is required to proxy the request to their backend in order to sign the transaction (partially signed by user) before sending to `/execute` |
The backend signs client-supplied bytes with a funded payer key. A malicious client can swap in instructions that drain the payer.
Tell the backend to compare the transaction byte for byte against the one cached by requestId, or to parse it and refuse anything that debits the payer beyond fees and rent.
Review: ultra/add-payer.md:30 says the integrator "is required to proxy the request to their backend in order to sign the transaction (partially signed by user)". swap/advanced/gasless.md:115,145 does the same. Nowhere in either page (grep for validate/verify/compare/requestId finds nothing relevant) is the integrator told to check what the client sent before co-signing with a funded payer key. The only warning (gasless.md:145 Tip) is about users closing accounts, a different vector. This is the most security-relevant omission in the audited copy; it is guidance omission, not a bug, so MEDIUM and not HIGH.
src/pages/trigger/best-practices.md:32From the report
renew by updating the order before it expires via the [update endpoint]
The update body (trigger.yaml:926-943) has no expiresAt field. Orders expire even though the integrator thinks they renewed them.
Add expiresAt to the update endpoint, or document cancel-and-recreate.
Review: trigger/best-practices.md:32 recommends 30-day expiry "with periodic renewal" and says to renew "by updating the order before it expires via the update endpoint". The update body in trigger.yaml:~917-943 has no expiresAt, and manage-orders.md:44-70 lists the updatable fields per order type with none either. The recommended renewal path does not exist as documented. A stop-loss or take-profit then lapses silently. Funds are recoverable (manage-orders.md:156), which is why this is not higher.
src/pages/prediction/forecast.md:148From the report
slippageBps: "10000",
A real-funds swap is executed with no price protection, which invites sandwich attacks and very bad fills.
Use a bounded value, explain the risk, and check the quoted output before signing.
Review: prediction/forecast.md:148 sets slippageBps: "10000" (100%) on a real-funds USDC swap into an outcome token, with the prose "Use a high slippageBps". A copy-pasted sample with no price protection is an unsafe default whatever the managed landing path does. I could not check from the audited copy whether /execute adds MEV protection, so I did not go higher.
src/pages/transaction/submit.md:456 (also swap/build.md:390, swap/build/index.md:390)From the report
const confirmation = await rpc
.confirmTransaction(signature, {
The kit RPC client has no confirmTransaction method. The sample throws after the transaction has already been sent, so the developer has no confirmation path.
Use the kit's send-and-confirm factory, or poll getSignatureStatuses until lastValidBlockHeight passes.
Review: transaction/submit.md:456 (same in swap/build.md:390, swap/build/index.md:390) calls rpc.confirmTransaction(...).send() in the @solana/kit tab. A kit RPC has no such method (it is a JSON-RPC proxy, so the call is sent to the node as an unknown method and fails), and strategy: {type: "blockhash"} is not a kit option. The web3.js tab of the same page (submit.md:223) uses the real Connection.confirmTransaction. The sample throws only after the transaction was sent, so a naive retry could swap twice. Three pages and the primary /build flow keep this at MEDIUM; it would be LOW if only one page had it.
Low after review (56)
src/pages/trigger/create-order.md:69 (also src/pages/trigger/dca.md:87)From the report
const { token } = await fetch(`${BASE}/auth/verify`, { ...
body: JSON.stringify({ type: "message", walletPubkey: owner, signature: bs58.encode(signature) }) })
The authentication page says access_only is deprecated and will be removed. These rewritten quick starts omit authMode and read token. Any integration copied from them stops working when the mode is removed. The "24h JWT" comments at lines 60 and 78 repeat the old model.
Send authMode: "access_refresh", read accessToken/refreshToken/expiresAt, and add refresh-on-401 handling. You can also link to the migration section.
Review: authentication.md:11 says access_only "is still the default ... Nothing breaks today". Stale teaching, nothing fails.
src/pages/trigger/lifecycle.md:53 (also line 22)From the report
The token is valid for 24 hours. There is no refresh endpoint: when it expires, repeat the challenge-response flow.
This contradicts the authentication page and the spec, which document /auth/refresh, /logout and /logout-all. Integrators following this page build a flow that forces wallet re-signing and never adopts the new mode.
Describe the 15-minute access token with a rotating refresh token. Update the diagram label "token · JWT 24h", and mention access_only only as deprecated.
Review: The false sentence is real (:53), but it matches the access_only mode the quick starts use and re-sign is a working fallback. Same root cause as 1 and 3.
src/pages/trigger/best-practices.md:51 (also trigger/errors.md:33, trigger/index.md:121)From the report
**Authentication errors** (401) mean the JWT has expired. Re-authenticate with a new challenge-response flow rather than retrying the same token.
With 15-minute access tokens, this sends users through a wallet signature every 15 minutes. It contradicts authentication.md:223 and :314 ("refresh once and retry").
On a 401, call /auth/refresh once and retry. Start a new challenge only if the refresh itself returns 401.
Review: Real contradiction with authentication.md:223,314. Costs extra wallet prompts, not failure.
src/pages/trigger/authentication.md:15From the report
**`access_only` is deprecated (2026-07-21) and will be removed.** ... Nothing breaks today
Integrators cannot plan the migration window, and the old mode is still the default.
Publish the enforcement date and the date the default flips to access_refresh.
Review: Line says "deprecated (2026-07-21) and will be removed".
src/pages/trigger/authentication.md:117From the report
const signedTx = await wallet.signTransaction(
VersionedTransaction.deserialize(Buffer.from(challenge.transaction, 'base64'))
);
The page's own security note says to verify the challenge before signing, but the sample signs blindly. A tampered challenge transaction would be signed.
Before signing, confirm that the transaction holds only the memo instruction, that the user is the fee payer and that it has no transfers. For the message variant, confirm the wallet and timestamp.
Review: Sample as quoted; page elsewhere says verify first.
src/pages/trigger/manage-orders.md:132 (also trigger/deposit.md:120, trigger/authentication.md:92)From the report
const signedTransaction = await wallet.signTransaction(transaction);
...
signedTransaction: Buffer.from(signedTransaction.serialize()).toString('base64'),
A user cancel or a silent rejection surfaces as an unhandled exception mid-flow.
Wrap the call in try/catch, and stop before confirm-cancel or deposit when the user rejects.
Review: Also deposit.md:120, authentication.md:92 exist.
src/pages/prediction/claim-payouts.md:256 (note at line 151)From the report
const transaction = VersionedTransaction.deserialize(
Buffer.from(claimResponse.transaction, 'base64')
The new note says some markets return a "no claim required" message and no transaction. Buffer.from(undefined) throws, which aborts the loop, so no later position is claimed.
Check that claimResponse.transaction is a string before deserializing. Skip and log any other response.
Review: The throw needs a position that is claimable yet needs no claim; the page does not say that combination occurs.
src/diffs/prediction/social-features.md.diff:24From the report
-| Follow | `POST /follow/{pubkey}` | Follow a trader |
-| Unfollow | `DELETE /unfollow/{pubkey}` | Unfollow a trader |
Several breaking changes were made silently: follow endpoints removed, the claim blockhash moved into txMeta, and status values dropped. Stale text remains, such as "follow traders" at prediction/position-data.md:310, so existing clients break without warning.
Publish a change note that lists each breaking change and its date, and remove the stale references.
Review: Follow rows removed, stale "follow traders" at position-data.md:310 is real. Pages carry a beta breaking-change warning.
src/pages/prediction/forecast.md:61From the report
const tx = VersionedTransaction.deserialize(Buffer.from(build.transaction, "base64"));
tx.sign([wallet]);
The integrator never confirms that the fee payer and transfer source are their own wallet, or that requiredSigners are covered.
Add a short pre-sign check of fee payer, transfer source and required signers.
src/pages/prediction/forecast.md:145From the report
outputMint: market.outcomeMint,
The page documents the outcome token as Token-2022 and the API exposes outcomeTokenProgram, but the sample trusts the returned mint unchecked.
Check that outcomeTokenProgram is the Token-2022 program and that the mint is the expected one before building the swap.
src/pages/lend/dex/cpi.md:67 and :107From the report
The Liquidity Layer accounts are validated inside the Liquidity Layer CPI; you don't verify them yourself.
let cpi_ctx = CpiContext::new(ctx.accounts.dex_program.to_account_info(), cpi_accounts);
If an integrator's program accepts a caller-supplied dex_program or token program, that program is never checked. In the PDA-signing variant, the program's signature can then be spent on a malicious program, which risks the funds the PDA controls.
State that the DEX, Liquidity Layer, oracle and token programs must be constrained to their published ids, for example Program<'info, Dex> or an address = constraint. Show these constraints in the example accounts struct.
Review: Hardening omission is real, but the MySwap accounts struct is never shown, so no insecure code is taught. The "you don't verify them yourself" sentence covers Liquidity Layer accounts only.
src/pages/portal/migration.md:16From the report
| Old API gateway (including `lite-api`) progressively deprecated | When new gateway is stable.
Integrators cannot plan the cut-over.
Publish a warning window and an enforcement date.
src/pages/transaction/submit.md:579From the report
The legacy REST endpoint `POST https://api.jup.ag/tx/v1/submit` still accepts the same signed transactions, but `tx.jup.ag` is the recommended path going forward.
There is no sunset date, the new path requires an API key, and swap.md:28 and :32 still name /submit.
Publish the deprecation timeline, state the key requirement, and update the stale references.
Review: swap.md:28,32 name /submit as stated.
payer example is Jupiter's own sponsorship walletsrc/pages/openapi-spec/swap/v2/swap.yaml:240From the report
example: gasTzr94Pmp4Gf8vknQnqxeYxdgwFjbgdJa4msYRpnB
The gasless page uses this same address as the marker for automatic sponsorship. A copied request is ambiguous and cannot be co-signed by the integrator.
Use a placeholder such as YOUR_PAYER_WALLET.
src/pages/prediction/open-positions.md:118 (also prediction/claim-payouts.md:261, prediction/manage-positions.md:184)From the report
const blockhashInfo = await connection.getLatestBlockhashAndContext({ commitment: "confirmed" });
...
lastValidBlockHeight: blockhashInfo.value.lastValidBlockHeight,
The API now returns txMeta.blockhash and txMeta.lastValidBlockHeight for the signed transaction. Confirming against a later blockhash reports expiry later than it really happens.
Confirm with the response's txMeta values.
Review: Also claim-payouts.md:261, manage-positions.md:184.
src/pages/trigger/best-practices.md:74From the report
Poll the history endpoint to track order state; the states differ by family.
With a 1 RPS shared plan, unbounded polling consumes the rate budget.
State an interval with backoff, the terminal states per order family and a maximum duration.
src/pages/trigger/authentication.md:228From the report
if (res.status === 401) {
await refreshTokens(); // updates accessToken/refreshToken, or re-authenticates on 401
Each request that gets a 401 starts its own refresh with the same rotating refresh token. The page says that reusing a refresh token outside a short grace window revokes the whole session, so parallel requests can log the user out. refreshTokens() is also left undefined.
Share one in-flight refresh promise that every caller awaits. Store the new pair atomically, and coordinate across tabs if the tokens are persisted.
Review: The page itself (:~217) tolerates repeats of the same /refresh inside a "short grace window", which is what parallel callers produce. refreshTokens() undefined is a sketch gap.
resolveAt type changed and metadata removed in placesrc/diffs/api-reference/prediction/get-market.md.diff:47From the report
- type: number
- description: Unix timestamp (seconds) when the market resolved (null if pending)
- metadata:
Parsers written against the old contract fail on the same /prediction/v1 path. The new spec allows string | number, which leaves clients guessing.
Pick one type, add new keys rather than repurpose old ones, and announce the change.
Review: Beta; the new spec at least states "ISO 8601 string once resolved". string | number looseness is the residual.
src/pages/openapi-spec/prediction/prediction.yaml:2302From the report
description: YES-side levels sorted by price ascending.
The baseline said "descending". Code that reads yes[0] as the best price now reads the worst level.
State which end holds the best bid, and announce the change. Prefer a new field to reversing an existing one.
Review: "ascending" is now stated identically in 3 places (events-and-markets.md:254, get-orderbook.md:61,73). Whether the API changed or the docs were corrected cannot be told; the missing "best price is at which end" line is the real gap.
src/pages/prediction/social-features.md:144From the report
"id": 425993,
The spec now types id as a string ("order-1906029"). Clients built from the example parse it as an integer and fail.
Update the example and the field table to string ids.
Review: Real stale example (get-trades.md:54 says "order-1906029").
src/pages/openapi-spec/prediction/prediction.yaml:862From the report
realizedPnlUsd:
type: number
description: Realized PnL in micro USD (i64 as number)
Sibling amounts are strings, and forecast.md tells readers never to use Number for amounts. Values above 2^53 lose precision.
Return the amount as a string.
maxQuoteAmount is a number while sibling amounts are stringssrc/pages/openapi-spec/studio/studio.yaml:568From the report
maxQuoteAmount:
type: number
The fee endpoint returns base-unit amounts as strings, but this request takes a number with no stated unit, which risks precision and unit errors.
Accept a base-unit string and state the unit.
src/pages/lend/borrow/api.md:59From the report
Vault and position IDs are scoped to a market. The same `vaultId` refers to a different vault in `main` than in `ethena`
A position id alone is ambiguous, and an operate call that omits market silently uses main.
Echo market in every vault, position and operate response. Document it in the operate payload table.
src/pages/openapi-spec/tokens/v2/verification.yaml:262From the report
description: Atomic JUP output from the craft quote (CraftTxnResponse.amount), echoed back so revenue tracks the actual JUP collected on swap paths.
The server already has requestId, so a client-supplied value can be wrong or forged.
Look the value up server-side by requestId, and reject mismatches.
Review: Field at :262, quoted description at :264.
src/pages/ultra.md:7 (same in ultra/index.md)From the report
> Jupiter's flagship swap API: ... Start here for most swap integrations.
**Ultra Swap API** is no longer actively maintained and has been superseded by [Swap V2](/docs/swap).
New integrators are told to start on an unmaintained API.
Rewrite the tagline and intro to point to Swap V2 and the migration guide.
Review: Tagline "Start here" is wrong, but a Warning box four lines below says "no longer actively maintained".
src/pages/recurring.md:26 (also lines 36-37, recurring/get-recurring-orders.md:63)From the report
| **Price-based recurring** | Create price-based recurring orders that execute when certain market conditions are met. |
...orderStatus=history&recurringType=price
The sub-pages say price-based orders are no longer supported and that price is deprecated, yet the overview and the history sample use them.
Remove or mark the price-based rows and steps, and use recurringType=time in the sample.
Review: The whole Recurring section carries a deprecation warning; sub-pages mark price-based deprecated. Overview rows and the recurringType=price sample (get-recurring-orders.md:63) are stale.
src/pages/swap/build.md:375 (also line 507 and swap/build/index.md)From the report
const submitRes = await fetch("https://api.jup.ag/tx/v1/submit", {
...
const { signature } = await submitRes.json();
The submit page names tx.jup.ag as the recommended path, and the REST body is no longer documented anywhere. The sample also never checks submitRes.ok, so an error yields signature === undefined.
Switch to the tx.jup.ag sample from the submit page, or label these samples legacy. Check the response status.
Review: submit.md:579 says the legacy endpoint "still accepts the same signed transactions"; portal/api-keys.md:51 treats it as live. Missing submitRes.ok check is a sample nit.
src/pages/portal/migration.md:15 and :68 (also portal/faq.md:55, :59)From the report
| Grace period ends and new payment method needs to be already set up | 30th June 2026 |
You must choose a paid plan before the grace period ends or your rate limit will drop to the Free tier (1 RPS).
The docs are dated 2026-10-11. Readers cannot tell what limit applies to them now.
Rewrite in the past tense and state the current limits for keys that were not migrated.
Review: 30 June 2026 is in the past, so the page is stale (faq.md:55,59 same). A historical timeline, not a trap.
src/pages/tool-kits/plugin/faq.md:18From the report
You can create via [scripts](/docs/ultra/add-fees-to-ultra) or [Referral Dashboard](https://referral.jup.ag).
Current guidance routes readers into the deprecated section.
Link to the current referral setup.
src/DELTA_MANIFEST.md:384From the report
| FETCH-FAILED | `mcp.md` | 404 | yes | sitemap | URL listed in current sitemap.xml but returns HTTP 404
The sitemap entry leads crawlers and readers to a dead page.
Remove the entry or redirect it to the MCP pages, and add a release check that every indexed URL returns 200.
src/DELTA_MANIFEST.md:21From the report
It found 19 live pages that are in no index: the deprecated Ultra section ... plus `skill`.
The indexes and the live site disagree, so AI tools and search miss the pages or index them without a deprecation label.
List them with an "unmaintained" label, or unpublish them and redirect.
src/DELTA_MANIFEST.md:48From the report
Portfolio (6 pages ...) all redirect to `get-started`: the Portfolio API docs are gone.
No page in the delta mentions the retirement. (The changelog moved out of the docs and was not checked.)
Add a retirement notice that gives the date and the alternatives.
src/diffs/portal/plans.md.diff:35From the report
-| `/ultra/v1/order` | 1 |
-| `/tokens/v2/content` | 1 |
Users of still-documented endpoints cannot find what those calls cost.
Keep a labelled group for these endpoints while their pages remain live.
Review: tokens/v2/content rows are at diff :72.
src/pages/ai/skills.md:23From the report
| Recurring | Dollar-cost averaging (DCA) strategies |
It contradicts the move of DCA to the Trigger API.
Point the DCA row to Trigger.
src/pages/trigger/lifecycle.md:118From the report
* **`open`** orders can be updated or cancelled. Updates and cancellation are only valid from this state.
* **`expired`** orders ... retrieved with the same cancel flow (see below).
Integrators may block withdrawal of expired orders, which strands the user's funds in the vault UI.
List every state that accepts cancel (open, expired, and mid-cancel), and say that confirm-cancel may be retried.
Review: One over-broad sentence ("only valid from this state"); the next bullet (:120), :159, index.md:105 and manage-orders.md:156 all say expired orders use the cancel flow.
src/pages/openapi-spec/trigger/v2/trigger.yaml:641From the report
Call this once per wallet. Subsequent calls return the existing vault.
'409': description: Vault already exists
Clients that follow the description treat the 409 as fatal.
Document the 409 together with the returned vault details, matching deposit.md:45 and errors.md:65.
Review: Spec contradiction is real; deposit.md:44 and errors.md:65 explain the 409 and the quick starts call GET /vault first.
src/pages/recurring/withdraw-price-order.md:31From the report
Choose the order account to deposit by making a post request to the `/priceDeposit` endpoint
Following the text on a funds-moving flow builds a deposit, not a withdrawal.
Use /priceWithdraw and "withdraw from".
Review: Prose copy-paste error on a deprecated endpoint; the code sample on the same page (:52) correctly calls /priceWithdraw.
src/pages/prediction/open-positions.md:168 vs src/pages/prediction/position-data.md:180From the report
| `created` | Order account created on-chain, waiting to be filled |
| `status` | `pending`, `filled`, or `failed` |
Clients that switch on pending never match. The spec enum and prediction.md:117 disagree as well.
Use one status set everywhere: created, partiallyfilled, filled, failed.
Review: Real: created/partiallyfilled vs pending in position-data.md:180, prediction.md:117, spec enum :390. /orders/status has an untyped status string, so two endpoints may legitimately differ.
contracts, which the spec says is sell-onlysrc/pages/prediction/open-positions.md:44From the report
| `contracts` | string | No | Number of contracts to purchase |
(spec line 687: Legacy whole-contract sell quantity (only when `isBuy` is `false`))
Buy requests are sized wrongly or rejected.
Size buys with depositAmount, and document the sell-only quantity fields.
Review: Real, but the field is marked optional ("No") and the sample sizes the buy with depositAmount.
src/pages/openapi-spec/prediction/prediction.yaml:718From the report
description: Relative execute path. Resolves to `https://api.jup.ag/prediction/v1/execute`.
example: /api/v1/execute
A client that resolves the example calls a URL that does not exist.
Make the example match the description, and state the base URL.
Review: The description states the full URL; the example is likely the raw relative value the server returns. Same text in 3 reference pages.
tag, but the API takes tagssrc/pages/prediction/forecast.md:87From the report
`${PREDICTION_API}/events?provider=bisonfi&category=crypto&tag=15m&includeMarkets=true`
The filter is ignored, and the sample returns every crypto event.
Use tags=15m.
Review: Typo confirmed against spec :1448-1451 (name: tags). The sample still filters to provider=bisonfi and tradable. Returns extra markets only.
src/pages/prediction/position-data.md:181From the report
| `filledContracts` | Number of contracts executed |
The same page warns that unsuffixed contract fields are floored. Accounting built on them is wrong for fractional fills.
Document the *Micro and *Decimal fields and mark the plain fields as legacy.
Review: Spec has filledContractsMicro/Decimal (prediction.yaml:1104,1107); the guide omits them. index.md:256 already warns the plain fields are floored.
result and resolveAt cannot be null under the spec as writtensrc/pages/openapi-spec/prediction/prediction.yaml:141-159From the report
result:
type: string
nullable: true
enum: ["yes", "no"]
In OpenAPI 3.0 the enum must include null, and nullable next to a bare oneOf has no effect. Generated clients reject every open market.
Add null to the enum and give resolveAt a single nullable type.
Review: Strict-OpenAPI-3.0 pedantry (OAS 3.0.3 wants null in the enum). "Every open market rejected" assumes a strict generator.
src/pages/openapi-spec/prediction/prediction.yaml:127 vs :1345From the report
- polymarket / - gx / - bisonfi (Market response)
- kalshi / - polymarket / - bisonfi (GET /events filter)
Typed clients fail when a returned value is not in the response enum.
Share one provider schema.
Review: kalshi in the filter (:1343), gx in the response (:127) is a real mismatch. Failure needs the API to actually return kalshi, which the audited copy cannot show.
src/pages/price.md:35 vs :94From the report
you may face many tokens where price is not available or returns null.
Tokens without a reliable price are **omitted entirely** from the response.
Code that checks for null throws on the missing key.
Rewrite the caution to tell readers to check whether the key is present.
Review: The delta itself added the explicit "omitted entirely" section with an id in price check (:94). Only the older caution sentence is stale.
src/pages/portal/rate-limits.md:58 and :62From the report
const response = await fetch("https://api.jup.ag/tokens/v1", {
const remaining = Number(response.headers.get("x-ratelimit-remaining"));
/tokens/v1 is not a documented route. When the header is missing, Number(null) is 0, so every call sleeps 1 s. The request is also never retried after a 429.
Use a real /tokens/v2 route, apply the check only when the header is present, and retry with a cap.
Review: /tokens/v1 is undocumented (elsewhere /tokens/v2); Number(null) gives 0 so it sleeps 1s when the header is absent. Illustrative snippet.
src/pages/lend/api-vs-sdk.md:28From the report
| Vault config and state | - | `getAllVaults`, `getVaultByVaultId` | - |
| Borrow vaults and user positions | `vaults`, `positions` endpoints | ...
Readers are steered to the SDK for data the REST API now serves. The recommendation table at lines 113-116 repeats the error.
Add the REST endpoints to the row and the recommendations.
Review: Row 28 shows REST "-", row 29 shows REST vaults, positions; recommendations :113-116 steer to SDK. Steering only.
src/pages/openapi-spec/send/send.yaml:379 and :76From the report
confirmed:
type: integer (examples: confirmed: true)
- Unix timestamp of when the invite will expire (example: "2025-03-22T10:30:00.000Z")
Generated clients fail to parse responses that match the examples.
Make confirmed a boolean and describe expiry as an ISO-8601 string, or change the examples to match the types.
Review: confirmed is integer with true in examples (:223); expiry described as Unix timestamp with ISO example (:73,84). Both real.
fee required yet describes a defaultsrc/pages/openapi-spec/studio/studio.yaml:341 and :436From the report
- fee
- If not provided, the default fee will be 100 basis points (1%)
Callers that omit fee on the strength of the description get a 400.
Make fee optional, or delete the default text.
Review: Both lines confirmed (required at :341, default text at :436). Which one is wrong is unknown.
src/pages/ultra/fees.md:13From the report
with only 5 to 10 bps of the swap amount as a fee.
| New Tokens (within 24 hours token age) | 50 |
Fee quotes built from the summary are wrong for new tokens and for free pairs.
State the real range and point to the table.
Review: Real; table (:25-33) has 0, 2, 5, 10 and 50. Deprecated page; /order returns feeBps.
src/pages/swap/migration/metis-to-build.md:55From the report
Use the `/order` response fields (`inputAmountResult`, `outputAmountResult`)
These fields belong to the /execute response, and /build users call neither endpoint.
Tell /build users to parse token balance changes.
Review: inputAmountResult is an /execute field (order-and-execute.md:103), not /order. The sentence also offers "or parse token balance changes".
src/pages/ultra/execute-order.md:47 (also ultra/add-fees-to-ultra.md:241)From the report
const wallet = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY || '')));
There is an extra ) and bs58 is never imported, so the snippet fails on copy-paste.
Fix the parentheses and add import bs58 from 'bs58'.
Review: Extra ) and no bs58 import confirmed; also mixes import and require. Visible compile error on a deprecated page.
src/pages/ultra/add-fees-to-ultra.md:362 (also ultra/add-payer.md:55)From the report
'taker=jdocuPgEAjMfihABsPgKEvYtsmMzjUHeq9LX4Hvs7f3&' +
The signature does not match the required signer, so execution fails.
Use taker=${wallet.publicKey.toBase58()} and a payer placeholder.
Review: Taker jdocu... is a placeholder and the call fails at execute without harm. The second cite (add-payer.md:55) is a payer= value, not a taker (taker there is <pass-in-taker-account>), so that half does not apply.
src/pages/swap/advanced/transaction-versions.md:220From the report
if (simulation.value.err) console.error("Simulation failed:", simulation.value.err);
A transaction known to fail is broadcast with preflight skipped, and the user pays the fee.
Stop when the simulation returns an error.
Review: console.error without exit is confirmed; the sibling kit sample exits (submit.md:419). Cost is one tx fee.
src/pages/swap/advanced/transaction-versions.md:237From the report
for (let i = 0; i < 40; i++) {
await new Promise((r) => setTimeout(r, 1500));
After about 60 s the loop ends with no error, so "not landed" looks like success.
Throw after the loop, or poll until the block height passes lastValidBlockHeight.
Review: Confirmed, no throw after 40 tries. Sample-quality issue.
nullable beside $ref makes a common null field invalidsrc/pages/openapi-spec/swap/v2/swap.yaml:616From the report
cleanupInstruction:
$ref: "#/components/schemas/Instruction"
nullable: true
OpenAPI 3.0 ignores siblings of $ref, so null, which is returned on the main /build path, fails strict validation. tokens.yaml:406-417 has the same problem.
Wrap the reference in allOf, as tipInstruction already does.
Review: Pattern confirmed (tipInstruction uses allOf, :626). Strict-validator issue only; same class as 49.
Info after review (2)
src/DELTA_MANIFEST.md:59From the report
| CHANGED | `ai.md` | 2,379 | yes | sitemap | +3/-3 lines |
| CHANGED | `ai/index.md` | 2,379 | yes | llms.txt+llms-full | +3/-3 lines |
The copies are in step today, but every edit must be made twice.
Generate one copy from the other, or redirect one to the other.
src/pages/transaction/submit.md:45From the report
| **Transaction size** | Must not exceed the Solana transaction size limit (1232 bytes) |
swap/advanced/transaction-versions.md:13 promotes v1 transactions of up to 4096 bytes. It is unclear whether those can be submitted here.
State whether larger v1 transactions are accepted.
Review: Conflict is real on its face (transaction-versions.md:13) but the report itself says "unclear"; the v1 sample sends via plain RPC, not /submit. Needs the live service.
Excluded on review (2)
Findings the review showed to be wrong or a repeat of another finding. They are not counted above.
- 19. Price-impact unit and router value changed in place (wrong): Looks like a doc correction:
pages/ultra/response.md:26,58showspriceImpact-0.0131 besidepriceImpactPct-0.00013 (100x), matching the new text. - 42. Auto-claim described as an absolute guarantee (wrong): The report says the page "still requires manual claims for some markets".