🤦♂️ The Mistake I Made On Day One
I was just playing around with transactions on Solana last week, and thought of just trying to send a huge amount (500 SOL) to my friend's wallet 💸 — when I only had about 0.5 SOL with me. I for sure knew that the transaction should fail, but I was like — why not just try it out of curiosity. I made the transaction call, the transaction failed. I checked my balance, and it was now 0.499995. 🤯
Huh — that was strange. If the transaction failed, how did my balance drop?
That ◎0.000005 started an itch I had to scratch. 🕵️♂️ Coming from Ethereum and Polygon, I assumed I understood what a transaction was. Turns out I had the shape right, but the soul completely wrong.
🤔 First Instinct: "This Looks Like an HTTP Request"
And honestly? It kinda does. When I started building transactions, the structure felt immediately familiar:
| Web2 HTTP Request | Solana Transaction |
|---|---|
| Headers | Message Header |
| Auth / Bearer Token | Signature |
| Request Body | Account Keys + Instructions |
| Request ID | Transaction Signature |
That surface-level similarity is real — and it's also the thing that will trip you if you stop there.
Because a Solana transaction is not a request. It's a job submission to a distributed processor.
The Better Analogy: A Distributed Job Queue
Here's how my Web2 brain eventually re-framed it:
Imagine a job/task processor service. You want something done. So you:
- Build a request payload containing the job name, parameters, and an access token
- Submit it to the service
- Get back a jobId
- Poll the service for status:
queued → processing → success/failure - The service persists the job metadata — payload, traces, logs, output — all of it
Now put on your Web3 goggles and look at the same picture:
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(signer, tx),
// 👇 your session token
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) =>
appendTransactionMessageInstruction(
getTransferSolInstruction({ // 👈 job name
source: signer,
destination,
amount: amountInLamports, // 👈 job parameters
}),
tx,
),
);
const signedTx = await signTransactionMessageWithSigners(transactionMessage);
const signature = getSignatureFromTransaction(signedTx); // 👈 your jobId
See setTransactionMessageLifetimeUsingBlockhash? That's setting your session token — a recent blockhash that acts exactly like a CSRF token with a validity window. It expires in ~60–90 seconds. If your transaction isn't processed in that window, it's dead. No retry, no extension.
The mapping now looks like this:
| Job Processor Concept | Solana Equivalent |
|---|---|
| Job name / Task reference | Instructions |
| Job parameters | Account Keys |
| Session token (CSRF) | Recent Blockhash |
| Access token / Checksum | Signature |
| JobId | Transaction Signature |
| Job status (queued → done) | Commitment level |
| Persisted job metadata | On-chain transaction record |
Submitting the job looks like this:
const wireTransaction = getBase64EncodedWireTransaction(signedTx);
await rpc.sendTransaction(wireTransaction, { encoding: "base64" }).send();
You're handing the job off. And now you wait.
👀 Where the Analogy Gets Interesting: Peer Review
In Web2, once the job processor says "done" — you trust it. That's it. Single source of truth.
Solana does something different. And this is the part that genuinely changed how I think about decentralised systems.
When a Leader Node (think: the primary job processor) picks up your transaction and runs it, it packages it into a block — a batch of jobs — and broadcasts that block to the network. 🚚
From here, the validators (the peers) pick up that block, re-execute every transaction independently, and vote on whether the results are valid.
The commitment lifecycle goes like this:
-
processed→ Leader node ran it, block is broadcast -
confirmed→ 66%+ of validators have voted it valid ✅ -
finalized→ 31+ more blocks have been built on top. It's permanent.
Here's what that looks like in practice, straight from my devnet terminal:
Reading the output above:
Recent Blockhash→ the CSRF-style session token, set at build timeSignature 0→ your jobId, returned the moment you submittedInstruction 0 / Transfer { lamports: 100000000 }→ the job definition — what to run, with what parametersFee: ◎0.000005→ the cost of execution (we'll come back to this)Finalized→ peer review complete. It's on-chain. It's permanent.
The interesting bit here is Peer Review.
In a Web2 environment, a malicious actor could potentially manipulate the response of a single job processor — intercept it, tamper with it, return a fake success. With Solana, to manipulate a result you'd need to corrupt 66%+ of the entire validator network simultaneously. That's not a bug fix, that's a coordinated attack at civilizational scale.
That's why decentralisation isn't just a philosophy. It's a security model.
⚠️ The Part Nobody Warned Me About: Failure Isn't Free
Let's have a look at this transaction:
Transfer { lamports: 500000000000 }→ just goofing around with 500 SOLStatus: Error processing Instruction 0→ states the failureTransfer: insufficient lamports→ that's right, I didn't have enough balanceFee: ◎0.000005→ wait what? — but you didn't make it throughTransaction failed→ ah you know that, still charging me?
Here's what I wish someone had told me upfront:
The network fairly compensates every participant — the Leader Node that ran your batch, the validators that peer-reviewed it. Everyone did work. Everyone gets paid. Whether your transaction succeeded or failed.
That's not a gotcha. It's honest accounting. But it will catch you off guard the first time.
Unlike me — don't make the mistake of assuming that failed transactions might be free.
🧠 The Mental Model, In One Sentence
Solana transactions are like HTTP requests in shape, but distributed job submissions in soul — with a session token that expires, a network of peer reviewers instead of a single server, and a fee structure that charges for effort, not outcome.
🚀 What I'm Building Next
Next week is all about diving deeper into the account model in Solana. Excited to make more mistakes and learn something with each fall.
This is Day 20 of my MLH 100 Days of Solana challenge. I'm a Web2 backend developer exploring Solana purely out of curiosity — and apparently, stubbornness.
Code from this series: github.com/denilbhatt0814/MLH-100-days-of-solana


Top comments (0)