Sample audits · Workstation audits, October 2026

Tensor developer documentation

A fresh audit of the current Tensor developer documentation.

Auditeddev.tensor.trade, all published pages, pages fetched 11 October 2026
Date11 October 2026
How it ranlocal run on our workstation, full audit, Standard review
Verdict after reviewPass with notes (rule: Fail if a High finding remains after review, otherwise Pass with notes)

Related: Tensor developer documentation (May 2026)

0
High after review
1
Medium after review
24
Low after review
0
Info after review
1
excluded on review
How to read this page

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. 22 findings were first rated Medium or High; 1 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 (1)

14. Medium Every transaction in a multi-transaction response is deserialized from the first one
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: recipes/buy-floor-nft.md:67
From the report
Evidence
tx.txV0
? VersionedTransaction.deserialize(response.txs[0].txV0.data)
: Transaction.from(tx.tx.data)

Same code in recipes/place-a-bid-within-5-of-floor-price.md:65 and reference/tensor-transactions.md:101.

Why it matters

the callback ignores its own element. When an endpoint returns more than one transaction (for example a sell with its mint-proof transaction, reference/selltx-1.md:233), the first transaction is signed and sent repeatedly and the later ones, which carry the actual trade, are never sent.

Suggested fix, not tested

use tx.txV0.data for the current element.

Review: recipes/buy-floor-nft.md:65-69 maps over response.txs but reads response.txs[0].txV0.data instead of the element's own tx.txV0.data; identical in place-a-bid-within-5-of-floor-price.md:63-67 and in the primary guide reference/tensor-transactions.md:99-103. When a response carries more than one transaction (the bid recipe comment at :70 says "Must send txs serially for a given response!", and selltx-1.md:235 documents an extra mint-proof tx) the first transaction is sent again and the rest are never sent, while the loop still prints "Transaction confirmed" with the same signature. If the first element is legacy and a later one is V0, txs[0].txV0 is null and it throws, which is loud. Stays MEDIUM because it is silent partial execution on the main documented flow across three pages. It is not higher: a typical buy returns one transaction, and the loss is an unfinished trade rather than lost funds.

Low after review (24)

1. Low Breaking price-argument change announced with conflicting dates, and the reference was never updated
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: changelog/price-endpoints-change.md:14
From the report
Evidence
For API calls, these changes will take affect **one month after the migration date** too.

Also changelog/%EF%B8%8F-final-migration-dates-%EF%B8%8F.md:54 (":stop_sign: Buy / Sell price arguments need to include creator royalties", under "March 31st", which is not one month after the Feb 17th migration), and reference/quickstart.md:9 ("Breaking changes may occur without warning!").

Why it matters

changing what maxPrice/minPrice mean is a breaking change to a money-moving contract. The two changelog pages give different cutover dates, neither gives a year, and the buy and sell reference pages (reference/buytx-1.md:165, reference/selltx-1.md:165) still do not say whether royalties are included. An integrator cannot tell which meaning applies today. Finding 8 shows the docs' own examples were not migrated.

Suggested fix, not tested

publish one dated cutover (with the year). Put the royalty-inclusive meaning in the maxPrice/minPrice parameter descriptions. For future breaking changes, announce them, measure who still uses the old meaning, and switch only after that.

Review: Real: :12,14 say "one month after the migration date"; the final-dates page puts it under "March 31st" (...final-migration-dates...md:50,54) against a Feb 17th migration (:31); no year anywhere; buytx-1.md:165 / selltx-1.md:165 do not mention royalties. But the dates differ by about two weeks, the changelog itself records several delays, and the cutover is long past (pages updated Sept 2025). Docs history, not a trap.

2. Low Examples sign whatever transaction the API returns, with the developer's main wallet, without checking it
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: reference/buytx-1.md:194
From the report
Evidence
"description": "The address that pays the NFT (if different from buyer)",
...
"description": "The transaction fee payer (if different from buyer)",

