Sample audits · Workstation audits, October 2026

Solana Mobile developer documentation

A fresh audit of the current Solana Mobile developer documentation.

Auditeddocs.solanamobile.com, 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: Solana Mobile documentation and SDKs (June 2026) · Solana Mobile stack, third audit (September 2026)

0
High after review
2
Medium after review
66
Low after review
8
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. 27 findings were first rated Medium or High; 2 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 (2)

3. Medium React Native quickstart sign-in has the same unverified, nonce-less flow
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: get-started/react-native/quickstart.md:122 (same text in _llms-full.txt:4116)
From the report
Evidence
const result = await signIn({ address: account?.address, chainId: chain,
  domain: "your-app-domain.com", statement: "Sign in to Your App", uri: identity.uri });
console.log("Signed in:", result.account.address);
Why it matters

this is the most-read entry point for React Native. It promises ownership verification but builds the payload on the client and only logs the result.

Suggested fix, not tested

take the payload (nonce, issuedAt, expirationTime) from the backend and send the result back for verification. Link to the server flow in the Seeker detection recipe.

Review: get-started/react-native/quickstart.md:111 says "Use signIn to connect to a wallet and verify ownership in a single step", and the sample at :122-131 builds {address, chainId, domain, statement, uri} on the client, awaits the result and only does console.log("Signed in:", ...). No nonce, issuedAt, expirationTime or verification step appears on the page (grep for nonce/verify finds only the signMessages text), and the "Next steps" cards at :250-257 do not point to the server flow. The correct pattern exists only in recipes/general/detecting-seeker-users.md:117-190 and as a warning at invoke-mwa-sessions-directly.md:373-375. The Kotlin quickstart (get-started/kotlin/quickstart.md:90,104-105, finding 2) repeats the claim and the sample. This is the entry-point sample for new apps, the promise ("verify ownership") is false as written, and a copy-paster ships authentication that proves nothing with no signal. It is guidance, not a bug, so MEDIUM and not HIGH. The Kotlin reference page (finding 1) stays LOW because it has its own "Verifying the sign-in result" section.

70. Medium Signing guides put keystore passwords into build.gradle
First rating: Medium · Reviewed rating: Medium · Review: confirmed
Location: dapp-store/build-and-sign-an-apk.md:121 (also dapp-store/publishing-from-google-play.md:68-78 and recipes/general/publishing-from-google-play.md:114-122)
From the report
Evidence
storePassword "your_keystore_password"
keyAlias "my-app-name"
keyPassword "your_key_password"
Why it matters

build.gradle is normally committed. Following the sample puts release-signing passwords into source control, and the dApp Store key cannot be rotated for a published app. Only the credentials.json variants carry a do-not-commit warning.

Suggested fix, not tested

read the passwords from environment variables or an untracked gradle.properties, and add the warning.

