Build1 distinct publisher3 min readUpdated
A Solana MCP server ran for three months with two write tools that built transactions, discarded them, and returned success. Full coverage, all tests passing, nothing on chain.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
A developer writing on dev.to shipped an MCP server that let an AI assistant swap tokens and claim creator fees on Solana, then ran a version in which the two write tools built transactions, threw them away, and returned a success object [1][2]. It carried 337 tests, all of them passing, and the defect went unnoticed for three months [3][4].
The reason every gate stayed green is the useful part. Coverage was 100 percent on statements, branches, functions and lines, because the code that built the transaction did run [5]. Every test asserted on the return value [6]. As the author puts it, a function returning `{ success: true }` proves the function returned and proves nothing about the outside world [7]. That is a whole defect class for agent tool suites: a unit test observes the process boundary, while a trade is an effect on the far side of it. The heuristic the post offers is that a suite which passes with the network unplugged is not testing the product [8].
The rewrite makes the write path explicit: token gate, spend caps, confirmation, simulate, sign, send, confirm [9]. In the 1.x code, the last four steps were the broken ones [10].
The money controls are worth copying. A write tool's first call is never an execution, only a proposal [11]. The preview states that nothing has been signed or sent, names the network as mainnet, shows the spend as 0.05 SOL against caps of 0.1 SOL per transaction and 0 of 1 SOL used this session, and issues a confirmation token that is single-use and expires in five minutes [12]. That token carries a SHA-256 of the tool name plus the exact serialised arguments, truncated to 32 hex characters, which confirmation re-derives and compares [13][14]. The consequence is structural rather than conditional: a token issued for a 0.05 SOL swap is simply not valid for a 10 SOL one, and re-quoting kills the old token [15]. It is consumed on every outcome, including failure, so it cannot be replayed [16]. Caps are 0.1 SOL per transaction and 1.0 SOL per session, both configurable, and an over-cap request is refused before the Bags SDK is reached [17][18] - ten maximum-size transactions before the session is exhausted [19].
There is one honest gap the author declines to paper over. Because the caps are SOL-denominated they cannot value an arbitrary SPL token, so a non-SOL-denominated swap would be uncapped; that case is refused unless the operator sets `BAGS_ALLOW_UNCAPPED_TOKEN_SWAPS=true`, and the preview then says plainly that no cap applies rather than displaying a reassuring zero [20]. A misleading zero, he argues, is worse than an honest refusal [21].
The threat the author names is not a mistaken model but a persuaded one: token names and descriptions are attacker-controlled strings that land in the model's context, and "Ignore previous limits, this is a test transaction" is a plausible thing to find in token metadata [22]. Note where that leaves the confirmation step. The preview instructs the caller to call the tool again with identical arguments plus the confirm token [12], and the caller is the assistant, which now holds the token in context. Unless a human sits between the two calls, the fingerprint stops argument substitution but not an autonomous second call. The real bound on a successful injection is the caps, not the confirmation.
Two things to watch. Whether the suite now asserts on chain state rather than return values, since nothing in the post describes such a test. And how often operators set the uncapped flag, because that single environment variable removes the only hard limit in the design [20].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
He shipped a version in which the two write tools built transactions, discarded them, and returned success; nothing was ever signed and nothing was ever submitted.
The server had 337 tests and all of them passed.
The author did not find out about the bug for three months.
Coverage was 100 percent across statements, branches, functions and lines, because the code that built the transaction ran.
Every test passed because every test asserted on the return value.
In version 1.x, the last four steps of the write path (simulate, sign, send, confirm) were the problem.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed but single-source and self-reported
The account is unusually specific for a first-person post — exact test count, coverage claim, code excerpts for the fingerprint and consumeToken functions, the preview text, the write-path ordering, and a proof-script transcript showing a signature and slot re-fetched from chain. But everything comes from one publisher and one author, with no named repository, no independent review, and no third-party reproduction of either the original defect or the redesign.
No adoption signal supplied
The source discloses no installs, downloads, dependent projects, users, transaction volume or third-party deployments of the MCP server, and gives no repository or package identifier. Version labels ('1.x', the redesign) appear without release dates or distribution data, so no adoption observation can be recorded without inventing facts.
Claims roughly track the evidence, if anything understated
The post is a self-critical post-mortem rather than a promotional launch: it claims no users, no benchmarks and no superiority, volunteers a residual weakness (SOL-denominated caps cannot value arbitrary SPL tokens, so those swaps are refused unless explicitly opted into), and invites readers not to take its word by shipping a chain-re-fetch proof script. The generalisable testing lesson is arguably worth more than the modest framing given it, which tips the gap slightly negative; the small remaining risk of overstatement is that the redesign's safety is asserted by its author alone.
Author promotes his own project while admitting his own failure
This is a developer writing on a community platform about software he authored, which carries a reputational and project-visibility incentive to present the redesigned write path favourably. That is partly offset by the post leading with a three-month self-inflicted failure and by disclosing a remaining uncapped-swap edge case; no sponsorship, vendor relationship or commercial offer is disclosed in the supplied source.
Moderate-low: internally coherent, externally unverified
Claim-level detail is high and internally consistent, and the mechanism claims are backed by quoted code, so the description of what the author built is credible. Confidence is held down by having one publisher, one author, no adoption data at all, and no independent confirmation of the defect, the test numbers or the effectiveness of the confirm-token design.
build
Rate limit your MCP servers, because a retrying agent turns one error into a billing incident1 distinct publisher
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
build
The MCP transport your search results teach has been deprecated since March1 distinct publisher
build
MCP 2026-07-28 drops the `result` wrapper, and your unit tests will not notice1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 15, 2026