Sample audits · Open-source projects and documentation, eight more

sendaifun/solana-agent-kit

A toolkit that lets AI agents act on Solana through plugins.

Auditedsendaifun/solana-agent-kit at commit 1254fe550872ee48d42f168fc8ca854c87ab6eff
Date11 October 2026
How it rancloud session, full audit, Standard review
Verdict after reviewFail (rule: Fail if a High finding remains after review, otherwise Pass with notes)
3
High after review
25
Medium after review
110
Low after review
0
Info after review
2
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. 113 findings were first rated Medium or High; 28 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. Code locations link to the file and line at the audited commit.

High after review (3)

63. High Sign-only mode is bypassed by plugins that send directly
First rating: High · Reviewed rating: High · Review: confirmed
From the report
Evidence
  • plugin-defi/src/drift/tools/drift.ts:222 and 288, and drift_vault.ts:639
  • the Flash tools
  • plugin-misc/src/gibwork/tools/create_gibwork_task.ts:62
  • plugin-misc/src/crossmint/tools/checkout.ts:104
  • plugin-blinks/src/sendarcade/tools/rock_paper_scissor.ts:99, which also reports the prize as claimed without confirmation
  • plugin-nft/src/metaplex/tools/deploy_token2022.ts:80
if (agent.wallet.signAndSendTransaction) {
  const { signature } = await agent.wallet.signAndSendTransaction(tx);
Why it matters

A host that configured sign-only, for example to have a human approve each transaction, still has funds moved without approval.

Suggested fix, not tested

Route every send through one function that honours sign-only. Fix the cross-version instanceof check, for example with 'version' in tx, instead of working around it.

Review: lavarage.ts:39-49 calls agent.wallet.signAndSendTransaction(tx) and falls back to signTransaction + sendRawTransaction, never looking at agent.config.signOnly. Same in drift.ts:221-225 and :286-291, drift_vault.ts:639 (txSender.sendVersionedTransaction), create_gibwork_task.ts:61-62, crossmint/checkout.ts:102-106, rock_paper_scissor.ts:99-102, the Flash tools (perpClient.sendTransaction, flash_open_trade.ts:155) and deploy_token2022.ts:80 (umi sendAndConfirm). Only about a dozen tools honour the flag, so a host that set signOnly as its human-approval gate still has funds moved. This is the one defect that defeats an explicit safety control, which is why it stays HIGH. Upstream: 2 searches, no match found.

Upstream: No exact upstream report; related items: #599 (PR, open).

107. High OpenAI secret key is shipped in client bundles
First rating: High · Reviewed rating: High · Review: confirmed
From the report
Evidence
  • phantom-agent-starter/src/app/utils/provider.ts:9
  • crossmint-sak-v2/src/lib/ai/providers.ts:9
  • privy-agent-tanstack-starter/src/lib/ai/providers.ts:9
  • privy-sak-react-native/src/lib/ai/providers.ts:20
apiKey: process.env.NEXT_PUBLIC_OPENAI_API_KEY,
Why it matters

Any visitor, or anyone who unpacks the APK, can copy the key and spend on the account.

Suggested fix, not tested

Call the model from an authenticated server route, and rotate the keys.

Review: All five cited files build the provider from a client-visible variable: process.env.NEXT_PUBLIC_OPENAI_API_KEY (turnkey and phantom, utils/provider.ts:9), import.meta.env.VITE_OPENAI_API_KEY (crossmint-sak-v2 and privy-agent-tanstack, providers.ts:9) and @env OPENAI_API_KEY (React Native, providers.ts:7,20, compiled into the APK). Anyone who opens the deployed page or unpacks the app gets the deployer's key. Examples are not shipped to npm, but the template itself causes the leak, so it keeps HIGH. Upstream: 2 searches, no match found.

Upstream: No matching upstream report found (11 October 2026).

108. High Turnkey organisation private key is shipped to the browser
First rating: High · Reviewed rating: High · Review: confirmed
From the report
Evidence
const API_PRIVATE_KEY = process.env.NEXT_PUBLIC_TURNKEY_API_PRIVATE_KEY as string
Why it matters

Anyone can create wallets and sign for the organisation.

Suggested fix, not tested

Keep the key server-side, and rotate it.

Review: turnkey-agent-starter/src/app/chat/page.tsx:35 reads NEXT_PUBLIC_TURNKEY_API_PRIVATE_KEY and uses it at :56-61 to build a Turnkey API client in a client component; .env.local.example:7 tells users to put it in that variable. The key lets the holder create wallets and sign for the organisation. Upstream: 2 searches, no match found.

Upstream: No matching upstream report found (11 October 2026).

Medium after review (25)

1. Medium Login trusts a client-supplied email or wallet address
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
const user = await getUser(data.email, data.walletAddress);
...
await session.update({ id: user[0].id,
Why it matters

loginFn and signupFn open a session for whichever user matches the posted email or wallet address. They check no signature and no token. Wallet addresses are public, so anyone can log in as any user and read or act on their chats. An unknown user also crashes the login: getUser returns an array, an empty array is truthy, and user[0].walletAddress then throws.

Suggested fix, not tested

Verify a Crossmint or Privy auth token, or a signed challenge, on the server before you create the session. Test user.length === 0 for the not-found case.

Review: session.ts:17-42 opens a session for whoever getUser matches, with no proof of ownership (confirmed in both starters). The !user test on an array is also wrong (an empty array is truthy, so user[0].walletAddress throws). Impact is chat history only: the wallet is held by Crossmint/Privy and signs client-side. Placeholder session secret: see 117.

Upstream: No matching upstream report found (11 October 2026).

2. Medium Mobile backend authenticates by a wallet-address header
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
const walletAddress = req.headers['x-wallet-address'] as string || req.query.walletAddress as string;
Why it matters

Wallet addresses are public on chain, so anyone can read, overwrite or delete any user's chats. A query such as ?walletAddress[$ne]=x reaches findOne as an operator object, which bypasses the login altogether. POST /user changes any user's profile without any check.

Suggested fix, not tested

Verify a Privy access token or a signed nonce, then issue a session. Reject values that are not strings. Put /user behind the same middleware.

Review: POST /user (userRoutes.ts:11) has no middleware. Real, but chat data in a demo backend, not funds.

Upstream: No matching upstream report found (11 October 2026).

3. Medium Agent signs raw transaction bytes supplied by the caller
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
const txBuffer = Buffer.from(transactionData.substring(2), "hex");
const transaction = VersionedTransaction.deserialize(txBuffer);
Why it matters

The bridge-execution tool takes a hex transaction from the model's tool arguments and signs it with the agent wallet. A prompt injection can make the model pass a transaction that drains the wallet.

Suggested fix, not tested

Execute only orders this tool created itself (keep them server-side and look them up by id). Decode the instructions and check the programs, the fee payer, the amounts and the destinations before signing.

Review: execute_bridge_order.ts:15-31 deserialises a hex string from the tool argument, swaps the blockhash and calls signOrSendTX (so sign-only is honoured). It is the second half of a create-then-execute flow, and a prompt-injected model can already call the plain transfer tool, so this adds arbitrary program calls rather than a new class of loss. A fix (server-side order lookup, program allow-list) would be good hardening.

Upstream: No matching upstream report found (11 October 2026).

31. Medium EVM signing wallet is cached at module level across agents
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
let evmWallet: Wallet | null;
Why it matters

In a process that serves several agents or tenants, later agents sign EVM swaps with the first agent's private key and spend from the wrong wallet.

Suggested fix, not tested

Build the wallet from the current agent's configuration on each call, or cache it per agent.

Review: mayan/tools/swap.ts:147-160: let evmWallet is created from the first agent's ETHEREUM_PRIVATE_KEY and reused for every later agent in the process. Real cross-tenant bug, but it needs several agents with different raw EVM keys in one process.

Upstream: No matching upstream report found (11 October 2026).

33. Medium Para example shares one agent whose wallet is swapped by each request
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
solanaAgentWithPara.wallet = {
Why it matters

Concurrent users sign and send with each other's wallets. Anyone can post a key share or drive the chat route.

Suggested fix, not tested

Create one agent per authenticated session or request, and require authentication on both routes.

Review: init_server.ts:6,20 mutates the module-level solanaAgentWithPara.wallet per request; wallet/init/route.ts and chat/route.ts have no authentication. Concurrent users would sign with each other's wallets, but this is a single-user demo.

Upstream: No matching upstream report found (11 October 2026).

34. Medium Next.js LangChain example shares one memory thread and wallet with every visitor
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
const memory = new MemorySaver();
...
thread_id: "Solana Agent Kit!",
Why it matters
  • Conversations from different visitors merge and can leak to each other.
  • History is duplicated on every request.
  • The unauthenticated route lets any visitor direct the server wallet (line 46).
Suggested fix, not tested

Key the thread by an authenticated session, send only the new message, require authentication, and limit spending.

Review: chat/route.ts:30,58: one module-level MemorySaver and a fixed thread_id: "Solana Agent Kit!" for every visitor, and the client also sends the full history each time. The unauthenticated-server-wallet half is the same root cause as 122.

Upstream: No matching upstream report found (11 October 2026).

36. Medium Drift client polls every 10 ms and is not cleaned up on error paths
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
accountLoader: new BulkAccountLoader(agent.connection, "processed", 10),
Why it matters

Each early throw leaves a poller hitting the RPC for the life of the process. Repeated failures stack pollers until the provider rate-limits the agent. One deposit opens four or more clients.

Suggested fix, not tested

Call cleanUp() in finally blocks, use one client per operation, and poll about every second.

Review: drift.ts:71-74 builds BulkAccountLoader(connection, "processed", 10); cleanUp() is called only on success paths (e.g. depositToDriftUserAccount:199 throws "create a Drift user account first" before it). The same applies to the vault tools, each of which opens its own client. A failed call leaves a 100 req/s poller running for the life of the process. Upstream: 2 searches, no match found.

Upstream: No matching upstream report found (11 October 2026).

62. Medium Batch send throws after broadcasting the first transaction
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
txSigs.push(signature);
}
throw new Error(
Why it matters

The throw runs on every loop iteration. Every array call sends transaction 1 and then reports failure, so the rest are never sent. Callers may retry and pay twice. Affected callers include cancelling limit orders and the Raydium launch.

Suggested fix, not tested

Put the throw in an else branch, or check support once before the loop. Add a two-transaction test.

Review: wallet.ts:134-144: the throw follows the if unconditionally, so for any array the first transaction is signed and sent, then the call throws. jupiter/cancel_limit_orders.ts:22 and the Raydium launch use this path. Real and in core, but the damage is a false error plus unsent follow-up transactions, not a loss of funds. Upstream: 1 search, no match found.

Upstream: No matching upstream report found (11 October 2026).

64. Medium Production vendor API key is hard-coded in a published package
First rating: High · Reviewed rating: Medium · Review: rated too high
Location: packages/plugin-defi/src/lavarage/tools/lavarage.ts:6: a Lavarage API key whose value starts lv2_.
From the report
Evidence
const API_KEY = "lv2_…";
Why it matters

Everyone who installs the package gets the key and can use up its quota.

Suggested fix, not tested

Revoke and rotate the key. Read it from the agent configuration and fail fast when it is missing.

Review: lavarage.ts:6 holds lv2_prod_... in plugin source that ships in dist. Rotate it. Upstream: 2 searches, no match found.

Upstream: No matching upstream report found (11 October 2026).

68. Medium Third-party wallet is the default creator of every launched token
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
let REFERRAL_WALLET = new PublicKey(
  "FPfG…",
Why it matters

That address is passed as the token creator (line 88), so creator-fee ownership goes to a fixed outside wallet unless the user overrides it.

Suggested fix, not tested

Default the creator to the agent wallet, and make any referral an explicit, documented opt-in.

Review: launchPumpfunToken.ts:62-67,83-90 passes FPfGD3kA8Z... (a fixed wallet) as the creator unless PUMP_FUN_REFERRAL_WALLET is set; the variable is declared in types/index.ts:41 but documented nowhere in the repo. Creator-fee ownership goes to a wallet the user did not choose. (The launch itself is broken, see 66.)

Upstream: No matching upstream report found (11 October 2026).

69. Medium Wallet signing methods are passed without their object
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
  • plugin-defi/src/flash/tools/utils/flashUtils.ts:275
  • plugin-defi/src/adrena/tools/utils/anchor/AdrenaClient.ts:45
  • plugin-defi/src/okx/tools/execute_swap.ts:33
agent.wallet.signTransaction,
Why it matters

The default keypair wallet reads this.payer, so a detached call throws. Mayan, Flash and Adrena trades cannot be signed.

Suggested fix, not tested

Pass wrappers such as (tx) => agent.wallet.signTransaction(tx).

Review: mayan/tools/swap.ts:101,109 passes agent.wallet.signTransaction/signAllTransactions as bare functions; flashUtils.ts:275-276 and AdrenaClient.ts:45-46 put them in an object literal. KeypairWallet methods read this.payer (keypairWallet.ts:52-55), so this is undefined or the literal and signing throws. Mayan, Flash and Adrena fail with the default wallet and need a wallet whose methods are closures. The OKX item (execute_swap.ts:31-36) is not affected: the ...agent.wallet spread copies the own payer property. Fail-closed, so MEDIUM. Upstream: 2 searches, no match found.

Upstream: No matching upstream report found (11 October 2026).

70. Medium Legacy transactions are built without a fee payer
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
  • lines 113, 138 and 274
  • plugin-defi/src/drift/tools/drift.ts:215 and 280
  • plugin-token/src/solana/tools/close_empty_token_accounts.ts:60
const txn = new Transaction().add(...depositPlaceOrderIx);
txn.recentBlockhash = blockhash;
Why it matters

The keypair wallet's partialSign cannot compile a message without a fee payer. Every Manifest tool, Drift deposit and Drift withdraw fails.

Suggested fix, not tested

Set feePayer = agent.wallet.publicKey, or build a v0 message with an explicit payer.

Review: manifest_trade.ts:39-43,85-87,113-115,138-140, drift.ts:215-220,280-285 and close_empty_token_accounts.ts:26,57-60 never set it, so with KeypairWallet about eight tools always fail. Systemic but fail-closed. Upstream: 2 searches, no match found.

Upstream: No matching upstream report found (11 October 2026).

80. Medium OpenAI adapter makes optional parameters required and aborts on common schema types
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
const required = Object.keys(properties);
...
throw new Error(`Unsupported Zod type: ${typeName}`);
Why it matters

The model has to invent values, for example a mint for a plain SOL transfer. One union schema in the misc plugin breaks creation of the whole tool list.

Suggested fix, not tested

Emit optional fields as nullable. Handle unions, records and any. Skip, and log, actions that cannot be converted.

Review: openai/utils.ts:123,172 lists all keys as required and unwraps ZodOptional without making the property nullable (isNullable is only set for ZodNullable, :57); :127 throws on any unsupported type, so one union schema breaks creation of the whole tool list.

Upstream: No matching upstream report found (11 October 2026).

81. Medium MCP adapter turns optional parameters into required ones
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
result[key] = isZodOptional(value) ? value.unwrap() : value;
Suggested fix, not tested

Keep the optional schema, and parse inputs through the action schema.

Review: adapter-mcp/src/index.ts:43 unwraps ZodOptional before passing the shape to server.tool, so clients must supply every optional field (for example tokenAddress on the balance action).

Upstream: Already reported upstream: #600 (issue, open), #601 (PR, open).

84. Medium Dependent market-creation transactions are sent in parallel
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
const txs = await Promise.all(
  transactions.map(async (tx) => {
Why it matters

A failure partway leaves accounts half-created, with their rent spent.

Suggested fix, not tested

Send the transactions in order, confirm each one, and report how far it got.

Review: openbook_create_market.ts:49-55 runs Promise.all over the Raydium marketV2.create transactions, each fetching its own blockhash. The SDK's account-creation and initialise transactions depend on each other; the code part is confirmed, the exact SDK ordering was not read. A partial failure leaves rent spent.

Upstream: No matching upstream report found (11 October 2026).

91. Medium Prices are truncated to two decimals
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
const adjustedPrice = price.mul(new BN(100));
Why it matters

Tokens priced below one cent are reported as 0.00.

Suggested fix, not tested

Format using the full exponent.

Review: pyth_fetch_price.ts:77-85: scaling by 100 and slicing the last two digits. Worse than reported: for a price of 0.05 the scaled string is "5", "5".slice(0,-2) is empty and the result is "0.5", ten times too high (verified with a one-line node -e on the same expression). Prices from 0.01 to 0.099 are wrong by 10x.

Upstream: No matching upstream report found (11 October 2026).

98. Medium Drift perp amount is documented as tokens but treated as USD
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
const convertedAmount =
  params.amount / convertToNumber(baseAssetPrice.price, PRICE_PRECISION);
Why it matters

A request for "50 SOL" opens a 50 USD position.

Suggested fix, not tested

Choose one unit, and name and document it.

Review: drift.ts:351-352 divides params.amount by the oracle price, while the action schema says "amount of the token ... e.g. 50 SOL" (actions/tradePerpAccount.ts:53). "50 SOL" opens a 50 USD position. Same pattern in drift_vault.ts:591.

Upstream: No matching upstream report found (11 October 2026).

102. Medium Flash trades hard-code 10% slippage and skip preflight
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
const slippageBps = new BN(1000);
Suggested fix, not tested

Make slippage a bounded parameter, set a maximum leverage, and keep preflight on.

Review: flash_open_trade.ts:127-133 fixes slippageBps = 1000; createPerpClient also sets skipPreflight: true (flashUtils.ts:281). At 10x leverage a 10% adverse fill equals the whole collateral, and the user cannot change it.

Upstream: No matching upstream report found (11 October 2026).

109. Medium Model output is rendered as unsanitised HTML on wallet pages
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
dangerouslySetInnerHTML={{ __html: marked(m.content) }}
Why it matters

Text that reaches the model from tools or chain data can inject script into a page that holds signing access.

Suggested fix, not tested

Render with react-markdown, or sanitise with DOMPurify.

Review: turnkey-agent-starter/.../Chat.tsx:299 and the phantom starter use dangerouslySetInnerHTML={{ __html: marked(m.content) }}; marked does not sanitise. Token names or other tool output can carry markup into a page that holds signing access.

Upstream: No matching upstream report found (11 October 2026).

115. Medium Wallet key shares can be read or deleted by email with no authentication
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
const email = searchParams.get("email");
...
db.findOne({ email }, (err, doc) => {
Why it matters
  • Anyone who knows an email can download that user's key share, or delete it.
  • Storage is in memory only.
  • The save route reports success before the write completes.
Suggested fix, not tested

Require a session that matches the email. Never return raw shares. Use durable storage and acknowledge only after the write.

Review: usershare/route.ts:37-80 returns the stored document (email plus userShare) for any email, and :83 deletes it; the store is in-memory nedb (db.ts:4). A share is one part of an MPC key and needs the provider session to sign, so it is not a drain on its own.

Upstream: No matching upstream report found (11 October 2026).

120. Medium Telegram bot puts private keys in the model prompt and stores them in plaintext
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
If user asks for his funds back, you can send them their private key ${keyPair.privateKey}.
Why it matters

The key is sent to the model provider and can be pulled out by prompt injection. It is also stored unencrypted.

Suggested fix, not tested

Never put secrets in prompts. Use a custodial or key-management wallet service.

Review: advanced-tg-bot/.../route.ts:54,92 stores the secret key in Firestore in plaintext and interpolates it into the system prompt. It is a deliberate custodial design (the user's own key, in their own thread), but sending keys to the model provider and storing them unencrypted is poor practice.

Upstream: No matching upstream report found (11 October 2026).

121. Medium Group bot posts the user's private key into the chat
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
await ctx.reply("Your private key is:");
await ctx.reply(`${String(keyPair.privateKey)}`);
Why it matters

The key ends up in chat history and backups on every private message.

Suggested fix, not tested

Do not echo keys. Offer an authenticated export flow instead.

Review: group-tg-bot/.../route.ts:129-130,138-139 replies with the private key to every private message from the owner. It goes to the owner's own chat, so this is exposure in history and backups rather than to a third party (but see 123).

Upstream: No matching upstream report found (11 October 2026).

122. Medium Bots and routes let any user spend one shared wallet
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
  • examples/social/tg-bot-starter/basic-tg-bot/src/app/api/bot/route.ts:31
  • examples/defi/wormhole-nextjs-agent/app/api/chat/route.ts:18
const secretKey = bs58.decode(process.env.SOLANA_PRIVATE_KEY as string);
Why it matters

Anyone who can message the bot or call the route can transfer or swap the wallet's funds.

Suggested fix, not tested

Add allow-lists or authentication, spending caps and confirmation steps.

Review: discord-bot-starter/src/index.ts:26 and basic-tg-bot/.../route.ts:31 load one SOLANA_PRIVATE_KEY; the Discord bot answers any DM and shares one memory thread. The wormhole citation is moot: that route uses the removed v1 API and cannot start (see 129). This is the nature of the demo, with only generic README advice (README.md:912).

Upstream: No matching upstream report found (11 October 2026).

123. Medium Telegram webhook accepts unauthenticated updates
First rating: High · Reviewed rating: Medium · Review: rated too high
From the report
Evidence
const handler = webhookCallback(bot, "std/http");
Why it matters

A forged update can claim any user id and drive that user's custodial wallet.

Suggested fix, not tested

Register the webhook with a secret token and verify it.

Review: route.ts:183 uses webhookCallback(bot, "std/http") with no secretToken, so a forged update can claim any user id. Exploiting it needs the (unpublished) webhook URL and the victim's numeric id, but combined with 120/121 it would leak keys.

Upstream: No matching upstream report found (11 October 2026).

134. Medium Mobile chat retries whole generations that may already have sent transactions
First rating: Medium · Reviewed rating: Medium · Review: confirmed
From the report
Evidence
const shouldRetry = isTransactionRelated(messageContent) && retryCount < MAX_RETRIES;
Why it matters

A transfer or swap can run twice.

Suggested fix, not tested

Retry only idempotent calls, and deduplicate by signature.

Review: useChat.ts:330-352,449-463 wraps generateText({ tools, maxSteps: 5 }) in executeWithRetry, retried up to twice for any error when the message contains words like "send" or "swap". A tool call that already landed a transaction is run again.

Upstream: No matching upstream report found (11 October 2026).

Low after review (110)

4. Low Vendor-built transactions are signed without inspection
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
  • plugin-misc/src/gibwork/tools/create_gibwork_task.ts:59
  • plugin-blinks/src/sendarcade/tools/rock_paper_scissor.ts:26
  • plugin-defi/src/lulo/tools/lend.ts:25
  • plugin-defi/src/solayer/tools/stake_with_solayer.ts:33
  • plugin-misc/src/crossmint/tools/checkout.ts:102
const transaction = VersionedTransaction.deserialize(swapTransactionBuf);
return await signOrSendTX(agent, transaction);
Why it matters

A compromised, spoofed or buggy API response receives a full wallet signature. Several of these paths also send with skipPreflight: true.

Suggested fix, not tested

Before signing, check:

  • the fee payer is the agent wallet
  • the program ids are on an allow-list
  • the token outflows match the requested mint, amount and recipient

Review: the "skipPreflight: true" remark does not apply to the Jupiter path (KeypairWallet.signAndSendTransaction passes no options); it does apply to checkout.ts:105 and the Lavarage fallback.

6. Low The vendor sets the tip paid from the user wallet, with no cap
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const { tipLamports } = await lavaApi("/bundle/tip");
... body: { ...body, astralaneTipLamports: tipLamports },
Why it matters

An abnormal value is paid in full. Transactions go out with skipPreflight: true (line 48), so failed trades still land on chain and pay fees.

Suggested fix, not tested

Cap the tip in configuration, inspect the returned transaction, and keep preflight on.

7. Low The core send loop reports success on "processed" and resends blindly
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/core/src/utils/send_tx.ts:172. The loop starts at line 160.
From the report
Evidence
if (statuses.value[0]) {
  if (!statuses.value[0].err) {
    return signature;
Why it matters
  • A "processed" status can still be rolled back, but the caller is told the transfer or swap happened.
  • The same signed transaction is re-sent for up to 90 s without tracking the block height until which its blockhash is valid.
  • A transaction that lands between polls can come back as "already processed" or "Blockhash not found", and that gets reported as a failure. The caller may then retry and pay twice.
Suggested fix, not tested

Require at least "confirmed" status. Use confirmTransaction with lastValidBlockHeight, and treat "already processed" as a status check, not an error.

8. Low Confirmation results are ignored
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
  • plugin-token/src/solana/tools/request_faucet_funds.ts:20
  • plugin-nft/src/metaplex/tools/deploy_token2022.ts:80, which confirms only at "processed"
await agent.connection.confirmTransaction({
  signature,
  blockhash: latestBlockhash.blockhash,
Why it matters

A transaction that reverts is reported as success.

Suggested fix, not tested

Use the blockhash of the signed transaction. Throw when value.err is set, and confirm at "confirmed" or higher.

9. Low An off-chain explorer reply overrides an on-chain revert
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
throw new Error(`Transaction ${swapRes.serializedTrx} reverted!`);
...
if (res.status !== 200) { throw error;
Why it matters

The revert error is thrown inside the same try block, so the catch returns the signature as success whenever the vendor explorer answers 200.

Suggested fix, not tested

Handle result.value.err outside the try and always fail on it. Fall back to the explorer only for RPC timeouts.

10. Low Token transfers are hard-wired to the classic token program
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
  • plugin-defi/src/drift/tools/drift.ts:143, plus lines 208, 274, 601 and 683
  • plugin-defi/src/meteora/tools/create_meteora_dlmm_pool.ts:34
const mintInfo = await getMint(agent.connection, mint);
...
createTransferInstruction(
Why it matters

Token-2022 mints fail or derive the wrong token account. An unchecked transfer also lets a wrong-decimals amount through.

Suggested fix, not tested
  • Read the mint's owner program and pass it to the mint lookup, the ATA derivation and the instruction builders.
  • Use createTransferCheckedInstruction.
  • Create the ATA with the idempotent instruction.
11. Low Pool creation accepts any mint owner and does not check Token-2022 extensions
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
programId: mintInfoA.owner.toString(),
Why it matters

Liquidity can be deposited into a pool for a mint with a permanent delegate, a transfer hook or a transfer fee, and none of these are checked.

Suggested fix, not tested

Require the owner to be one of the two token programs. Decode the mint, and reject or warn on risky extensions and on active mint or freeze authorities.

12. Low Balance listings ignore Token-2022 accounts
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
  • plugin-defi/src/sanctum/tools/sanctum_get_owned_lst.ts:12
  • plugin-misc/src/helius/tools/send_transaction_with_priority.ts:99
  • plugin-nft/src/tensor/tools/tensor_trade.ts:22
programId: TOKEN_PROGRAM_ID,
Why it matters

Token-2022 holdings are left out of portfolios and lookups.

Suggested fix, not tested

Query both token programs and merge the results.

13. Low Creator-fee claim uses the referral wallet as fee payer and claimant
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const ix = await pumpSdk.collectCoinCreatorFeeInstructions(REFERRAL_WALLET);
...
payerKey: REFERRAL_WALLET,
Why it matters

The agent cannot provide that wallet's signature, so the claim fails. It also targets a third party's fees instead of the agent's.

Suggested fix, not tested

Use agent.wallet.publicKey as both the payer and the creator.

14. Low Fee payer or user wallet is taken from caller input
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
fee_payer: agent.wallet.publicKey.toBase58(),
...
...rest,
Why it matters

...rest can override the fee payer, and the OKX schema lets the model choose the user wallet. The Ranger module is not registered at the moment, but it will become reachable once someone registers it.

Suggested fix, not tested

Set these fields from the agent wallet, after the spread. Remove them from the schemas.

15. Low Vendor HTTP responses are used without status or shape checks
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
  • jupiter/tools/stake_with_jup.ts:28
  • mayan/tools/swap.ts:99
  • dexscreener/tools/get_token_data.ts:19
  • plugin-defi/src/drift/tools/drift.ts:754
  • plugin-blinks/src/sendarcade/tools/rock_paper_scissor.ts:68
  • plugin-misc/src/messari/tools/ask_messari_ai.ts:31
const { swapTransaction } = await (
...
const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
Why it matters

An outage or an error body ends as an obscure TypeError, or is returned to the user as data with status "success".

Suggested fix, not tested

Check response.ok, validate the fields you need, and pass the vendor's error message on.

16. Low Oracle prices are used without a freshness check
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const data = await response.json();
const parsedData = data.parsed;
Why it matters

Stale or wide-confidence prices size positions and collateral.

Suggested fix, not tested

Reject prices older than a threshold or with a large confidence interval, and check the HTTP status.

17. Low Vendor transaction is rebuilt and loses its address-lookup-table accounts
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
programId: messages.staticAccountKeys[ix.programIdIndex],
keys: ix.accountKeyIndexes.map((i) => ({
  pubkey: messages.staticAccountKeys[i],
Why it matters

Indexes that point into lookup tables resolve to undefined, so the swap crashes or is built wrongly.

Suggested fix, not tested

Use the vendor message as delivered and only refresh the blockhash. Otherwise, decompile it with the lookup-table accounts.

18. Low Failure messages are unreadable
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
`Transaction failed: ${statuses.value[0].err.toString()}`,
Why it matters

Users see [object Object] or {} instead of the reason the transaction failed.

Suggested fix, not tested

Serialise the error object, or map known errors to text. Include the signature.

Review: send_tx.ts:176 is a real [object Object]; the Orca citation uses JSON.stringify(error), which gives {} for Error objects.

19. Low Misleading or raw error text reaches users
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: packages/plugin-token/src/mayan/tools/swap.ts:131 puts the whole serialised transaction into the error. Also:
From the report
Evidence
  • plugin-defi/src/drift/tools/drift.ts:615 says "Failed to get APYs" in the staking function.
  • examples/misc/agent-kit-nextjs-langchain/app/api/chat/route.ts:79 returns raw exception text.
  • examples/embedded-wallets/para-plugin-example/app/api/wallet/init/route.ts:19 returns raw exception text.
  • plugin-nft/src/tensor/tools/tensor_trade.ts:35 swallows the ownership error.
throw new Error(`Transaction ${swapRes.serializedTrx} reverted!`);
Suggested fix, not tested

Give a short, accurate message with the signature, and keep the technical detail in the logs.

20. Low Missing or invalid balances are reported as zero
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
return token_account.value.uiAmount || 0;
Why it matters
  • get_balance passes the mint address to getTokenAccountBalance, which expects a token account, and a null result becomes 0.
  • In the vault tool, an explicit fee or hurdle of 0 is treated as "not provided".
Suggested fix, not tested
  • Derive the owner's token account from the mint.
  • Use the raw amount and the decimals.
  • Treat null as an error.
  • Test !== undefined, not truthiness.

Review: with a mint address getTokenAccountBalance throws; the "returns 0" claim is only true for a null uiAmount.

21. Low || and ?? defaults hide missing data
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
.toSorted((a, b) => (b.daily_volume ?? 0) - (a.daily_volume ?? 0))
Suggested fix, not tested

Handle a missing field explicitly. Use ?? so that a value of 0 survives, and do not mutate the caller's parameters.

22. Low An undocumented vendor web-app endpoint is used as an API
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
`https://worker.jup.ag/blinks/swap/So111.../jupSoLaH.../${amount}`
Suggested fix, not tested

Move to a documented public API, or put this endpoint behind a configuration flag with a clear error when it stops working.

23. Low Browser-wallet results are used with the wrong shape
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const transactionHash = await phantom.solana.signAndSendTransaction(tx);
return { signature: transactionHash };
Why it matters

The provider returns an object, so the "signature" is an object. Rendering the signMessage result as a string crashes the component.

Suggested fix, not tested

Read .signature and encode it before you store or display it.

24. Low Phantom is initialised without a secure-context check
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
const phantomInstance = await createPhantom({
Suggested fix, not tested

Check window.isSecureContext and show a clear message when the page is served over plain HTTP.

25. Low Mobile Wallet Adapter path is taken on every Android device
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
if (wallet.provider === 'mwa' || Platform.OS === 'android') {
Why it matters
  • On Android, a user of the embedded wallet would be routed to a different wallet.
  • The cluster is hard-coded to devnet, and the authorization result, including its token, is logged.
  • The file imports a module that does not exist, so today it is dead code.
Suggested fix, not tested

Route only on wallet.provider === 'mwa', take the chain from configuration, and stop logging auth tokens.

26. Low System colour-scheme check is inverted
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const prefersDark = window.matchMedia('(prefers-color-scheme: light)').matches;
Why it matters

First-time visitors get the opposite of their system theme.

Suggested fix, not tested

Query (prefers-color-scheme: dark).

27. Low Theme is applied after first paint, and the native splash is white
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
const [theme, setTheme] = useState<Theme>("light");
Why it matters

Dark-mode users see a light flash on every load. The Android splash is white, with no night override, before the app's dark UI appears.

Suggested fix, not tested

Set the theme with an inline head script before hydration. Make the splash colour match the app background.

28. Low Hard-coded colours and a logo fixed to one theme
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
<item name="android:statusBarColor">#ffffff</item>
Suggested fix, not tested

Use colour resources with night variants, and switch the logo per theme.

29. Low Responsive image sizes is invalid
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
sizes="100%"
Why it matters

The browser falls back to 100vw and downloads a full-width image for a small avatar.

Suggested fix, not tested

Pass the drawn width, for example sizes="48px".

30. Low Login modal interrupts typing
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
if (!authenticated && e.target.value.trim() !== "") {
  checkAuthAndShowModal();
Suggested fix, not tested

Ask for sign-in on send, which the submit handler already does.

32. Low SolutioFi client is cached once for all agents and stored before it authenticates
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/plugin-token/src/solutiofi/tools/solutiofi.ts:9. Line 20 has the same problem.
From the report
Evidence
let solutiofiClient: SolutioFi | null = null;
Why it matters

Later agents reuse the first agent's API key. Parallel calls can use the client before authenticate() has finished.

Suggested fix, not tested

Cache one in-flight promise per agent or per API key, and clear it on failure.

35. Low Order-confirmation polling always reports a timeout and leaves timers running
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/plugin-misc/src/crossmint/tools/confirm-order.ts:42. The function returns at line 79.
From the report
Evidence
const pollInterval = setInterval(async () => {
...
return { success: false, error: "Payment confirmation timeout",
Why it matters

Values returned from timer callbacks go nowhere. A paid order is reported as failed, which invites a re-order, and the timers fire after the function has returned.

Suggested fix, not tested

Write an awaited loop that polls, sleeps and checks a deadline, returns the real status, and leaves no dangling timers.

37. Low Confirmation is polled instead of subscribed
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
const statuses = await agent.connection.getSignatureStatuses([signature]);
Why it matters

This is about 180 RPC calls per transaction.

Suggested fix, not tested

Use confirmTransaction with the blockhash and block height, and re-send at a slower interval.

38. Low Plugin registration is not atomic
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
plugin.initialize(this as SolanaAgentKit);
...
throw new Error(`Method ${methodName} already exists in methods`);
Why it matters

A name collision throws after some methods are already bound. The plugin is left half-registered, and a retry fails.

Suggested fix, not tested

Check all names first, then update the methods, actions and plugin map together.

39. Low Two adapters bypass the shared action executor
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
JSON.stringify(await action.handler(solanaAgentKit, inputs)),
Suggested fix, not tested

Run every adapter through executeAction, so that validation and error shape are the same everywhere.

40. Low Values that never change are recomputed per call
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
const connection = new Connection(this.rpcUrl);
Suggested fix, not tested

Create these once.

41. Low A fixed sleep stands in for confirmation
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
await new Promise((resolve) => setTimeout(resolve, 2000));
Suggested fix, not tested

Wait for the init transaction to confirm, fetch the account again, and rethrow unexpected errors.

Review: the sleep is at drift_vault.ts:77, not :69. 52: the import is at wallet.ts:13-17, not :104. All other LOW locations match.

42. Low Dead persistence code implies a design that does not exist
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
if (walletDataStr) {
  fs.writeFileSync(WALLET_DATA_FILE, walletDataStr);
Suggested fix, not tested

Remove the no-op read and write, and create the checkpointer once.

43. Low "In progress" flag can stay set for good, and the check-and-set is not atomic
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: examples/social/tg-bot-starter/advanced-tg-bot/src/app/api/bot/route.ts:148. Same pattern in group-tg-bot at lines 164–195.
From the report
Evidence
await updateDoc(userDocRef, { inProgress: true });
Why it matters

Two messages can both pass the check. A function killed at its time limit leaves the user locked out permanently.

Suggested fix, not tested

Use a transactional compare-and-set with a lease timestamp that expires, and reset the flag in one place.

44. Low A message from another user's chat can be overwritten by id
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const existingMessage = await Message.findOne({ id });
Why it matters

Ownership is checked only against the chat id in the request, so a caller can supply a message id that belongs to another user's chat.

Suggested fix, not tested

Look the message up by both { id, chatId }.

45. Low User and assistant message ids collide
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const messageId = `${ctx.chatId}-${ctx.message.message_id}`;
...
id: messageId + 1,
Why it matters

"<chat>-45" + 1 becomes "<chat>-451", which later collides with a real message id. The batch insert then fails and the chat history silently stops growing.

Suggested fix, not tested

Use UUIDs, or prefixes such as u- and a-.

46. Low User identity columns have no uniqueness or invariant
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
walletAddress: text("walletAddress").notNull().default(""),
Why it matters

Concurrent sign-ups create duplicate users, and every user without a wallet matches ''.

Suggested fix, not tested

Add unique constraints and a check that an email or a wallet address is present. Use an upsert.

47. Low MCP example ignores its configuration and starts with zero tools
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const agent = new SolanaAgentKit(keypairWallet, keypairWallet.rpcUrl, {});
Why it matters

No plugins are loaded, so the server starts without error but exposes no tools. The start promise is also left unhandled.

Suggested fix, not tested

Load the plugins, build the configuration from the environment, and exit with an error when the tool list is empty.

48. Low An API key is read from the process environment instead of the configuration
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
"x-api-key": process.env.FLEXLEND_API_KEY!,
Why it matters

A key supplied through the configuration is ignored. If the variable is unset, the request fails with an unrelated TypeError.

Suggested fix, not tested

Read the key from agent.config, fail fast when it is missing, and check response.ok.

49. Low Missing credentials silently become empty strings
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: packages/plugin-defi/src/okx/tools/get_quote.ts:24, and the same in five other OKX tools.
From the report
Evidence
apiKey: agent.config.OKX_API_KEY ?? "",
Suggested fix, not tested

Add one credential helper that throws a clear error.

50. Low Oversized files mix unrelated concerns
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: packages/plugin-defi/src/drift/tools/drift.ts:1020 (1,057 lines). Also packages/plugin-defi/src/index.ts:258 (527 lines of hand-kept lists).
From the report
Evidence
export async function getLendingAndBorrowAPY(
Suggested fix, not tested

Split the file into client, user account, insurance fund, swap and market data modules. Have each protocol export its own bundle of methods and actions.

51. Low Shared logic and types are duplicated, and the copies have drifted
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
  • the openai, langchain and vercel-ai adapters
  • packages/core/src/types/action.ts:25 versus types/index.ts:115
  • the no-op initialize loop copied into every plugin (packages/plugin-token/src/index.ts:156)
for (const action of actions.slice(0, 127)) {
Suggested fix, not tested

Add one helper for limiting actions and building tool descriptions, and keep one Action type definition.

52. Low Runtime transaction logic lives in the types layer and forms an import cycle
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
import { type feeTiers, getComputeBudgetInstructions, sendTx } from "../utils/send_tx";
Suggested fix, not tested

Move signOrSendTX into utils/ and keep types/ for declarations only.

53. Low CI runs no tests and rewrites code instead of checking it
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
- name: Run lint and fix
  run: pnpm run lint:fix
Suggested fix, not tested

Run pnpm run lint instead. Add a test job, including a smoke test that loads every plugin and builds each tool list. Align the pnpm version with the one in package.json.

54. Low The test suite reports success with zero assertions and imports modules that no longer exist
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: test/programmaticTests/index.ts:17. The test/tools/okx_*.test.ts files and test/tools/wormhole.ts import ../../src, which does not exist.
From the report
Evidence
const tokenData = await agent.methods.getAsset(
...
// Add your DeFi plugin test here
Suggested fix, not tested

Write real assertions against a mocked RPC, port or delete the old-API files, and exit non-zero on failure.

55. Low Every package declares a Jest test script, but Jest is not installed
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: packages/plugin-defi/package.json:27. No *.test.ts or *.spec.ts exists under packages/ (searched).
From the report
Evidence
"test": "jest"
Suggested fix, not tested

Add a test runner with a configuration and smoke tests, or remove the scripts.

56. Low Trade parameters ship with unresolved "confirm" TODOs
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: packages/plugin-defi/src/ranger/tools/ranger_perp_trading.ts:35. Also lines 80, 181, 226 and 275.
From the report
Evidence
adjustment_type: "Increase", // TODO: Confirm if this should be "Increase" or another type for open
Suggested fix, not tested

Check each value against the provider's API before you enable this module.

57. Low The token table is copied into five packages, and the copies differ
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
Location: packages/core/src/constants/index.ts:6. Also in the jupiter, adrena, fluxbeam and sns constants. The core copy is not exported.
From the report
Evidence
export const TOKENS = {
  SEND: new PublicKey("SENDdRQt..."),
Suggested fix, not tested

Keep one exported table in core, import it everywhere, and fix the RERERRAL_FEE typo once.

58. Low The priority-fee request is written out three times
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
const response = await fetch(
  `https://mainnet.helius-rpc.com/?api-key=${agent.config?.HELIUS_API_KEY}`,
Suggested fix, not tested

Extract one helper in core.

59. Low Ordinary results are logged as warnings, and failures are logged twice
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
console.warn(
  `No token account found for wallet ${wallet_address.toString()} ...
Suggested fix, not tested

Log once, at the boundary that handles the error, through an injectable logger.

60. Low CI ignores the lockfile
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
run: pnpm install --no-frozen-lockfile
Suggested fix, not tested

Use --frozen-lockfile, and publish from CI with provenance.

61. Low Scripts and tooling from the previous version remain and cannot run
First rating: Low · Reviewed rating: Low · Review: location checked, kept as rated
From the report
Evidence
  • the root generate script points at a missing file
  • .lintstagedrc targets ESLint, but the project uses Biome and the .husky hook directory is gitignored
import { SolanaAgentKit } from "../src";
Suggested fix, not tested

Port the duplicate-tool check to the current API and run it in CI, or delete these files.


65. Low Fallback API key is hard-coded
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/plugin-misc/src/allora/tools/get_all_topics.ts:18: an Allora key whose value starts UP-d. The same literal is in two sibling files.
From the report
Evidence
agent.config?.ALLORA_API_KEY || "UP-d…";
Suggested fix, not tested

Remove the literal, require the key from configuration, and rotate it.

66. Low Pump.fun launch never signs with the new mint keypair
First rating: High · Reviewed rating: Low · Review: rated too high
Location: packages/plugin-token/src/pumpfun/tools/launchPumpfunToken.ts:140. The keypair is created at line 60.
From the report
Evidence
const mint = Keypair.generate();
...
const txHash = await signOrSendTX(agent, tx);
Why it matters

The create instruction needs the mint's signature, so no token can ever be launched.

Suggested fix, not tested

Call tx.sign([mint]) before the wallet signs, in both the send and sign-only branches.

Review: (Pump.fun launch, Manifest market creation, close-empty-accounts, compressed airdrop, NFT mint, SNS register) are all real and each tool fails closed: no mint signature (launchPumpfunToken.ts:130-140); no market keypair signature (manifest_trade.ts:45); token_2022[i] is undefined when i >= token_2022.length (close_empty_token_accounts.ts:34-36); the action calls sendCompressedAirdrop(mintAddress, ...) without agent and with strings (compressedAirdrop.ts:79), and the tool serialises a transaction with no blockhash (send_compressed_airdrop.ts:125); mint_nft.ts:73,84 sends twice; register_domain.ts:28-31 passes (owner, mint) to getAssociatedTokenAddressSync(mint, owner). Rated LOW only because nothing is lost; they would be MEDIUM in a product-quality ranking.

67. Low Pump.fun initial buy receives a SOL amount as a raw token amount
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
new BN(amount),
Why it matters

A value such as 0.1 is truncated to 0 or throws. The slippage and priority-fee inputs are dropped.

Suggested fix, not tested

Convert SOL to lamports, derive the token amount explicitly, and pass slippage and fee through.

71. Low New market account is never signed by its keypair
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
return [await signOrSendTX(agent, tx), marketKeypair.publicKey.toBase58()];
Why it matters

Creating the account requires the new account's signature, so market creation always fails.

Suggested fix, not tested

Call tx.partialSign(marketKeypair), or pass the keypair as an extra signer.

Review: (Pump.fun launch, Manifest market creation, close-empty-accounts, compressed airdrop, NFT mint, SNS register) are all real and each tool fails closed: no mint signature (launchPumpfunToken.ts:130-140); no market keypair signature (manifest_trade.ts:45); token_2022[i] is undefined when i >= token_2022.length (close_empty_token_accounts.ts:34-36); the action calls sendCompressedAirdrop(mintAddress, ...) without agent and with strings (compressedAirdrop.ts:79), and the tool serialises a transaction with no blockhash (send_compressed_airdrop.ts:125); mint_nft.ts:73,84 sends twice; register_domain.ts:28-31 passes (owner, mint) to getAssociatedTokenAddressSync(mint, owner). Rated LOW only because nothing is lost; they would be MEDIUM in a product-quality ranking.

72. Low Closing empty token accounts crashes when there are fewer than 40
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
for (let i = 0; i < Math.max(0, MAX_INSTRUCTIONS - spl_token.length); i++) {
  transaction.add(token_2022[i]);
Why it matters

undefined is added and throws, including when there is nothing to close.

Suggested fix, not tested

Bound the loop by token_2022.length, move the empty check above the loops, and report the number actually closed.

Review: (Pump.fun launch, Manifest market creation, close-empty-accounts, compressed airdrop, NFT mint, SNS register) are all real and each tool fails closed: no mint signature (launchPumpfunToken.ts:130-140); no market keypair signature (manifest_trade.ts:45); token_2022[i] is undefined when i >= token_2022.length (close_empty_token_accounts.ts:34-36); the action calls sendCompressedAirdrop(mintAddress, ...) without agent and with strings (compressedAirdrop.ts:79), and the tool serialises a transaction with no blockhash (send_compressed_airdrop.ts:125); mint_nft.ts:73,84 sends twice; register_domain.ts:28-31 passes (owner, mint) to getAssociatedTokenAddressSync(mint, owner). Rated LOW only because nothing is lost; they would be MEDIUM in a product-quality ranking.

73. Low Compressed-airdrop action calls the tool with the wrong arguments
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const txs = await sendCompressedAirdrop(
  mintAddress,
Why it matters

The agent argument is missing and strings are passed where PublicKeys are expected, so the action always fails. The tool also serialises a transaction that has no blockhash or fee payer (send_compressed_airdrop.ts:125).

Suggested fix, not tested

Pass the agent and PublicKey values, and build complete transactions.

Review: (Pump.fun launch, Manifest market creation, close-empty-accounts, compressed airdrop, NFT mint, SNS register) are all real and each tool fails closed: no mint signature (launchPumpfunToken.ts:130-140); no market keypair signature (manifest_trade.ts:45); token_2022[i] is undefined when i >= token_2022.length (close_empty_token_accounts.ts:34-36); the action calls sendCompressedAirdrop(mintAddress, ...) without agent and with strings (compressedAirdrop.ts:79), and the tool serialises a transaction with no blockhash (send_compressed_airdrop.ts:125); mint_nft.ts:73,84 sends twice; register_domain.ts:28-31 passes (owner, mint) to getAssociatedTokenAddressSync(mint, owner). Rated LOW only because nothing is lost; they would be MEDIUM in a product-quality ranking.

74. Low NFT mint sends the same transaction twice
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const sigOrTx = await signOrSendTX(agent, tx);
...
await signOrSendTX(agent, tx);
Why it matters

A successful mint ends in a duplicate-send error, and the agent may mint again.

Suggested fix, not tested

Delete the second call.

Review: (Pump.fun launch, Manifest market creation, close-empty-accounts, compressed airdrop, NFT mint, SNS register) are all real and each tool fails closed: no mint signature (launchPumpfunToken.ts:130-140); no market keypair signature (manifest_trade.ts:45); token_2022[i] is undefined when i >= token_2022.length (close_empty_token_accounts.ts:34-36); the action calls sendCompressedAirdrop(mintAddress, ...) without agent and with strings (compressedAirdrop.ts:79), and the tool serialises a transaction with no blockhash (send_compressed_airdrop.ts:125); mint_nft.ts:73,84 sends twice; register_domain.ts:28-31 passes (owner, mint) to getAssociatedTokenAddressSync(mint, owner). Rated LOW only because nothing is lost; they would be MEDIUM in a product-quality ranking.

75. Low Domain registration swaps the token-account arguments
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const buyerTokenAccount = getAssociatedTokenAddressSync(
  agent.wallet.publicKey,
  TOKENS.USDC,
Why it matters

The wallet is passed as the mint and USDC as the owner, so registration always fails.

Suggested fix, not tested

Use getAssociatedTokenAddressSync(TOKENS.USDC, agent.wallet.publicKey).

Review: (Pump.fun launch, Manifest market creation, close-empty-accounts, compressed airdrop, NFT mint, SNS register) are all real and each tool fails closed: no mint signature (launchPumpfunToken.ts:130-140); no market keypair signature (manifest_trade.ts:45); token_2022[i] is undefined when i >= token_2022.length (close_empty_token_accounts.ts:34-36); the action calls sendCompressedAirdrop(mintAddress, ...) without agent and with strings (compressedAirdrop.ts:79), and the tool serialises a transaction with no blockhash (send_compressed_airdrop.ts:125); mint_nft.ts:73,84 sends twice; register_domain.ts:28-31 passes (owner, mint) to getAssociatedTokenAddressSync(mint, owner). Rated LOW only because nothing is lost; they would be MEDIUM in a product-quality ranking.

76. Low Priority-fee lookup crashes when the RPC returns no samples
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
fees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[
  Math.floor(fees.length * feeTiers[feeTier])
].prioritizationFee,
Why it matters

On localnet, devnet or a quiet RPC, every send fails.

Suggested fix, not tested

Fall back to a default fee and clamp the index.

77. Low Each send signs twice, and a signed transaction goes to a third party for a fee estimate
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const signedTransaction = await agent.wallet.signTransaction(transaction);
...
return sendTx(
Why it matters

Browser and hardware wallets prompt the user several times. A valid signed transaction is handed to the fee-estimate provider.

Suggested fix, not tested

Sign once. Estimate fees from an unsigned serialisation.

78. Low Remote priority fee is paid with no cap, and the fee host is hard-coded to mainnet
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
priorityFee = data.result.priorityFeeEstimate;
Why it matters

An abnormal estimate is paid in full. On devnet with a Helius key configured, every send is blocked.

Suggested fix, not tested

Validate and cap the estimate, check response.ok, and pick the host from the configured cluster.

79. Low Amounts are converted to base units with floating-point maths
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
  • plugin-nft/src/metaplex/tools/deploy_token.ts:64
  • plugin-defi/src/fluxbeam/tools/create_pool.ts:30
  • plugin-misc/src/helius/tools/send_transaction_with_priority.ts:45
  • plugin-defi/src/pumpfunAmm/tools/removeLiquidity.ts:31, which also assumes 6 decimals
lamports: amount * LAMPORTS_PER_SOL,
Why it matters

1.1 * 1e9 is not an integer, so web3.js rejects ordinary amounts. Large values lose precision.

Suggested fix, not tested

Convert decimal strings to bigint using the mint's decimals.

Review: the report's example is wrong. 1.1 * 1e9 evaluates to exactly 1100000000. About 4.5% of two-decimal SOL amounts (for example 1.07 or 2.01) are not integers after the multiplication, and web3.js rejects them, so it fails closed.

82. Low Vercel AI tools are named by array index
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
tools[index.toString()] = tool({
Why it matters

The model sees tools named "0", "1" and so on, and the names change whenever plugins change. Descriptions are cut at 1,023 characters.

Suggested fix, not tested

Use tools[action.name].

83. Low Tool limit drops the 128th tool silently
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/core/src/langchain/index.ts:16. Same in the openai, vercel-ai and claude adapters.
From the report
Evidence
const tools = actions.slice(0, 127).map((action) => {
Suggested fix, not tested

Use one constant for the check, the slice and the message.

85. Low CoinGecko URLs end in "undefined" when no key is set
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
`.../search/trending${agent.config?.COINGECKO_DEMO_API_KEY && `?x_cg_demo_api_key=...`}`
Suggested fix, not tested

Use a ternary that falls back to "", and send the key in a header.

86. Low API key is written to logs through the raw HTTP error
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
console.error("Error creating Crossmint Amazon order:", error);
Why it matters

The logged error includes the request headers, and with them the production key.

Suggested fix, not tested

Log only the message, the status and the response body.

87. Low SPL branch of the priority-fee transfer serialises an empty, unsigned transaction
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/plugin-misc/src/helius/tools/send_transaction_with_priority.ts:126. Line 146 also creates the ATA with the non-idempotent instruction.
From the report
Evidence
transaction: bs58.encode(transaction.serialize()),
Why it matters

Every SPL transfer through this action fails. When the recipient already has a token account, it fails anyway.

Suggested fix, not tested

Add the instructions first, serialise with signature checks disabled for the estimate, and use the idempotent ATA instruction.

88. Low Committed debug script opens a real mainnet leveraged position
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const openResult = await test("OPEN_POSITION (2x long cbBTC/USDC, $2)", () =>
Why it matters

If the position lookup is slow, the position stays open with real funds and the errors are swallowed.

Suggested fix, not tested

Delete the script, or gate it behind an explicit opt-in on devnet and close the position in a finally block.

Review: packages/plugin-defi/package.json lists "files": ["dist"], so test-lavarage.mjs is not published.

89. Low Plugins pin old exact core versions as peers and also depend on core directly
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
"solana-agent-kit": "2.0.7"
Why it matters

Core is at 2.0.10, so installs hit peer conflicts. A second copy of core can be installed, which breaks shared class checks.

Suggested fix, not tested

Make core a peer dependency with a range only.

90. Low CommonJS require inside an ES-module package
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const { exportWallet } = require("@open-wallet-standard/core") as {
Why it matters

The package is "type": "module", and require is undefined in its ES-module build.

Suggested fix, not tested

Use a static import, createRequire, or a dynamic import().

92. Low Ticker lookup picks the token with the highest self-reported FDV
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
.sort((a: any, b: any) => (b.fdv || 0) - (a.fdv || 0));
Why it matters

A copycat token with an inflated supply wins the ranking, and value-moving actions then use its mint.

Suggested fix, not tested

Resolve tickers against a verified list, or require the mint address.

93. Low Per-transaction failures are swallowed in burn, close and merge flows
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
} catch (_error) {
  continue;
Why it matters

Partial or failed irreversible operations are reported as success.

Suggested fix, not tested

Collect a result per transaction and throw on failure.

94. Low Mayan action schema requires a field the tool never uses
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
inputAmount: z.number().positive("Input amount must be positive"),
Why it matters

Calls that follow the documented examples are rejected.

Suggested fix, not tested

Remove the field, or make it optional.

95. Low Jupiter swap ignores the user's slippage setting
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
_slippageBps: number = DEFAULT_OPTIONS.SLIPPAGE_BPS,
Why it matters

Users cannot limit swap slippage.

Suggested fix, not tested

Send slippageBps, or cap dynamic slippage with it.

96. Low Voltr withdraw requests the deposit endpoint
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
`https://voltr.xyz/api/remaining-accounts/deposit-strategy?vault=...`
Why it matters

Withdrawals revert, or run the wrong strategy instruction.

Suggested fix, not tested

Call the withdraw endpoint.

97. Low Drift stake-account check can never detect a missing account
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
await driftClient.connection.getAccountInfo(
  deriveInsuranceFundStakeAccount,
Why it matters

getAccountInfo returns null rather than throwing, so first-time stakes fail.

Suggested fix, not tested

Set shouldCreateAccount = info === null.

99. Low Vault withdrawal always uses 6-decimal precision
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
numberToSafeBN(amount, QUOTE_PRECISION),
Why it matters

For a 9-decimal vault, the amount is off by a factor of 1,000.

Suggested fix, not tested

Use the precision of the vault's asset.

100. Low Adrena short positions derive the long-position address
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: packages/plugin-defi/src/adrena/tools/adrena_perp_trading.ts:445 (open short). Also line 66 (close short).
From the report
Evidence
const position = AdrenaClient.findPositionAddress(owner, principalCustody, "long",
Why it matters

Short trades target the wrong account.

Suggested fix, not tested

Use "short".

101. Low Adrena leverage unit contradicts the protocol
First rating: Medium · Reviewed rating: Low · Review: could not be settled
From the report
Evidence
.describe("Leverage in basis points (2000 = 2x)")
Why it matters

On chain, 10,000 means 1x, so 2000 is 0.2x, and the confirmation text shows the wrong multiplier.

Suggested fix, not tested

Accept a multiplier and convert it once.

103. Low Bridge affiliate fee and recipient come from model input
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
affiliateFeeRecipient: params.affiliateFeeRecipient,
Why it matters

A prompt injection can divert a share of bridged funds.

Suggested fix, not tested

Take these values from trusted configuration only, with a cap.

104. Low Order ids are random floats
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
clientOrderId: Number(Math.random() * 1000),
Why it matters

A u64 id field receives a non-integer from a small range, so ids collide.

Suggested fix, not tested

Use an integer that increases monotonically or is generated with a cryptographic RNG.

105. Low Lavarage slippage has no bounds
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
slippageBps: z.number().optional().default(50)
Suggested fix, not tested

Add .int().min(1).max(500) or a configured ceiling.

106. Low Network is detected from a substring of the RPC URL
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
if (agent.connection.rpcEndpoint.includes("mainnet")) {
Why it matters

Valid custom endpoints are rejected as an unsupported network.

Suggested fix, not tested

Use the genesis hash or explicit configuration.

111. Low Typing animation restarts itself after unmount
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
]).start(() => {
  // Loop the animation
  animateDots();
Why it matters

stopAnimation completes the sequence, which starts the loop again, so it costs battery and CPU indefinitely.

Suggested fix, not tested

Restart only when finished is true.

112. Low Clipboard is imported from React Native core
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
Clipboard.setString(walletAddress);
Why it matters

The core Clipboard has been removed, so copying the address fails.

Suggested fix, not tested

Use expo-clipboard, which is already a dependency.

113. Low Helius key is bundled in the mobile app and placed in URLs
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
...transactions?api-key=${HELIUS_API_KEY}`;
Suggested fix, not tested

Proxy the calls through the app's server, and rotate the key.

114. Low Android release build is signed with the debug keystore
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
signingConfig signingConfigs.debug
Why it matters

The keystore and its password are public, so anyone can sign a replacement update for this wallet app.

Suggested fix, not tested

Add a release signing configuration fed from CI secrets.

Review: are the stock Expo/React Native prebuild defaults (debug keystore for release with a "Caution! generate your own keystore" comment; overlay permission in the main manifest).

116. Low Chat-changing server functions skip authentication and ownership checks
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
  • privy-agent-tanstack-starter/src/functions/chats.ts:93
  • examples/misc/privy-server-wallet-agent/app/(chat)/actions.ts:37 and 46, which also changes chat visibility
  • crossmint-sak-v2/src/functions/chats.ts:133, where saving a message checks only that some session exists
export const deleteTrailingMessages = createServerFn({ method: "GET" })
Why it matters

Anyone can delete other users' history or make their chats public. Using GET also makes the delete triggerable by cross-site requests.

Suggested fix, not tested

Require a session, check that the chat belongs to the caller, and use POST or DELETE.

117. Low Session-sealing secret is hard-coded
First rating: High · Reviewed rating: Low · Review: rated too high
From the report
Evidence
password: "Chan…",
Why it matters

Anyone with the repository can forge session cookies.

Suggested fix, not tested

Load a random secret from the environment and refuse to start without it.

Review: the "hard-coded secret" is the string ChangeThisBeforeShippingToProdOrYouWillBeFired, an explicit placeholder (same in privy-agent-tanstack-starter).

118. Low Unauthenticated file upload with no quota
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
router.post('/upload', upload.single('file'), uploadFile);
Suggested fix, not tested

Require authentication, add per-user quotas, and serve uploads from a separate origin.

119. Low Saved message parts are silently replaced with a placeholder
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
text: 'Message content unavailable'
Why it matters

Tool, image and reasoning parts are lost on save.

Suggested fix, not tested

Persist the real part shapes, and reject invalid input.

Review: the placeholder text is only inserted into empty text fields (...part is kept), so non-text parts are not dropped.

124. Low Bot timeout never covers the stream
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: examples/social/tg-bot-starter/advanced-tg-bot/src/app/api/bot/route.ts:149. Same in group-tg-bot and basic-tg-bot.
From the report
Evidence
for await (const chunk of (await Promise.race([
  stream,
Why it matters

The stream is already resolved when the race starts, so a hung model call runs until the platform kills the function.

Suggested fix, not tested

Apply the deadline to the whole iteration.

125. Low Invite redemption and keypair creation race
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
if (codeData?.usedBy != null) {
Why it matters

One invite code can be redeemed several times. A second concurrent setDoc replaces a keypair whose address was already shown to the user, so funds sent to it are lost.

Suggested fix, not tested

Use Firestore transactions, or create-if-absent writes.

126. Low Discord history stores bot replies as user messages and grows forever
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
userChatHistory.push(new HumanMessage(agentMessage));
Suggested fix, not tested

Store replies as AI messages, cap the history, and persist it.

127. Low Wallet delete command removes the only link to a funded wallet
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
db.delete(usersTable).where(eq(usersTable.id, userId))
Why it matters
  • There is no confirmation and no balance check, so funds become unreachable.
  • The messages that the help text promises to delete remain.
  • History is read in no defined order and with no limit.
Suggested fix, not tested

Ask for confirmation, check the balance, delete the messages, and read history ordered and limited.

128. Low Autonomous loop with a funded key has no spending limit and unbounded memory
First rating: Medium · Reviewed rating: Low · Review: rated too high
Location: examples/defi/market-making-agent/index.ts:110. Same in persistent-agent and orbofi-personality-engine.
From the report
Evidence
while (true) {
  try {
    const thought = "Be creative and do something interesting on the blockchain. " +
Suggested fix, not tested

Add spending caps, allow-lists and a dry-run default, and trim the conversation history.

129. Low Wormhole example uses the removed constructor and builds a RegExp from user input
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const solanaAgent = new SolanaAgentKit(
  process.env.SOLANA_PRIVATE_KEY!,
Why it matters

The route fails at start. Once that is fixed, special characters in user input throw or backtrack badly.

Suggested fix, not tested

Migrate to the current API, and escape the input or use startsWith.

Review: the wormhole example imports solana-agent-kit/dist/langchain and calls new SolanaAgentKit(privateKey, rpc, openAiKey), the removed v1 API; the whole example is stale.

130. Low Discord example reads two different RPC variables
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
const keypairWallet = new KeypairWallet(keypair, process.env.RPC_URL as string);
Why it matters

The template defines only SOLANA_RPC_URL, so the wallet has no RPC.

Suggested fix, not tested

Use one variable and fail fast if it is missing.

Review: discord-bot-starter/.env.template defines only SOLANA_RPC_URL, while KeypairWallet is built from RPC_URL (index.ts:28), so new Connection(undefined) fails on send.

131. Low Token deploy ignores the requested authority options
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
if (authority?.isMutable !== undefined) {
  defaultAuthority.isMutable = authority?.isMutable;
Why it matters

Tokens are created mutable, with the wallet as update authority, whatever the user asked for.

Suggested fix, not tested

Pass the options into the create call, or reject them.

132. Low 3Land tool calls the SDK with empty credentials and reports the payer as the NFT
First rating: Medium · Reviewed rating: Low · Review: could not be settled
From the report
Evidence
const result = await createCollection({}, collectionOpts);
Suggested fix, not tested

Provide the agent's signer, and return the minted asset's address.

133. Low Multisig treasury transfer uses the member wallet as sender and always vault 0
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
fromPubkey: agent.wallet.publicKey,
Suggested fix, not tested

Use the vault PDA as the sender, and pass the vault index through.

135. Low Message signature is wrapped as text bytes instead of being decoded
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
return Buffer.from(signature);
Suggested fix, not tested

Decode with the encoding the provider documents, and check that the result is 64 bytes.

136. Low Wallet app requests the overlay and audio permissions in its main manifest
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Why it matters

Drawing over other apps makes overlay attacks on the signing prompts possible.

Suggested fix, not tested

Keep that permission in the debug manifest only, and drop the permissions the app does not use.

Review: are the stock Expo/React Native prebuild defaults (debug keystore for release with a "Caution! generate your own keystore" comment; overlay permission in the main manifest).

137. Low Signer stubs return the unsigned transaction as if it were signed
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
signTransaction: async (tx) => {
  return tx;
Suggested fix, not tested

Throw "not supported", or implement real signing.

138. Low Server-wallet signAllTransactions builds malformed transactions
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
signatures: [signedTransaction],
Suggested fix, not tested

Deserialise and return the signed transaction. Make the stub sendTransaction throw.

139. Low Example prints the full keypair, including the secret key
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
console.log("keyPair", keyPair);
Suggested fix, not tested

Log only the public key.

140. Low DEX starter shows wrong numbers and lets the model swap before the user confirms
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
Exchange Rate: ${formatNumber(result.priceImpactPercentage)}%
Suggested fix, not tested

Compute the rate from both token amounts. Give the model only the quote tool, and run the swap only after an explicit yes.


Excluded on review (2)

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.