Review: dapp-store/build-and-sign-an-apk.md:116-124 puts storePassword "..." and keyPassword "..." in app/build.gradle, and publishing-from-google-play.md:62-79 and recipes/general/publishing-from-google-play.md:114-122 do the same for two keystores. build.gradle is normally committed, and the sample also points at keystores/*.keystore inside the project. The only commit warning in these pages is for the credentials.json variants (publishing-from-google-play.md:151, recipes/.../publishing-from-google-play.md:69), which shows the authors know the risk; the Gradle variants carry none, and build-and-sign-an-apk.md:37 stresses that all updates must use the same key. The values are placeholders and the failure is a late compromise with no signal, so MEDIUM is fair but it is a security-hygiene omission, not a bug.

Low after review (66)

1. Low Kotlin sign-in sample has no server challenge, and its verification advice skips nonce and domain
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: android-native/using_mobile_wallet_adapter.md:166
From the report
Evidence
SignInWithSolana.Payload("solana.com", "Sign in to Ktx Sample App")
Why it matters

the payload is built on the client with no nonce, issue time or expiry, and the domain is not the app's own. The verification advice at line 188 says only "verify that the message was correctly signed". A backend that follows it accepts a replayed or foreign sign-in result as proof of wallet ownership.

Suggested fix, not tested

fetch the payload from the backend with a single-use nonce, issuedAt and expirationTime. Verify the result server-side, including the domain and nonce consumption. Copy the production warning that the React Native page has.

Review: Payload is SignInWithSolana.Payload("solana.com", "Sign in to Ktx Sample App") (real). But the next section (:179-188) is titled "Verifying the sign-in result" and links a Kotlin and a server-side example. Reference page, not a "you are signed in" claim.

4. Low Web sign-in example builds a replayable input and discards the output
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/web/ux-guidelines.md:91
From the report
Evidence
const input: SolanaSignInInput = { domain: window.location.host,
    statement: "Sign in to My Web App", uri: window.location.origin, }
const output = await signIn(input);
Why it matters

the input carries no nonce or expiry, and the output is never verified. The page is about UX, but developers copy the snippet as is.

Suggested fix, not tested

obtain the nonce and expiry from the server and post the output back for verification.

Review: const output = await signIn(input) at :96; no nonce.

5. Low Direct-session sign-in sample uses a static payload and a client-side check
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/invoke-mwa-sessions-directly.md:312
From the report
Evidence
sign_in_payload: { domain: "yourdomain.com",
  statement: "Sign into React Native Sample App", uri: "https://yourdomain.com", },
Why it matters

the warning at line 373 is correct but comes after the code. verifySIWS checks the signature only against the input the caller supplies, which is not an authentication check.

Suggested fix, not tested

put a placeholder such as await fetchSignInPayloadFromBackend() in the sample, and label verifySIWS as an illustration that does not replace server verification.

Review: Production warning exists at :373-375 (after the code, as stated).

6. Low Server verifier consumes the nonce before checking the signature
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/general/detecting-seeker-users.md:217
From the report
Evidence
const signInPayload = await consumeSignInPayload(nonce);
if (!signInPayload) { return null; }
Why it matters

a request that carries a valid nonce and a bad signature burns the nonce, so the real user's sign-in then fails and has to restart.

Suggested fix, not tested

look up the payload without consuming it, verify the signature and domain, then mark the nonce used atomically.

Review: As quoted. Burn-on-first-use is also a defensible choice (shape is checked first at :206-211).

8. Low signTransactions is deprecated on one page and offered as a normal option on others
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/invoke-mwa-sessions-directly.md:499
From the report
Evidence
Alternatively, you can request the wallet to just sign a transaction by issuing a `signTransactions` request.
Why it matters

android-native/using_mobile_wallet_adapter.md:331 marks the method deprecated under the current protocol version and recommends signAndSendTransactions. The React Native guide and its reference (get-started/react-native/mobile-wallet-adapter.md:480) carry no such note.

Suggested fix, not tested

add the same deprecation warning to the React Native pages.

9. Low Current-version reference cites methods it does not document
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/mobile-wallet-adapter.md:307
From the report
Evidence
If, during the current session, the specified auth token was returned by the most recent call to authorize or reauthorize, ...
Why it matters

reauthorize and clone_authorization (lines 214, 313) are legacy methods. The page documents silent reauthorization as authorize with auth_token, so readers look for methods that are not there.

Suggested fix, not tested

remove the legacy method names or mark them legacy-only.

Review: reauthorize / clone_authorization at :213,312.

10. Low Legacy adapter deprecation has no timeline
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/mobile-wallet-adapter/migrating-to-wallet-standard.md:98
From the report
Evidence
The legacy `@solana-mobile/wallet-adapter-mobile` library will be deprecated and enter maintenance mode, and only receive updates for bug fixes.
Why it matters

integrators cannot plan the migration. Other pages still document the legacy API without a pointer to this notice.

Suggested fix, not tested

give dates or version gates for maintenance mode and end of support, and link this notice from the legacy reference.

Review: Line as quoted.

12. Low Quickstart handlers ignore wallet rejection
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/quickstart.md:75
From the report
Evidence
const signature = await signMessages(messageBytes);
console.log("Signature:", signature);
Why it matters

a user cancel becomes an unhandled promise rejection in every quickstart handler (connect, sign, sign in, send).

Suggested fix, not tested

wrap the calls in try/catch and handle the declined case explicitly.

Review: await signMessages(...) then console.log with no try/catch.

13. Low Seeker Connect example calls connect() on a wallet that may not exist
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: solana-mobile-stack/seeker-connect-quickstart.md:180
From the report
Evidence
const { accounts } = await wallet.features[StandardConnect].connect();
console.log("connected as", accounts[0]?.address);
Why it matters

wallet comes from a find() that can return undefined, and a cancelled consent throws.

Suggested fix, not tested

guard for a missing wallet, catch rejection, and require at least one account.

Review: find() at :165-168 can return undefined.

14. Low SGT check reports lookup failures as "no token"
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: solana-mobile-stack/seeker-genesis-token.md:218
From the report
Evidence
} catch (error) {
  console.error("Error verifying SGT ownership:", error.message);
  return null;
Why it matters

an RPC outage looks exactly like a user who owns no Seeker, so callers deny genuine users with no chance to retry.

Suggested fix, not tested

rethrow, or return a distinct error result.

Review: catch returns null.

15. Low The same Kotlin library is pinned to different versions on different pages
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: android-native/rpc-requests.md:27
From the report
Evidence
implementation("com.solanamobile:rpc-core:0.2.6")
Why it matters

get-started/kotlin/installation.md:32 pins 0.2.7, and two other pages use an undefined ${versions.KOTLIN_RPC_CORE_VERSION} placeholder. Mixed versions of the same stack cause subtle API mismatches.

Suggested fix, not tested

publish one tested version set and reference it from every page.

Review: 0.2.6 here, 0.2.7 at get-started/kotlin/installation.md:32, placeholder at building-json-rpc-requests.md:31 and using-anchor-programs.md:32.

16. Low Minimum wallet-standard-mobile version differs between pages, and the install is unpinned
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/mobile-wallet-adapter/local-network-access.md:29
From the report
Evidence
Update `@solana-mobile/wallet-standard-mobile` to **v0.5.0 or later** to mitigate the issue.
Why it matters

dapp-store/build-and-sign-an-apk.md:171 and recipes/general/publishing-a-web-app.md:39 require v0.5.1. The install command at line 48 uses @latest.

Suggested fix, not tested

state one floor, or explain the browser case against the shell case, and pin the version.

Review: v0.5.0 here, v0.5.1 at build-and-sign-an-apk.md:171 and publishing-a-web-app.md:39; @latest at :48.

17. Low Two pages send React Native developers to different, incompatible adapter stacks
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: mobile-wallet-adapter/mobile-apps.md:38
From the report
Evidence
<Card horizontal title="React Native" icon="react" href="/get-started/react-native/invoke-mwa-sessions-directly" />
Why it matters

get-started/mobile-wallet-adapter.md:48 points to the Wallet UI hooks, while this page points to the low-level protocol. Readers end up mixing both in one app.

Suggested fix, not tested

point both pages to one recommended entry point, and say not to mix the two stacks.

18. Low Web page names a package scope the install pages never use
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/web/apps.md:25
From the report
Evidence
It is compatible with any web app using the `@anza/wallet-adapter` libraries.
Why it matters

every sample imports @solana/wallet-adapter-*, and AI tools copy the wrong scope into install commands.

Suggested fix, not tested

use the real package scope.

19. Low HTTPS origin requirement for mobile-web wallets is not documented
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/web/installation.md:49
From the report
Evidence
registerMwa({ appIdentity: { name: "My app", uri: "https://myapp.io",
Why it matters

wallets reject non-HTTPS origins other than localhost without a clear error. We searched the web pages and found no mention of the requirement.

Suggested fix, not tested

state that the web app must be served over HTTPS (localhost only for development), and show a protocol check with a clear error.

Review: No HTTPS-requirement text in get-started/web/*. Whether wallets reject non-HTTPS cannot be checked from the docs.

22. Low Helius API key travels in the URL, and the script is not marked server-only
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: solana-mobile-stack/seeker-genesis-token.md:85
From the report
Evidence
const HELIUS_RPC_URL = `https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}`;
Why it matters

the Seeker detection recipe says this runs on the backend, but this page does not. Copied into an app bundle, the key leaks. HELIUS_API_KEY is never declared.

Suggested fix, not tested

mark the script backend-only and read the key from server configuration.

Review: HELIUS_API_KEY is used at :85 and never declared; the page never says "backend" (grep finds none).

23. Low Disconnect wipes all of the app's stored data
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:352
From the report
Evidence
await wallet.deauthorize({ auth_token: currentAccount.authToken });
AsyncStorage.clear();
Why it matters

AsyncStorage.clear() deletes every key the app ever stored, including user settings and other libraries' caches, not just the two authorization keys. A developer who copies this sample loses user data every time a user disconnects.

Suggested fix, not tested

await AsyncStorage.multiRemove(["authToken", "base64Address"]).

Review: 1: see 3; the page has a verification section and links. 23: AsyncStorage.clear() at caching-wallet-authorization.md:352 really wipes every key, but it sits in an advanced accordion for the bare library (:275), the neighbouring lines call deauthorize and setCurrentAccount(null) correctly, and a one-line replacement is obvious on first test. 24: the boot code at :295-301 does set the cached account as current, and the connect handler (:322-327) does not pass auth_token, so a revoked app still looks connected; the next sign request fails visibly and the cache is overwritten on the next authorize. Staleness, not a silent loss. 58: the page's own AsyncStorage warning (:32-38) sits above and the secure-store cache is the page's main recommendation (:126-267); the sample is the unencrypted bare-library illustration the warning already covers.

24. Low Cached authorization is shown as connected without revalidation
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:295
From the report
Evidence
if (cachedBase64Address && cachedAuthToken) {
  const pubkeyAsByteArray = toByteArray(cachedBase64Address);
Why it matters

on boot the cached account becomes the connected account with no reauthorization, and the connect handler never passes the cached token to authorize. After the user revokes access in the wallet, the app still shows them as connected, and the cache never takes up a refreshed token.

Suggested fix, not tested

reauthorize with the cached auth_token inside the first session. On failure clear the cache, and on success store the returned token and accounts.

Review: 1: see 3; the page has a verification section and links. 23: AsyncStorage.clear() at caching-wallet-authorization.md:352 really wipes every key, but it sits in an advanced accordion for the bare library (:275), the neighbouring lines call deauthorize and setCurrentAccount(null) correctly, and a one-line replacement is obvious on first test. 24: the boot code at :295-301 does set the cached account as current, and the connect handler (:322-327) does not pass auth_token, so a revoked app still looks connected; the next sign request fails visibly and the cache is overwritten on the next authorize. Staleness, not a silent loss. 58: the page's own AsyncStorage warning (:32-38) sits above and the secure-store cache is the page's main recommendation (:126-267); the sample is the unencrypted bare-library illustration the warning already covers.

25. Low Stored copy and in-memory state are written separately and not awaited
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:329
From the report
Evidence
AsyncStorage.setItem("authToken", auth_token);
AsyncStorage.setItem("base64Address", firstAccount.address);
Why it matters

the two keys form a pair, but they are written by two unawaited calls and then state is set. A failure in between leaves storage and memory out of step.

Suggested fix, not tested

use one saveAccount() and one clearAccount() helper that awaits a single multiSet (or one JSON value) and then sets state.

Review: Two un-awaited setItem calls at :329-330. Same lines as 58.

26. Low Generic, unprefixed storage keys in shared app storage
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:136
From the report
Evidence
const STORAGE_KEY = "authorization-cache";
Why it matters

"authToken" and "authorization-cache" live in storage the whole app and other libraries share, which invites collisions.

Suggested fix, not tested

prefix the keys with an owner name, such as <app>_wallet_authorization.

Review: STORAGE_KEY = "authorization-cache" is in the SecureStore cache, which is app-scoped; collision risk is low. A nit.

27. Low Cache key ignores the chain and app identity the token was issued for
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:136
From the report
Evidence
const STORAGE_KEY = "authorization-cache";
Why it matters

an authorization is issued per chain and identity. When a build switches from devnet to mainnet or changes identity, the old authorization is served as current.

Suggested fix, not tested

include the chain and identity.uri in the key, or store them in the value and compare them on read.

Review: Same line as 26.

28. Low Reauthorization sample reads a stored token but never stores the new one
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/invoke-mwa-sessions-directly.md:224
From the report
Evidence
const authorizationResult = await wallet.authorize({
  auth_token: storedAuthToken ? storedAuthToken : undefined,
Why it matters

the token that authorize returns can change, and nothing writes it back, so the next launch reuses a stale token.

Suggested fix, not tested

show persistAuthToken(authorizationResult.auth_token) next to the read, and what to do when reauthorization fails.

Review: Sample ends with "Rest of transact code goes below..." (:230).

29. Low Deauthorize sample leaves the persisted token in place
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/invoke-mwa-sessions-directly.md:278
From the report
Evidence
// Pass in the prior auth token to invalidate it.
await wallet.deauthorize({ auth_token: previouslyStoredAuthToken });
Why it matters

the wallet invalidates the token but the app's stored copy survives, so the next launch sends a dead token.

Suggested fix, not tested

clear the stored token in the same step as the deauthorize call.

Review: As quoted.

30. Low Kotlin pages restore a persisted auth token but show no save or clear path
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: android-native/using_mobile_wallet_adapter.md:73 (also get-started/kotlin/setup.md:91)
From the report
Evidence
val previouslyStoredAuthToken = maybeGetStoredAuthToken()
walletAdapter.authToken = previouslyStoredAuthToken
Why it matters

only the restore half is shown. Nothing persists the token after connect, and disconnect never clears the stored copy, so an invalidated token comes back on the next launch.

Suggested fix, not tested

add a saveAuthToken() and clearAuthToken() pair backed by encrypted storage, and call it on connect, on token change and on disconnect.

Review: Also get-started/kotlin/setup.md:91; text at :68 says the client stores the token itself.

31. Low Seeker Connect and web authorization caches have no documented clear path
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: solana-mobile-stack/seeker-connect-quickstart.md:186
From the report
Evidence
"Connected" means your app holds a wallet-issued authorization token ... silently reauthorizes with the cached token
Why it matters

we searched the Seeker Connect and web pages for disconnect, deauthorize and clear, and found nothing. Readers do not know where the token lives or how to revoke it.

Suggested fix, not tested

document where the token is cached, how disconnect clears it, and what happens when it is invalid.

Review: grep for disconnect/deauthorize/clear/revoke in seeker-connect*.md and get-started/web/*.md finds nothing. True.

32. Low Confirmation polling has no interval, bound or backoff
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/solana-development/anchor-integration.md:257
From the report
Evidence
the hook polls for confirmation afterwards and invalidates the counter query on settle
Why it matters

polling with no interval, timeout or backoff can hammer a public RPC endpoint. A subscription is available.

Suggested fix, not tested

prefer a signature subscription, and give the poll interval, upper bound and backoff.

Review: Prose at :257.

33. Low SGT pagination loop has no page or time budget
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: solana-mobile-stack/seeker-genesis-token.md:154
From the report
Evidence
} while (paginationKey); // Continue until no more pages
Why it matters

the caller supplies the wallet address, so a wallet with very many token accounts keeps one backend request looping for a long time.

Suggested fix, not tested

cap the page count and the elapsed time, and fail when either cap is reached.

Review: while (paginationKey) at :154.

34. Low Reward-claim checks are described as something "the app" does
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: solana-mobile-stack/seeker-genesis-token.md:38
From the report
Evidence
To implement this, the app should check for 3 properties:
Why it matters

an eligibility check and seen-before ledger that run on the client can be bypassed.

Suggested fix, not tested

state that all three checks and the mint ledger run on the server.

Review: "the app should check for 3 properties".

35. Low Recording a claimed SGT mint is not atomic
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/general/detecting-seeker-users.md:279
From the report
Evidence
// Store the mint address to enforce uniqueness across transfers.
return await checkWalletForSGT(walletAddress);
Why it matters

when the check and the record are separate steps, two concurrent claims with the same token can both pass.

Suggested fix, not tested

show an atomic insert-if-absent keyed on the mint address, for example a unique constraint.

Review: Comment "Store the mint address..." with no insert-if-absent shown.

36. Low Send and connect buttons have no in-flight guard
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/quickstart.md:236 (also the connect button at line 39)
From the report
Evidence
return <Button title="Send Transaction" onPress={handleSendTransaction} />;
Why it matters

a double tap can start two wallet sessions or two sends.

Suggested fix, not tested

disable the button or reuse the pending promise while a request is in flight.

Review: Also connect button at :39.

37. Low Quickstart says minContextSlot is required, but the reference sample omits it
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/mobile-wallet-adapter.md:431
From the report
Evidence
const transactionSignatures = await wallet.signAndSendTransactions({
  transactions: [transferTransactionMessage],
Why it matters

quickstart.md:243 states that signAndSendTransactions requires minContextSlot, without saying the warning applies only to the hook. Readers cannot tell which contract is right.

Suggested fix, not tested

align the two samples, or scope the warning to the hook.

Review: quickstart.md:243 says required; reference lists it as plain minContextSlot: number (:407); invoke-mwa-sessions-directly.md:467 also omits it.

38. Low Two different scaffolding commands for the same purpose
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/react-native/create-solana-mobile-app.md:30
From the report
Evidence
npm create solana-dapp@latest
Why it matters

recipes/solana-development/anchor-integration.md:53 uses npx solana-mobile@latest create. Readers do not know which one is current.

Suggested fix, not tested

document one canonical command, or explain when to use each.

Review: npm create solana-dapp@latest vs npx solana-mobile@latest create at anchor-integration.md:53.

39. Low MWA compatibility page is out of date
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: mobile-wallet-adapter/mobile-apps.md:62
From the report
Evidence
| Mobile Web - Chrome (Android) | ✅ | Automatic integration if using `@solana/wallet-adapter-react`. |
Why it matters

recipes/mobile-wallet-adapter/migrating-to-wallet-standard.md:87-91 says @solana/wallet-adapter-react 1.0.0 and later no longer include MWA by default. A web developer who follows this page gets no mobile wallet option and no explanation. Line 70 also still lists Seed Vault Wallet as "Coming soon!".

Suggested fix, not tested

update the table to say that registerMwa is required, and refresh the wallet list.

Review: 39: the compat row (mobile-apps.md:62) is genuinely stale against migrating-to-wallet-standard.md:87-92 (and :70 "Coming soon!"), but the migration page explains it directly, the web installation page teaches registerMwa, and mobile-apps.md is itself missing from every index (finding 76). Visible stale statement, not a trap. 40: the confirmation at quickstart.md:228-231 indeed ignores value.err, but the log line is "Transaction signature:", which is not a success claim, and the wallet already returned a signature from signAndSendTransactions. invoke-mwa-sessions-directly.md:480 shows the stricter check.

40. Low Quickstart reports a sent transaction as successful without checking the confirmation
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/react-native/quickstart.md:228
From the report
Evidence
await connection.confirmTransaction(
  { signature, ...latestBlockhash }, "confirmed",
);
Why it matters

the confirmation's value.err is never read, so a transaction that failed on chain is logged as sent. invoke-mwa-sessions-directly.md:480 does check it.

Suggested fix, not tested

capture the result and treat value.err as a failure.

Review: 39: the compat row (mobile-apps.md:62) is genuinely stale against migrating-to-wallet-standard.md:87-92 (and :70 "Coming soon!"), but the migration page explains it directly, the web installation page teaches registerMwa, and mobile-apps.md is itself missing from every index (finding 76). Visible stale statement, not a trap. 40: the confirmation at quickstart.md:228-231 indeed ignores value.err, but the log line is "Transaction signature:", which is not a success claim, and the wallet already returned a signature from signAndSendTransactions. invoke-mwa-sessions-directly.md:480 shows the stricter check.

41. Low Update guide states its preconditions but gives no way to check them
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: dapp-store/submit-an-update.md:50
From the report
Evidence
* The APK is signed with the same Android signing key you used for your initial release.
* The `versionName` and `versionCode` value is properly incremented between each update.
Why it matters

submission uploads to Arweave and mints a release NFT before review. A wrong key or version code is found out late.

Suggested fix, not tested

add apksigner verify --print-certs with a fingerprint comparison, and a versionCode check.

Review: Bullets as quoted (:48-50).

42. Low Google Play guide's verify step names the wrong file and checks the wrong thing
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: dapp-store/publishing-from-google-play.md:183
From the report
Evidence
apksigner verify --print-certs app-release.apk
Why it matters

the build produces app-dappStore-release.apk. The page's critical requirement is a key that differs from the Google Play key, and printing certs does not check that.

Suggested fix, not tested

use the real path, and compare the certificate fingerprint against the Play signing certificate.

Review: Real: build output is app-dappStore-release.apk (:112), verify uses app-release.apk.

43. Low Web-shell update path skips the verify and test steps
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: dapp-store/build-and-sign-an-apk.md:265
From the report
Evidence
Every dApp Store update needs a higher `versionCode`. Bump `SOLANA_MOBILE_VERSION_CODE` ...
Why it matters

the first release has Verify and Test steps, but the update path ends at the build command.

Suggested fix, not tested

repeat the verify and test steps for updates, and add a check that the signing certificate matches the previous release.

Review: "Updating your app" section ends at the build command.

44. Low CI publishing recipe stops at the publish call
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: dapp-store/publishing-cli.md:82
From the report
Evidence
dapp-store \
  --apk-file ./app/build/outputs/apk/release/app-release.apk \
Why it matters

the pipeline has no pre-publish signature check and no read-back of the release status.

Suggested fix, not tested

add apksigner verify before publishing and a status check after.

Review: As quoted.

45. Low Remote APK is published with no integrity check
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: dapp-store/publishing-cli.md:64
From the report
Evidence
dapp-store --apk-url https://example.com/your_apk_name.apk --keypair ./path/to/keypair.json ...
Why it matters

an APK fetched by URL goes into an on-chain release with no hash or signature check described.

Suggested fix, not tested

document an expected-hash option or a verify step, or state plainly that the tool performs no check.

Review: As quoted.

47. Low Web-shell apps are not re-tested when the live site changes
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: dapp-store/build-and-sign-an-apk.md:271
From the report
Evidence
Because the shell loads your live site, changes you ship to the web app itself reach users without a new APK.
Why it matters

the verified APK is only part of what users run. A site release can break wallet flows inside the shell with no review step in between.

Suggested fix, not tested

recommend re-testing connect, sign and send inside the shell for every site release.

Review: Sentence as quoted.

48. Low Anchor guide gives no end-to-end re-check after the program changes
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/solana-development/anchor-integration.md:261
From the report
Evidence
Add an instruction to the program, then rebuild and regenerate:
Why it matters

changing the account layout breaks existing accounts, and nothing tells the reader to re-run the whole flow.

Suggested fix, not tested

add a re-test step (initialize, increment, read) and a note on account-layout migration.

Review: "Add an instruction to the program, then rebuild and regenerate".

52. Low The RPC blockhash snippet is copied six times and the copies have drifted
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/solana-development/using-anchor-programs.md:149
From the report
Evidence
.setRecentBlockhash(blockhashResponse.result!!.blockhash)
Why it matters

the same "create client, fetch blockhash" block appears in five Kotlin pages with three different result shapes and versions.

Suggested fix, not tested

define one canonical snippet with error handling and link to it.

Review: Actual count is 3 Kotlin pages / 4 setRecentBlockhash calls (kotlin/quickstart.md:158, using-anchor-programs.md:149,186) plus rpc-requests.md:61, not six copies in five pages.

53. Low The Google Play publishing guide exists as two drifting copies
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/general/publishing-from-google-play.md:5
From the report
Evidence
# Publishing from Google Play to the dApp Store
Why it matters

compared with dapp-store/publishing-from-google-play.md, this copy uses different keystore and alias names and different Gradle syntax, and only the other copy has a verify step.

Suggested fix, not tested

keep one page and link to it from the other.

Review: Two files, 172 vs 197 lines, diff differs from line 7.

54. Low Mock wallet is described differently on two pages, and setup omits the lock-screen requirement
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: get-started/development-setup.md:80
From the report
Evidence
it does not store a persistent keypair and the wallet is reset each time the app is exited.
Why it matters

recipes/general/test-with-any-android-device.md:34-38 describes key importing and a required secure lock screen. A developer who follows the setup page alone installs a wallet that fails without a lock screen.

Suggested fix, not tested

describe the wallet in one place, including the lock-screen requirement.

Review: Lock-screen warning is at recipes/general/test-with-any-android-device.md:34-38; setup page says "does not store a persistent keypair".

55. Low Release-notes index exists twice as identical empty stubs
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: seeker/release-notes.md:5
From the report
Evidence
# Seeker Release Notes
Why it matters

seeker/release-notes/index.md is identical, and neither lists MR4 to MR9.

Suggested fix, not tested

keep one index and link the releases from it.

Review: diff with seeker/release-notes/index.md shows them identical; MR4-MR9 pages exist in seeker/release-notes/.

56. Low Migration guide never tells the reader to remove the legacy adapter
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/mobile-wallet-adapter/migrating-to-wallet-standard.md:25
From the report
Evidence
### 1. Install Mobile Wallet Standard
Why it matters

the old and new registrations can coexist, so stale call sites keep working silently and the wallet may be listed twice.

Suggested fix, not tested

add a step to remove the legacy package and its imports, and a check that only one entry is listed.

57. Low Program deploy step is not verified
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: recipes/solana-development/anchor-integration.md:293
From the report
Evidence
npm run anchor:deploy:devnet
Why it matters

the funding step is checked, but the deploy is not, so a failed deploy surfaces later as a confusing app error.

Suggested fix, not tested

add solana program show <id> --url devnet, or an initialize-and-increment check, after the deploy.

Review: Funding step has a check at :281-285, deploy has none.

58. Low Bare-library sample stores the wallet auth token in plain AsyncStorage
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:329 (same text in _llms-full.txt:5714)
From the report
Evidence
AsyncStorage.setItem("authToken", auth_token);
AsyncStorage.setItem("base64Address", firstAccount.address);
Why it matters

the page's own warning (lines 32-38) says AsyncStorage is unencrypted and readable from backups. The copy-paste sample does exactly that with a bearer token that lets the wallet reauthorize silently.

Suggested fix, not tested

use the SecureStore cache from the same page, or repeat the warning inside the sample.

Review: 1: see 3; the page has a verification section and links. 23: AsyncStorage.clear() at caching-wallet-authorization.md:352 really wipes every key, but it sits in an advanced accordion for the bare library (:275), the neighbouring lines call deauthorize and setCurrentAccount(null) correctly, and a one-line replacement is obvious on first test. 24: the boot code at :295-301 does set the cached account as current, and the connect handler (:322-327) does not pass auth_token, so a revoked app still looks connected; the next sign request fails visibly and the cache is overwritten on the next authorize. Staleness, not a silent loss. 58: the page's own AsyncStorage warning (:32-38) sits above and the secure-store cache is the page's main recommendation (:126-267); the sample is the unencrypted bare-library illustration the warning already covers.

59. Low Web connect and sign-in samples do not compile
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/web/ux-guidelines.md:49 (also line 96)
From the report
Evidence
const handleConnectClick = () => {
    ... await connect();
} else if (mobileWalletAdapter) {
Why it matters

the samples use await in non-async handlers, and mobileWalletAdapter, select and SolanaSignInInput are never declared or imported. The recommended connect flow fails to build.

Suggested fix, not tested

make the handlers async, take select from useWallet(), find the MWA wallet in wallets, and import the type.

Review: All verified real and all fail at build or parse time, which is the best kind of failure. 59: await inside non-async handlers (ux-guidelines.md:45-49, :87-96), undeclared select, mobileWalletAdapter, SolanaSignInInput. 62: Base58.encodeToString(signedTxBytes) at using_mobile_wallet_adapter.md:272 while the lambda binds it; the sibling at kotlin/quickstart.md:170 is right. 64: rpc.sendTransaction (the client is rpcClient, :82) and if (response.result) on a nullable object (rpc-requests.md:63,90). 65: commitment undefined, block-body function without return (building-json-rpc-requests.md:139-160). 68: implementation('...${versions...}') in a .kts block (using-anchor-programs.md:33); the ${versions.*} placeholders are also undefined on that page. Each stops the developer immediately and carries no money or security effect.

60. Low signMessages reference reads a field that authorize does not return
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/react-native/mobile-wallet-adapter.md:592 (also line 615 and reference/typescript/mobile-wallet-adapter-legacy.md:440)
From the report
Evidence
addresses: [authorizationResult.address],
Why it matters

the documented result exposes accounts[].address, so the sample passes undefined as the signing address. authorizeSession is also undefined.

Suggested fix, not tested

use authorizationResult.accounts[0].address and define the helper.

Review: The claim that authorize returns no address is true of the documented result (:226), but the helper's return shape is unknown. 61: charCodeAt truncation is real (also :574, :609, legacy :435), but the message is the ASCII "Hello world!", and quickstart.md:99-103 tells readers to use TextEncoder. 63: blockhashResponse.result!! (kotlin/quickstart.md:158, using-anchor-programs.md:149,186) throws on a failed RPC call; the exception is visible and the transaction was never sent.

61. Low Message-to-bytes conversion corrupts non-ASCII text
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/react-native/mobile-wallet-adapter.md:586 (also invoke-mwa-sessions-directly.md:574 and the legacy page)
From the report
Evidence
const messageBuffer = new Uint8Array(
  message.split("").map((c) => c.charCodeAt(0)),
Why it matters

charCodeAt returns UTF-16 units, and Uint8Array truncates them, so any character above U+00FF changes the signed bytes. The quickstart's own note says to use TextEncoder.

Suggested fix, not tested

new TextEncoder().encode(message).

Review: The claim that authorize returns no address is true of the documented result (:226), but the helper's return shape is unknown. 61: charCodeAt truncation is real (also :574, :609, legacy :435), but the message is the ASCII "Hello world!", and quickstart.md:99-103 tells readers to use TextEncoder. 63: blockhashResponse.result!! (kotlin/quickstart.md:158, using-anchor-programs.md:149,186) throws on a failed RPC call; the exception is visible and the transaction was never sent.

62. Low Kotlin sign-and-send sample uses an undefined variable
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: android-native/using_mobile_wallet_adapter.md:272
From the report
Evidence
txSignatureBytes?.let {
    println("Transaction signature: " + Base58.encodeToString(signedTxBytes))
Why it matters

the main signing sample fails to compile.

Suggested fix, not tested

Base58.encodeToString(it), as get-started/kotlin/quickstart.md:170 does.

Review: All verified real and all fail at build or parse time, which is the best kind of failure. 59: await inside non-async handlers (ux-guidelines.md:45-49, :87-96), undeclared select, mobileWalletAdapter, SolanaSignInInput. 62: Base58.encodeToString(signedTxBytes) at using_mobile_wallet_adapter.md:272 while the lambda binds it; the sibling at kotlin/quickstart.md:170 is right. 64: rpc.sendTransaction (the client is rpcClient, :82) and if (response.result) on a nullable object (rpc-requests.md:63,90). 65: commitment undefined, block-body function without return (building-json-rpc-requests.md:139-160). 68: implementation('...${versions...}') in a .kts block (using-anchor-programs.md:33); the ${versions.*} placeholders are also undefined on that page. Each stops the developer immediately and carries no money or security effect.

63. Low Kotlin quickstart crashes when the blockhash request fails
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/kotlin/quickstart.md:158 (also recipes/solana-development/using-anchor-programs.md:149 and :186)
From the report
Evidence
.setRecentBlockhash(blockhashResponse.result!!.blockhash)
Why it matters

a rate limit or network error leaves result null, and !! throws in the middle of the open wallet session.

Suggested fix, not tested

check blockhashResponse.error, handle null, and fetch the blockhash before opening the session.

Review: The claim that authorize returns no address is true of the documented result (:226), but the helper's return shape is unknown. 61: charCodeAt truncation is real (also :574, :609, legacy :435), but the message is the ASCII "Hello world!", and quickstart.md:99-103 tells readers to use TextEncoder. 63: blockhashResponse.result!! (kotlin/quickstart.md:158, using-anchor-programs.md:149,186) throws on a failed RPC call; the exception is visible and the transaction was never sent.

64. Low Kotlin RPC samples do not compile
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: android-native/rpc-requests.md:88 (also line 63 and recipes/solana-development/using-anchor-programs.md:209)
From the report
Evidence
val response = rpc.sendTransaction(transaction)
if (response.result) {
Why it matters

nullable objects are used as Boolean conditions, and the client is declared as rpcClient, not rpc.

Suggested fix, not tested

use rpcClient, and test response.result != null and response.error != null.

Review: All verified real and all fail at build or parse time, which is the best kind of failure. 59: await inside non-async handlers (ux-guidelines.md:45-49, :87-96), undeclared select, mobileWalletAdapter, SolanaSignInInput. 62: Base58.encodeToString(signedTxBytes) at using_mobile_wallet_adapter.md:272 while the lambda binds it; the sibling at kotlin/quickstart.md:170 is right. 64: rpc.sendTransaction (the client is rpcClient, :82) and if (response.result) on a nullable object (rpc-requests.md:63,90). 65: commitment undefined, block-body function without return (building-json-rpc-requests.md:139-160). 68: implementation('...${versions...}') in a .kts block (using-anchor-programs.md:33); the ${versions.*} placeholders are also undefined on that page. Each stops the developer immediately and carries no money or security effect.

65. Low JSON-RPC deep-dive function does not compile
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: android-native/building-json-rpc-requests.md:145
From the report
Evidence
val request = createBlockhashRequest(commitment, requestId)
Why it matters

commitment is undefined, the block-bodied function has no return, and it calls a suspending request from a non-suspending function.

Suggested fix, not tested

add a commitment parameter, mark the function suspend, and return the value.

Review: All verified real and all fail at build or parse time, which is the best kind of failure. 59: await inside non-async handlers (ux-guidelines.md:45-49, :87-96), undeclared select, mobileWalletAdapter, SolanaSignInInput. 62: Base58.encodeToString(signedTxBytes) at using_mobile_wallet_adapter.md:272 while the lambda binds it; the sibling at kotlin/quickstart.md:170 is right. 64: rpc.sendTransaction (the client is rpcClient, :82) and if (response.result) on a nullable object (rpc-requests.md:63,90). 65: commitment undefined, block-body function without return (building-json-rpc-requests.md:139-160). 68: implementation('...${versions...}') in a .kts block (using-anchor-programs.md:33); the ${versions.*} placeholders are also undefined on that page. Each stops the developer immediately and carries no money or security effect.

66. Low Legacy TypeScript reference samples do not parse
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: reference/typescript/mobile-wallet-adapter-legacy.md:428 (also lines 187-194 and 318)
From the report
Evidence
const result = return await transact(async (wallet: Web3MobileWallet) => {
Why it matters

the samples contain const x = return, a missing comma, an unbalanced }));, an unawaited authorize, and a cluster field the page does not document.

Suggested fix, not tested

correct the syntax, await authorize, and use the documented chain parameter.

Review: All verified real and all fail at build or parse time, which is the best kind of failure. 59: await inside non-async handlers (ux-guidelines.md:45-49, :87-96), undeclared select, mobileWalletAdapter, SolanaSignInInput. 62: Base58.encodeToString(signedTxBytes) at using_mobile_wallet_adapter.md:272 while the lambda binds it; the sibling at kotlin/quickstart.md:170 is right. 64: rpc.sendTransaction (the client is rpcClient, :82) and if (response.result) on a nullable object (rpc-requests.md:63,90). 65: commitment undefined, block-body function without return (building-json-rpc-requests.md:139-160). 68: implementation('...${versions...}') in a .kts block (using-anchor-programs.md:33); the ${versions.*} placeholders are also undefined on that page. Each stops the developer immediately and carries no money or security effect.

67. Low Anchor Kotlin guide encodes a u64 argument as 32 bits
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/solana-development/using-anchor-programs.md:94
From the report
Evidence
class Args_increment(val amount: UInt)
Why it matters

line 57 says the instruction takes amount: u64. UInt serialises as 4 bytes, so the program receives malformed data. amount is also undefined, while line 143 declares an unused incrementAmount.

Suggested fix, not tested

use ULong, and pass the variable the sample defines.

Review: 67: class Args_increment(val amount: UInt) (using-anchor-programs.md:94) against amount: u64 (:57) is a real width bug (and amount is undefined at :102, while incrementAmount at :143 is unused). The wrong-size instruction data makes the program reject the call with a deserialization error on devnet, so nothing is spent or lost. 69: the only account is the non-signer counter PDA (:124), so the message has no fee payer and the "sign with a keypair" branch (:190) signs with an unbound signer. Real, and the transaction cannot be sent, so it fails before reaching the network.

68. Low Gradle Kotlin DSL dependency uses single quotes and does not compile
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/solana-development/using-anchor-programs.md:33
From the report
Evidence
implementation('io.github.funkatronics:kborsh:${versions.KOTLIN_KBORSH_VERSION}')
Why it matters

in build.gradle.kts, single quotes are a Char literal, so the dependency block fails to build.

Suggested fix, not tested

use double quotes and a concrete, tested version.

Review: All verified real and all fail at build or parse time, which is the best kind of failure. 59: await inside non-async handlers (ux-guidelines.md:45-49, :87-96), undeclared select, mobileWalletAdapter, SolanaSignInInput. 62: Base58.encodeToString(signedTxBytes) at using_mobile_wallet_adapter.md:272 while the lambda binds it; the sibling at kotlin/quickstart.md:170 is right. 64: rpc.sendTransaction (the client is rpcClient, :82) and if (response.result) on a nullable object (rpc-requests.md:63,90). 65: commitment undefined, block-body function without return (building-json-rpc-requests.md:139-160). 68: implementation('...${versions...}') in a .kts block (using-anchor-programs.md:33); the ${versions.*} placeholders are also undefined on that page. Each stops the developer immediately and carries no money or security effect.

69. Low Anchor Kotlin transaction has no fee payer or signer
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/solana-development/using-anchor-programs.md:145
From the report
Evidence
Message.Builder()
    .addInstruction(
        incrementInstruction
Why it matters

the only account is the non-signer counter address, so the message has no fee payer. Line 158 says the fee payer must sign, and the keypair path signs with an arbitrary signer that is not bound to any account in the message. The transaction cannot be submitted as built.

Suggested fix, not tested

add the user's wallet as fee payer and signer, and sign with that same account.

Review: 67: class Args_increment(val amount: UInt) (using-anchor-programs.md:94) against amount: u64 (:57) is a real width bug (and amount is undefined at :102, while incrementAmount at :143 is unused). The wrong-size instruction data makes the program reject the call with a deserialization error on devnet, so nothing is spent or lost. 69: the only account is the non-signer counter PDA (:124), so the message has no fee payer and the "sign with a keypair" branch (:190) signs with an unbound signer. Real, and the transaction cannot be sent, so it fails before reaching the network.

71. Low Expo instructions can reuse the Google Play key the same page forbids
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: dapp-store/build-and-sign-an-apk.md:62
From the report
Evidence
For signing, EAS can automatically generate and manage a keystore for you on your first build.
Why it matters

line 40 says a separate key is mandatory for apps also on Google Play. A project already built for Play with EAS reuses that project keystore, and the submission is rejected.

Suggested fix, not tested

say that such projects must use local credentials with a dedicated keystore for the dApp Store profile, as the Google Play guide does.

Review: 71: build-and-sign-an-apk.md:62 offers EAS-managed keystores in the Expo tab, while the Warning at :39-42 (same page, above the tabs) says a separate key is mandatory for apps also on Google Play. Inconsistent wording, with the rule stated on the page; and the Google Play guide covers the Expo case with local credentials. 72: publishing-cli.md:77-86 treats the API key as the secret and the keypair as a path (./path/to/keypair.json). It is a docs omission about a publisher keypair that also needs the portal API key to act, and ${{ secrets.* }} is GitHub Actions syntax shown in a bash block (cosmetic).

72. Low CI recipe handles the publisher signing keypair as a plain file
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: dapp-store/publishing-cli.md:78
From the report
Evidence
secret in your CI environment and provide the signer keypair file via `--keypair`:
Why it matters

only the API key is treated as a secret. The keypair controls publishing for the app, and a repository path like ./path/to/keypair.json invites committing it. The ${{ secrets... }} expression also only works inside a workflow file, not in plain bash.

Suggested fix, not tested

document injecting the keypair from a secret store into a temporary file that is deleted afterwards, preferably for a dedicated publisher wallet.

Review: 71: build-and-sign-an-apk.md:62 offers EAS-managed keystores in the Expo tab, while the Warning at :39-42 (same page, above the tabs) says a separate key is mandatory for apps also on Google Play. Inconsistent wording, with the rule stated on the page; and the Google Play guide covers the Expo case with local credentials. 72: publishing-cli.md:77-86 treats the API key as the secret and the keypair as a path (./path/to/keypair.json). It is a docs omission about a publisher keypair that also needs the portal API key to act, and ${{ secrets.* }} is GitHub Actions syntax shown in a bash block (cosmetic).

73. Low Direct-session send sample fetches the blockhash too early and uses an undefined recipient
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/react-native/invoke-mwa-sessions-directly.md:435
From the report
Evidence
const latestBlockhash = await connection.getLatestBlockhash();
const txSignature = await transact(async (wallet: Web3MobileWallet) => {
Why it matters

the blockhash is fetched before the user sees the wallet, so a slow approval expires it. toPublicKey is never defined, and confirmation by signature alone cannot detect expiry.

Suggested fix, not tested

fetch the blockhash with its context inside the session, pass minContextSlot, define the recipient, and confirm with the blockhash strategy.

Review: 73: the blockhash is fetched before transact (invoke-mwa-sessions-directly.md:435) and toPublicKey is never defined (:452); minContextSlot is omitted (:467), contradicting quickstart.md:243. Real, but a slow approval is needed to hit it and the result is a visible failure. 74: the kit signTransactions sample (:511-518) has no authorize first, which the web3.js tab does via "transaction code from above" (:540); the wallet will refuse the privileged call, visibly, and the method is itself deprecated. 75: the bare-library connect handler (caching-wallet-authorization.md:324-326) has identity: {name} only; setup.md:104 says every field is optional in the protocol and only "recommends" a wallet decline when uri is missing; the other samples on the page set uri (:257-259).

74. Low Kit signTransactions sample calls a privileged method without authorizing
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: get-started/react-native/invoke-mwa-sessions-directly.md:511
From the report
Evidence
const signedTx = await transact(async (wallet: KitMobileWallet) => {
  // Request to sign the transaction.
  const signedTxs = await wallet.signTransactions({
Why it matters

signing is privileged and needs authorize first in the session. As written, the wallet rejects the request.

Suggested fix, not tested

call authorize (or reauthorize with a stored token) before signTransactions, as the web3.js tab does.

Review: 73: the blockhash is fetched before transact (invoke-mwa-sessions-directly.md:435) and toPublicKey is never defined (:452); minContextSlot is omitted (:467), contradicting quickstart.md:243. Real, but a slow approval is needed to hit it and the result is a visible failure. 74: the kit signTransactions sample (:511-518) has no authorize first, which the web3.js tab does via "transaction code from above" (:540); the wallet will refuse the privileged call, visibly, and the method is itself deprecated. 75: the bare-library connect handler (caching-wallet-authorization.md:324-326) has identity: {name} only; setup.md:104 says every field is optional in the protocol and only "recommends" a wallet decline when uri is missing; the other samples on the page set uri (:257-259).

75. Low Caching sample authorizes without an identity URI
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: recipes/mobile-wallet-adapter/caching-wallet-authorization.md:324
From the report
Evidence
identity: {
  name: "My amazing app",
},
Why it matters

get-started/react-native/setup.md:104 says the protocol specification recommends that wallets decline authorization when the identity has no uri. Copied as is, the connect flow can fail on such wallets.

Suggested fix, not tested

include an absolute uri and an icon, as the other samples do.

Review: 73: the blockhash is fetched before transact (invoke-mwa-sessions-directly.md:435) and toPublicKey is never defined (:452); minContextSlot is omitted (:467), contradicting quickstart.md:243. Real, but a slow approval is needed to hit it and the result is a visible failure. 74: the kit signTransactions sample (:511-518) has no authorize first, which the web3.js tab does via "transaction code from above" (:540); the wallet will refuse the privileged call, visibly, and the method is itself deprecated. 75: the bare-library connect handler (caching-wallet-authorization.md:324-326) has identity: {name} only; setup.md:104 says every field is optional in the protocol and only "recommends" a wallet decline when uri is missing; the other samples on the page set uri (:257-259).

76. Low AI index and sitemap omit ten pages that other pages link to
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: _llms.txt:1 (also _sitemap.xml, _URLS.txt, _URLS_llms.txt and _llms-full.txt)
From the report
Evidence
(no entry for android-native/*, marketing/*, mobile-wallet-adapter/mobile-apps, reference/typescript/mobile-wallet-adapter-legacy)
Why it matters

these pages exist and are linked from indexed pages, for example the Kotlin quickstart's "JSON RPC Requests" card. Search engines and AI tools that follow the indexes cannot reach them. FAQ is also listed twice, and the release-notes URL differs between index files.

Suggested fix, not tested

regenerate all index files from the full page set, and remove the duplicate.

Review: Verified, with a correction. get-started/faq.md appears twice in _llms.txt (lines 23 and 55). Several of the missing pages are legacy or marketing pages that a docs platform often hides on purpose, and only the Kotlin quickstart card (kotlin/quickstart.md:189) links one of them. Discoverability for AI crawlers, with no effect on a developer following the visible docs.

77. Low React Native link to the store listing fails on Android 11 and later
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: dapp-store/link-to-dapp-listing-page.md:49
From the report
Evidence
Linking.canOpenURL(url)
  .then((supported) => {
Why it matters

on Android 11 and later, canOpenURL returns false unless the scheme is declared under <queries>. Only the Kotlin tab mentions the manifest change, so the React Native button always logs "Unable to link". The Kotlin manifest snippet is also malformed (<manifest without >).

Suggested fix, not tested

add the <queries> entry to the React Native tab (an Expo config plugin or AndroidManifest.xml), or call openURL directly and catch the error.

Review: Also real: the manifest snippet is <manifest with no > (:66). The failure is a visible "Unable to link to dApp Store" log in a convenience link, and the fix is one manifest entry. I could not run Android to check the package-visibility behaviour, which is general platform knowledge.

Info after review (8)

7. Info iOS deep-link example trusts any response on the custom scheme
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: recipes/mobile-wallet-adapter/wallet-signing-on-ios.md:181
From the report
Evidence
if url.scheme == "your-dapp-scheme" && url.host == "connect-response" {
    let connectData = parseConnectResponse(url)
Why it matters

any app can open a custom scheme. Without a per-request state value, a forged response can inject a wallet address or token. The surrounding text speaks of Universal Links, but the code uses a custom scheme.

Suggested fix, not tested

bind each request to a random state value, check it on the response, and say so in the example.

Review: The snippet illustrates why deep links are a poor protocol; the page itself says "carries risk" at :~192. Not offered as a pattern to copy.

11. Info signMessages result is not awaited in the direct-session samples
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: get-started/react-native/invoke-mwa-sessions-directly.md:586 (also line 617)
From the report
Evidence
const signedMessages = wallet.signMessages({
  addresses: [authorizationResult.accounts[0].address],
Why it matters

the promise is returned unawaited, unlike every other sample, so a wallet rejection is not handled where the reader expects.

Suggested fix, not tested

await the call and show a try/catch for a declined request.

Review: Also :617. The promise is returned from an async callback, so transact awaits it and rejection still propagates. Style inconsistency, no behaviour difference.

20. Info SGT authenticity rests on extension fields alone
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: solana-mobile-stack/seeker-genesis-token.md:206
From the report
Evidence
// If both extensions match then it is an SGT
if (hasCorrectMetadata && hasCorrectGroupMember) {
  return mint.address;
Why it matters

the program id is used only as a query filter, and the metadata pointer is a value any mint can set. Today the group-member field carries the guarantee, but the check documents no defence in depth for a gate on rewards.

Suggested fix, not tested

also assert the mint's owning program, and check the mint and group authorities against published values.

Review: Speculative. Defence-in-depth wish only.

21. Info SKR page does not say which token program owns the mint
First rating: Info · Reviewed rating: Info · Review: location checked, kept as rated
Location: solana-mobile-stack/skr.md:26
From the report
Evidence
* **Token type**: SPL Token
Why it matters

the SGT page uses Token Extensions, so "SPL Token" is ambiguous for anyone verifying SKR transfers.

Suggested fix, not tested

give the owning program id next to the mint address.

Review: Token type: SPL Token.

46. Info Catalog APKs are installed with all permissions and no integrity statement
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: cli/device.md:23
From the report
Evidence
Install APKs from local files, directories, or the built-in APK catalog.
Why it matters

catalog downloads are cached and installed with --grant, and the page describes no signature check.

Suggested fix, not tested

document the check, or state that there is none.

Review: --grant is an opt-in flag (cli/device.md:61, "Grant all runtime permissions"), not the default. The "installed with --grant" premise is wrong; the missing integrity statement is a docs wish.

49. Info Overwrite behaviour of --force and of existing targets is undocumented
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: cli/webshell.md:47 (similar gaps in cli/templates.md:78, cli/create.md:19 and cli/emulator.md:23)
From the report
Evidence
The directory defaults to the current one. `--force` overwrites an existing directory.
Why it matters

readers cannot tell whether running the command against an existing project, emulator or directory is refused, merged or overwritten.

Suggested fix, not tested

document the behaviour without --force, and what --force discards.

Review: The cited lines already state what --force does (webshell.md:47; templates.md:78 "synced files may be overwritten or removed"). The no---force behaviour is the gap, for webshell, create and emulator.

50. Info Re-running the Anchor setup script can orphan a deployed program
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: recipes/solana-development/anchor-integration.md:280
From the report
Evidence
This generates a program keypair, writes the new address into `lib.rs` and `Anchor.toml` ...
Why it matters

a second run replaces the program address, and the earlier deployment loses its keypair reference.

Suggested fix, not tested

say what happens on a re-run, and tell the reader to back up the program keypair.

Review: Line says it generates a new keypair and rewrites the address (:280). The "orphaned deployment" outcome is the report's inference, on a devnet demo scaffold.

51. Info Kotlin samples nest unlabelled lambdas with ambiguous receivers
First rating: Low · Reviewed rating: Info · Review: rated too high
Location: android-native/building-json-rpc-requests.md:114 (also dapp-store/link-to-dapp-listing-page.md:82)
From the report
Evidence
HttpClient(Android).use { client ->
    client.request(request.url) {
        request.properties.forEach { (k, v) ->
Why it matters

inside the builder, request and this resolve to different objects than a reader expects, which invites mistakes when the snippet is adapted.

Suggested fix, not tested

label the lambdas or extract the inner loop. Build the Intent first and then start it.

Review: Snippet exists (:113-123) but request is a plain parameter and this is not used, so there is no real ambiguity. Also dapp-store/link-to-dapp-listing-page.md:82 (style only).

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.