DEV Community

TxDesk
TxDesk

Posted on

Your AI assistant shouldn't guess. In crypto, a confident wrong answer costs you your wallet.

Ask most AI assistants about your transaction and they will autocomplete an answer. The chain is right there. Read it.

A general-purpose model answers from training data. It learned a snapshot of text and it responds by predicting a plausible continuation. Ask it about a specific transaction hash or a specific contract address and it will produce something fluent and confident. It has no live view of the chain. It is autocompleting an answer that looks right.

In most domains, a confident wrong answer is an awkward correction. In crypto it is a drained wallet. That asymmetry is the entire reason I built TxDesk the way I did. When the cost of a wrong answer is your money, the model cannot be the source of truth. The chain has to be.
So here is what "reading the chain" actually means inside TxDesk, concretely.

When you ask about a transaction, it fetches that transaction from the chain's own RPC and block-explorer data, resolves the contract's interface, and decodes the real thing: the function that was called, the parameters it was called with, the events it emitted such as transfers and approvals. On EVM chains, if the transaction failed, it re-simulates the call to recover the actual revert reason when that revert still reproduces against current state. It is not describing how transactions like yours usually go. It is reading yours.

When you ask about an approval, it does not recite how token approvals work in general. It reads your wallet's current on-chain allowance for that token and that spender, right now, and re-checks it so it will not report an approval you already revoked. If the allowance is large enough to be effectively infinite, it says so, because an unlimited approval means the spender can move that token until you revoke it.

When you ask about a contract, it pulls structured signals: whether the contract is verified on the explorer, how old it is, whether it is a proxy. The model's job is to explain that data in plain language, not to remember facts about the world.

The part I care most about is the honest boundary. A tool like this earns trust by being explicit about the edge of what it actually pulled. "Here is what I can see on-chain, and here is what I cannot verify" beats a smooth answer that hides the seam. That shows up in two concrete ways.

First, it does not treat the chain's free text as proof of anything. A token's symbol, a contract's name, a transfer memo, a spender's label: every one of those is a string chosen by whoever deployed the contract or crafted the transaction. An attacker can name their contract "Safe Router" or issue a token whose symbol reads "verified airdrop." So none of those fields are ever used as safety or verification evidence. A name that claims to be trusted is treated as an unverified assertion by an untrusted party, because that is precisely what it is. Safety calls are based only on structured signals: the explorer's verification flag, on-chain age, computed risk factors. A label cannot talk the tool out of a warning, and an attacker cannot lower their own risk score by relabeling their spender.

Second, when it cannot read the data, it says so, instead of filling the gap. If the underlying read fails, it tells you it cannot check right now. It does not return an empty result that quietly implies you are clean. On one chain where approval data can come from two different sources, if both are unreachable the answer is an explicit "unknown," never a falsely reassuring "no approvals found." A blank is not a clean bill of health, and the tool refuses to let it read like one.

There are two ways to be wrong in this category of product. One is an assistant that does support but cannot read a chain, so it is friendly and uninformed. The other is a generic agent that reads nothing and guesses, so it is confident and uninformed. I built TxDesk to do the opposite of both: read first, then talk. It is read-only with respect to your funds, it never holds a key, and it works across more than forty chains. But the read-first part is the whole point.

In a domain where a confident wrong answer costs you your wallet, the model should not be the thing you trust. The chain should. The model's job is to read it to you, and to be honest about the edges of what it could see.

Top comments (7)

Collapse
 
jugeni profile image
Mike Czerwinski

The "placed=1 / placed_ok=0" version of this bit me last week and the shape is identical. Live trading system, Hyperliquid. The placement layer wrote attempted=1 and the execution-attest layer wrote placed_ok=0, and the dashboard read the first one. For ~4 days, ~20-30 cycles, zero positions actually opened, and ZERO execution_error rows in the events table — the failure was a silent path, no row got written because the code that would have written it never ran. Same green-pixel collapse, different chain.