The recipes load ~/.config/solana/id.json and run txsToSign.map((tx) => tx.sign([wallet])) (recipes/buy-floor-nft.md:20,71) with no inspection.

Why it matters

the endpoint can build transactions where the payer and the fee payer differ from the buyer. The examples teach developers to sign a server-built transaction with their CLI keypair without checking the fee payer, the signer set, the programs called or the amount transferred. No exploit is shown, but signing blindly is the habit that turns any API-side mistake or compromise into a loss.

Suggested fix, not tested

in the transaction guide, show a pre-sign check: decode the message, then assert the fee payer, the expected program ids and that the transfer amount is at most maxPrice. Recommend a dedicated low-balance wallet for scripts.

Review: payer (:194) and feePayer (:203-205) exist; recipe loads id.json at buy-floor-nft.md:18-20 and signs at :71.

3. Low WebSocket stream silently stops after any disconnect and cannot catch up
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/websockets-stream-new-transactions-for-a-collection.md:24
From the report
Evidence
socket.addEventListener('open', (event) => {
    socket.send(JSON.stringify({
        "event": "newTransaction",
Why it matters

the subscription is sent only once, in the open handler. There is no close or error handler, no reconnect and no re-subscribe. After a network blip the script keeps running but receives nothing. Events missed in the gap are lost, because no catch-up route or "since" cursor is documented for the stream.

Suggested fix, not tested

on close, reconnect with backoff and send the subscription again. Document how to backfill the gap (for example the collection transaction-history endpoint with its cursor).

Review: Subscription sent once in open (:24-33), no close/error/reconnect: true, but this is a 20-line quick-start sample. "No catch-up route is documented" is overstated: getcollectiontxhistory.md (cursor, traits) exists and the report itself names it as the fix; it is just not linked from the recipe.

4. Low Unsubscribe requires an id that no subscribe response returns
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/unsubscribe.md:19
From the report
Evidence
"subscriptionId": "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx"
Why it matters

none of the subscribe pages (reference/newtransaction.md:36-48, ammorderupdate.md, tcompbidupdate.md) shows an acknowledgement that carries a subscription id. A client cannot release one subscription without closing the whole socket, so subscriptions pile up on long-lived connections. The response sample at line 28 also shows an unresolved template string (${payload.subscriptionId}).

Suggested fix, not tested

document the subscribe acknowledgement and its id field, and fix the unsubscribe response sample.

Review: Confirmed: grep -ri subscriptionId finds it only in unsubscribe.md:11,19,28, and :28 is an unresolved ${payload.subscriptionId} template. Effect is a doc gap; closing the socket releases everything and a wrong id is a visible error.

5. Low The long-lived socket is never kept alive or closed
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/websockets-stream-new-transactions-for-a-collection.md:17
From the report
Evidence
const socket = new WebSocket("wss://api.mainnet.tensordev.io/ws", {
Why it matters

reference/subscription-api-endpoints.md:13 says ping is how to keep the connection alive, but neither the recipe nor reference/websockets.md pings periodically. Neither closes the socket or unsubscribes on exit. The idle timeout is not documented.

Suggested fix, not tested

add a ping timer that is cleared on close, close the socket on shutdown, and document the idle timeout.

Review: subscription-api-endpoints.md:13 says ping keeps the connection alive; websockets.md:38 sends a single ping.

6. Low The rule that exactly one of tx / txV0 is set is stated in prose and contradicted by the schema
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/buytx-1.md:25
From the report
Evidence
"txV0": {
  "type": "string",
  "format": "byte"
Why it matters

reference/tensor-transactions.md:17-19 says txV0 may be null and then tx must be used. The schema copied into the transaction pages marks txV0 required and non-nullable. It also types both fields as base64 strings, while every example reads tx.txV0.data / tx.tx.data as a byte-array object. A client generated from the schema and one written from the guide cannot both be right.

Suggested fix, not tested

state the rule (exactly one of the two is set), mark both fields nullable, and pick one wire format (base64 string or byte array) for the schema and all examples.

Review: Confirmed: txV0 is required and not nullable (:24-27,45-47), both fields are format: byte strings, while tensor-transactions.md:15-19 says txV0 may be null and every example reads .data. The schema is the auto-generated type, the guide and recipes are consistent with each other, and a mismatch shows at first parse.

7. Low Price and price-unit pairing is undocumented, and wei amounts may arrive as a double
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: reference/refreshmetadata-1.md:71
From the report
Evidence
"priceUnit": { "$ref": "#/components/schemas/Currency" },
"price": { "anyOf": [ { "$ref": ".../Decimal" }, { "type": "string" }, { "type": "number", "format": "double" } ] }
Why it matters

Currency includes ETH_WEI. A wei amount above 2^53 cannot be held exactly in a double, and the contract does not say which representation goes with which unit. The schema is also marked deprecated (line 402) but is still the live response of this endpoint.

Suggested fix, not tested

use a single decimal-string type for price, document how it pairs with priceUnit, and replace the deprecated schema.

Review: priceUnit/price at :71-85, ETH_WEI at :62, deprecated flag at :402 belongs to MintWithColl, which is this endpoint's response (:430). Field is lastSale metadata, not an order amount.

8. Low The meaning of maxPrice changed in place, and the buy examples still send the old meaning
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/buy-floor-nft.md:54
From the report
Evidence
buyParams.append("maxPrice", response.mints[0].listing.price);

Same code in reference/tensor-transactions.md:88.

Why it matters

changelog/price-endpoints-change.md:16 and :54 say maxPrice must now include creator royalties (1.05 SOL for a 1 SOL listing with 5% royalties). Redefining an existing field instead of adding a new one means old callers keep sending a value that type-checks but means something else. The docs' own buy examples are such callers: for any collection that enforces royalties, the documented buy fails its price check.

Suggested fix, not tested

compute maxPrice as listing price plus royalties (state the unit: lamports) in both examples. For future semantic changes, add a new parameter, migrate the consumers, then retire the old one.

Review: Real: :54 and tensor-transactions.md:88 send bare listing.price; the changelog (price-endpoints-change.md:16, :54) says to add royalties. It is the strongest of the LOWs: the flagship buy example. But a too-low maxPrice is a cap, so the failure is a rejected buy, not an overpayment, and whether listing.price already includes royalties is not shown in the audited copy (the 200 schema is empty). Stale example, loud failure, no fund risk.

9. Low Retired TSwap endpoints are still documented as live
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/getusertswaporders-1.md:31
From the report
Evidence
"/user/amm_pools": {
  "get": {
    "operationId": "GetUserTswapOrders",
Why it matters

changelog/%EF%B8%8F-final-migration-dates-%EF%B8%8F.md:41 and :60-61 say TSwap pool creation and deposits are disabled and all TSwap pools are closed. The TSwap pages (getammorders-1.md, tswap*.md, createtswapdepositwithdrawtx-1.md) carry no notice. The TSwap route is named /user/amm_pools while the current AMM route is /user/tamm_pools, which invites mix-ups. The same changelog line also links the "depositing sol" and "depositing NFT" labels to each other's pages, and links a pool-creation page that is not in the reference set.

Suggested fix, not tested

mark retired pages as removed or deprecated with dates, keep a written list of what was retired and when, and fix the changelog links.

Review: The changelog retires TSwap creation and deposits, not the read endpoints. The "no deprecation notice" point stands as a doc omission and the swapped links on the changelog line are real.

10. Low WebSocket handlers can crash the process on a bad frame or a rejected connection
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/websockets-stream-new-transactions-for-a-collection.md:40
From the report
Evidence
if (event_stringified !== '' && event_stringified !== null) {
    console.log(JSON.parse(event_stringified));

Same snippet in reference/websockets.md:48-49 and reference/newtransaction.md:29-30.

Why it matters

toString() never returns null, so the guard is dead. JSON.parse runs without try/catch, so any non-JSON frame throws inside the listener. No error listener is registered on the ws socket, so a rejected handshake (wrong API key) or a network reset ends the process with an uncaught error instead of a clear message.

Suggested fix, not tested

wrap the parse in try/catch, ignore or log unexpected frames, and register an error handler.

Review: Dead guard and bare JSON.parse confirmed (:39-41; same in websockets.md:47-49, newtransaction.md:28-30). The server sends JSON (all sample outputs are objects). With the ws package an unhandled error event is thrown with its message ("Unexpected server response: 401"), so "instead of a clear message" is overstated. Sample hardening.

11. Low Money amounts are declared as 32-bit floats
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: reference/buytx-1.md:170
From the report
Evidence
"format": "float",
"type": "number",
"minimum": 0
Why it matters

maxPrice, minPrice, price, topUp and the TSwap lamports fields use format: float on roughly 30 endpoints (for example listtx-1.md, selltx-1.md:170, collectionbidtx-1.md:172). A 32-bit float cannot hold most lamport values above about 16.7 million exactly, so a client generated from the spec can round a price.

Suggested fix, not tested

declare lamport amounts as 64-bit integers, or as decimal strings, and state the unit on every amount.

Review: Confirmed, but the extent is 14 pages with format: float (grep -l), not "roughly 30 endpoints". Same pages as listed in the report plus getmintsbycollid, traitbidtx-1, etc.

12. Low The recipes cannot run as published
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/buy-floor-nft.md:164
From the report
Evidence
COLLECTION_SLUG=0O0O-1a2b-3c4d
TENSOR_API_KEY=0O0O-1a2b-3c4d
node script.js
Why it matters

assignments on their own lines set shell variables but do not export them, so node script.js sees process.env.TENSOR_API_KEY and the other variables as undefined. This applies to every recipe's "Run Code" block. On top of that:

  • recipes/bid-wall-discovery.md:10 reads COLLECTION_ID, but its run block (line 199) sets COLLECTION_SLUG.
  • recipes/calculate-collection-moving-average.md:44 reads COLLECTION_READABLE_SLUG, but its run block (line 227) sets COLLECTION_SLUG.
  • recipes/place-a-bid-within-5-of-floor-price.md:66 uses Transaction without importing it (line 10).
  • recipes/calculate-collection-moving-average.md:55 calls api-tradingview.tensordev.io, while reference/get-ohlc.md:20 gives api-tradingview.mainnet.tensordev.io.

None of these survives a single end-to-end run.

Suggested fix, not tested

use export VAR=... or prefix the command (VAR=... node script.js), align the variable names, fix the import and the host, and run every recipe end to end in CI before publishing.

Review: Every sub-claim is true and independently checkable, which makes it the best-evidenced of the set. All failures are loud and early (undefined API key, undefined variable, ReferenceError before any send, or a wrong host), so the effect is a failed first run, not a silent wrong result.

15. Low Legacy signing passes an array, and success is printed without checking whether the transaction failed
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/buy-floor-nft.md:71
From the report
Evidence
txsToSign.map((tx) => tx.sign([wallet]));
...
await connection.confirmTransaction(sig, { blockhash: "confirmed" });

Same in recipes/place-a-bid-within-5-of-floor-price.md:69,73.

Why it matters

a legacy Transaction.sign takes signers as separate arguments, so the array form fails on that path. The second argument of confirmTransaction is not a valid commitment. The result of the confirmation is never checked for an error, so a buy or bid that fails on chain is still logged as "Transaction confirmed". A developer can believe a purchase went through when it did not.

Suggested fix, not tested

sign with tx.sign(wallet) for legacy transactions. Confirm with a valid commitment or a blockhash strategy, and check value.err before printing success.

Review: tx.sign([wallet]) on a legacy Transaction (buy-floor-nft.md:71, place-a-bid...md:69) throws before anything is sent (and in the bid recipe Transaction is not even imported, :10). The report's other point needs correcting: confirmTransaction(sig, { blockhash: "confirmed" }) (:74 / :73) has no signature in the strategy object, so in web3.js 1.x (my knowledge, library not in the audited copy) it should throw after sendTransaction has already landed the transaction, meaning the recipes would log an error rather than "Transaction confirmed". The unchecked-error point does apply to the guide's version (tensor-transactions.md:115-116), but a simulated send makes failure rare. Worst case is a user re-running a bid after a misleading error. Loud and low probability, so LOW; it deserves a fix together with 14.

16. Low Examples send identifiers under keys the endpoints do not define, and one can price a bid off the wrong collection
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/buy-floor-nft.md:33
From the report
Evidence
queryParams.append("slug", COLLECTION_SLUG);
Why it matters

/mint/collection requires collId and defines no slug (reference/getmintsbycollid.md:186-191). The same mismatch is in reference/tensor-transactions.md:58, in /tx/collection_bid at recipes/place-a-bid-within-5-of-floor-price.md:52 (requires collId, reference/collectionbidtx-1.md:189-195) and in the newTransaction subscription at recipes/websockets-stream-new-transactions-for-a-collection.md:30 (documented argument collId, reference/subscription-api-endpoints.md:14). In the bid recipe, the floor lookup filters /collections with slugs (line 32), but that endpoint accepts slugDisplays or collIds (reference/getcollections-1.md:330,342). If the unknown filter is ignored, sortBy=statsV2.volume1h:desc&limit=1 returns the busiest collection on the market, and the recipe prices a mainnet bid from that unrelated floor without checking which collection came back.

Suggested fix, not tested

use the documented parameter names, show how to resolve a slug to a collId, and check that the returned collection is the requested one before using its price.

Review: The spec facts are right: /mint/collection and /tx/collection_bid require collId (getmintsbycollid.md:186-191, collectionbidtx-1.md:189-195), /collections takes slugDisplays/collIds (getcollections-1.md:330,342), the WebSocket argument is collId (subscription-api-endpoints.md:14), and the recipes send slug/slugs. The scary branch (bid priced from the busiest collection's floor) needs two things the audited copy cannot show: that /collections silently ignores slugs, and that collection_bid accepts an undocumented slug. The WebSocket recipe's sample output shows events arriving for a slug subscription, so the server may in fact accept slug. Real inconsistency, and the recipe should check which collection came back, but unverified and conditional.

17. Low The bid wall is computed from the first page of bids only
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/bid-wall-discovery.md:22
From the report
Evidence
queryParams.append("limit", 100);
...
for (const bid of response) {
Why it matters

the endpoint is cursor-paginated with a maximum of 100 (reference/getcollectionbids.md:101-120). The recipe never follows the cursor, so the "bid wall" stops at the top 100 bids, and the sample output shows more price levels than 100 bids could produce. The response shape it iterates is not documented.

Suggested fix, not tested

loop on the cursor until it is exhausted, and document the response shape.

Review: The loop never follows a cursor (:22-31); getcollectionbids.md:95-118 has limit max 100 and cursor. The sample output has 126 price levels, more than 100 bids could produce (counted), so the sample is from another run. Analytics recipe, no funds; inherits 18.

18. Low Cursor pagination is offered, but no response documents the cursor or the item shape
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/getcollectionbids.md:39
From the report
Evidence
"schema": {}
Why it matters

25 data endpoints declare an empty 200 response schema while 14 accept a cursor ("The cursor returned from the previous response"). The next-cursor field, the end-of-data signal and the item fields are not defined anywhere, so clients cannot paginate reliably (finding 17 is the result). Pagination styles also differ: getmintlist-1.md:120 uses an undocumented after, and getcollections-1.md:378 uses page numbers.

Suggested fix, not tested

publish response schemas including the cursor and end-of-data signal, and use one pagination style.

Review: Counted: 25 reference pages with "schema": {} and 14 with a cursor parameter. Pagination styles differ (getmintlist-1.md:120 after, getcollections-1.md:378 and searchcollections.md:339 page). A reference gap that shows on the first call, not wrong guidance. Largest real gap in the set, borderline, but missing documentation is LOW under the yardstick.

19. Low Four subscription events are documented with the newTransaction payload
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/ammorderupdate.md:30
From the report
Evidence
type: 'ammOrderUpdate',
data: { tx: { tx: [Object], mint: [Object], instr: null } }
Why it matters

ammOrderUpdate, ammOrderUpdateAll, tcompBidUpdate and tcompBidUpdateAll all show the same transaction-shaped payload, with nested objects elided as [Object]. Pool and bid updates are not transaction records, so nobody can write a parser for these events from the docs.

Suggested fix, not tested

document the real payload of each event with its fields expanded.

Review: Confirmed: ammorderupdate.md:29-30, ammorderupdateall.md, tcompbidupdate.md, tcompbidupdateall.md all show data: { tx: { tx: [Object], mint: [Object], instr: null } }. Copy-paste placeholder; a developer sees the real payload on the first message. Report cites :30, the type: line is :29.

20. Low Lamport amounts typed as 32-bit integers cap prices at about 2.15 SOL
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/tammcreateordertx.md:213
From the report
Evidence
"name": "startingPrice",
...
"format": "int32",
Why it matters

the unit is lamports (line 208), and int32 tops out at 2,147,483,647 lamports. A pool starting price, delta (line 224) or depositLamports (line 282) above about 2.15 SOL cannot be expressed under the published contract, and a client using 32-bit integers would wrap silently. reference/tammdepositwithdrawsoltx.md:168 and reference/tammeditpooltx.md have the same type.

Suggested fix, not tested

type lamport amounts as 64-bit integers or decimal strings.

Review: Confirmed: startingPrice :210-213, delta :224, depositLamports :282, and tammdepositwithdrawsoltx.md:165-168, tammeditpooltx.md:169,180. Spec defect, whether the server enforces it is unknown.

21. Low Deposit and withdraw endpoints contradict their own parameter contracts
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/depositwithdrawescrowntx-1.md:101
From the report
Evidence
"description": "The amount of SOL to deposit/withdraw",
"in": "query",
"name": "lamports",
Why it matters

the parameter is named lamports but described as SOL, a factor of one billion apart, on an endpoint that moves funds. The same is true in reference/tswapdepositwithdrawsoltx-1.md:163-165, while the AMM twin says lamports. The action descriptions say "deposit" or "withdraw" in lower case (escrow line 83, TSwap and AMM SOL pages line 145), but the enum accepts only DEPOSIT and WITHDRAW (line 20 / 80), so following the description gets a validation error.

Suggested fix, not tested

state one unit and make the name match it, and make the descriptions quote the enum values exactly.

Review: Confirmed: lamports described "amount of SOL" in depositwithdrawescrowntx-1.md:101-103 and tswapdepositwithdrawsoltx-1.md:163-165, while tammdepositwithdrawsoltx.md:163 says lamports. The unit error that would hurt is reading lamports as SOL in the other direction; a reader following "SOL" and passing 1 deposits 1 lamport, which is harmless. Lower-case "deposit" in the text (:83, :145) vs upper-case enum is a visible 400.

22. Low The sell endpoint's minPrice is described as a maximum
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/selltx-1.md:165
From the report
Evidence
"description": "The maximum price that would be paid for the NFT",
"in": "query",
"name": "minPrice",
Why it matters

the text was copied from the buy endpoint. minPrice is the seller's protection against a bid being lowered before execution. A seller who follows the description and treats it as a cap may set it low and lose that protection.

Suggested fix, not tested

describe it as the minimum amount the seller will accept, and say whether creator royalties are subtracted, as the changelog requires.

Review: Confirmed: selltx-1.md:165 has the buy text under name: minPrice (:167). The name and the changelog ("subtract the expected creator royalties", price-endpoints-change.md:15) disambiguate. Copy-paste in a generated description.

23. Low Devnet-only endpoints declare the mainnet server
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/getbuy.md:161
From the report
Evidence
"url": "https://api.mainnet.tensordev.io/api/v1"
Why it matters

the page title is "Buy Transaction [Devnet Only]", and the same holds for getlist.md, getcancel.md, getedit.md, getapproveseat.md, getinitializemarket.md and getlistings.md. The interactive "Try it!" console and any generated client send these requests to the mainnet host, so a developer testing devnet trading is pointed at mainnet.

Suggested fix, not tested

give these pages the devnet server URL, or say clearly that they are not available.

Review: Confirmed in all seven pages (getbuy.md:161, getlist.md:171, getcancel.md:161, getedit.md:162, getapproveseat.md:160, getinitializemarket.md:199, getlistings.md:130); every title carries "[Devnet Only]". No devnet host appears anywhere in the audited copy, so the right replacement cannot be stated from here. A Try-it call needs a signed-in key and does not sign, so the effect is an error or a read.

24. Low OHLC response types prices as integers, and its parameter docs contradict themselves
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/get-ohlc.md:62
From the report
Evidence
"c": {
  "type": "array",
  "items": { "type": "integer",
Why it matters

the recipe's own output shows fractional SOL prices (close: 49.988...), so a client generated from the schema truncates or rejects them. The status field s has no documented values, so a "no data" reply cannot be told apart from data. resolution says minutes but lists 1D/1W/1M, and countback counts back "from from" while from is documented as unused.

Suggested fix, not tested

declare number types, list the values of s, and correct the parameter descriptions.

Review: Confirmed: all arrays typed integer with example 1 and s is "string" (placeholder output, :36), resolution "in minutes" lists 1D/1W/1M (:133), countback "starting from from" (:152) while from is "not used" (:144). Fractional prices are real: the recipe output has close: 49.98888... (calculate-collection-moving-average.md:94, a mean of 9 values that cannot all be integers). Read-only chart data.

25. Low The traits filter is typed as an array but described as a JSON string
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/getmintsbycollid.md:349
From the report
Evidence
"name": "traits",
...
"type": "array",

The description (line 347) says: "Format must match the following JSON string: {"trait_type": ["value1", "value2"]}".

Why it matters

a client generated from the spec sends traits=a&traits=b, not the JSON string the server expects. The same contradiction is in getactivelistingsschema.md:348, getcollectiontxhistory.md:296 and traitbidtx-1.md:231.

Suggested fix, not tested

declare the parameter as a string containing JSON, or define the array encoding the server accepts.

Review: Confirmed with the same shape in getactivelistingsschema.md:350, getcollectiontxhistory.md:298, traitbidtx-1.md:233 (the bid variant takes {"trait_type": "value"}). The prose and the example object describe the real format; only a generated client would follow the type. A wrong filter returns visibly wrong results or a 400.

26. Low The "safe" proof-length advice exceeds the limits stated above it
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: docs/max-proof-length-for-tensor-cnfts.md:28
From the report
Evidence
> ❗️ To be safe mint your trees with proofs no longer than 8, worst case 10
Why it matters

the TLDR (lines 17-20) and the table (lines 141-165) cap collection-wide bids at 5 with 4 creators and 8 with 1 creator. A creator who mints a tree at the advertised "worst case 10" gets NFTs that cannot be traded through collection bids, and fixing that means reminting the tree.

Suggested fix, not tested

make the safe maximum match the table (5 for the worst case), or say which instruction the 10 applies to.

Review: Confirmed inconsistency: :28 says "no longer than 8, worst case 10", the TLDR (:17-19) and table (:147-155) cap collection bids at 5 (4 creators) and 8 (1 creator). It is design advice for one niche (compressed NFTs) and bites only collection-wide bids; list/buy limits are 14 to 17. The right numbers are in the TLDR directly above. Reminting cost makes it worth a fix, not MEDIUM.

Excluded on review (1)

Findings the review showed to be wrong or a repeat of another finding. They are not counted above.

Want this for your code?

Upload a ZIP or link a public GitHub repo, pick the areas and the depth, and get findings by severity with fixes.