The pattern that landed for me, after the fact, is two-counter discipline at the attest layer, not the action layer. attempted and succeeded are different rows, both required to write before the next state transition. The UI then renders the difference, not the success. "5 attempted, 3 succeeded" is a normal display state; "5 attempted, — succeeded" (literal em-dash, not zero, not blank) is the unknown state. Treating zero and unknown as visually distinct at the rendering layer forces the read path to carry the distinction all the way down. The bug class you described is almost always the unknown getting mapped to zero one layer below the UI, where it stops being recoverable.

The harder version of your question is: how do you force the upstream RPC adapter to surface its own partial-read state in a typed way the UI can render. I have not seen a clean cross-chain pattern there. Each provider lies differently about what it just did or didn't return. Curious if you settled on a shape for that, given 46 chains.

(Wrote up the trading version of this under #ai today, will link once it lands.)

Collapse
 
txdesk profile image
TxDesk

the silent-path version is the worst one because the absence reads as health, no error row written means nothing looks wrong, the failure is invisible by construction. two counters at the attest layer is the right fix, and rendering the difference rather than the success is the part people skip.

on the cross-chain RPC question, no universal pattern, and i don't think there is one. each provider reports a partial or degraded read differently, so the only thing that's held for me is not trusting any provider's own status and normalizing every response into one internal shape before anything downstream reads it: a result is either a value the system verified, or it's explicitly marked incomplete, never an empty that gets quietly read as zero. it's per-provider adapter work, not a clean abstraction, you write the translation for each one and treat anything you haven't explicitly classified as incomplete by default.

look forward to the trading writeup.

Collapse
 
jugeni profile image
Mike Czerwinski

The "incomplete by default" classification rule is the load-bearing part. Most adapter layers I've seen do the opposite, default to "value 0" or empty-string and rely on the caller to notice. Inverting that default is what makes the silent path stop being silent.

The per-provider adapter work is unavoidable on the trade-execution side too. Each exchange wraps order state slightly differently. Hyperliquid's HL_RECONCILE event, Binance's post-trade fill webhook, dYdX's order lifecycle messages, all carry similar intent but different schemas and different failure modes. The fix that scaled for me was the same shape you describe: normalize every exchange response to one internal record before any strategy logic reads it, with status verified or incomplete or failed and the underlying evidence attached. Anything I haven't classified is incomplete by default. The strategy never sees raw provider output.

Writeup is going up tonight. Will share the link.

Collapse
 
nazar-boyko profile image
Nazar Boyko

Two things here are sharp. Treating a token symbol or contract name as an unverified claim from an untrusted party is the right call, because those strings are picked by whoever deployed the thing and an attacker will happily name a contract "Safe Router". The part that matters most to me is the empty result. A failed read that returns "no approvals found" instead of "I couldn't check" is the kind of false reassurance that does real damage, and not only in crypto. Plenty of dashboards quietly turn a timeout into a green checkmark, and you've drawn that line in exactly the right place.

Collapse
 
txdesk profile image
TxDesk

the empty-result point is the one i think generalizes furthest, and you named exactly why: "no approvals found" and "i couldn't check" are different states that get collapsed into the same green pixel. the collapse is almost always silent, a timeout, a rate-limit, a partial RPC response, and the system has no incentive to surface it because the happy path looks identical. crypto just makes the cost legible fast, you lose the wallet. but you are right that it is everywhere, the dashboard that turns a failed health check into a checkmark is the same bug with a slower blast radius. the discipline i landed on is that a read either returns data or it returns its own failure, never a default that happens to look like success. curious if you have seen a clean pattern for forcing that distinction at the UI layer, since that is where it usually gets flattened.

Collapse
 
ryanjordan profile image
Ryan Jordan

But you know that's what they're doing every single word guessing

Collapse
 
txdesk profile image
TxDesk

yeah, fair, at the base it's a prediction tool, every token included. i won't argue that. the distinction i'd draw is what it's predicting from. predicting a balance from training priors is guessing what it probably is. predicting over a value a tool call just handed it is transcribing a number it didn't invent. the prediction never stops, it's just constrained to read a fetched result instead of recalling a likely one.

so "doesn't guess" is loose, you're right. the precise version is it doesn't answer live-data questions from its own weights, it's made to fetch and read. it can still misread what it fetched, that's a real failure mode i won't paper over. but the number came from the chain, not from what a balance like this tends to look like. narrower and more specific than "it doesn't guess," and that's the better way to put it.