<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Tanisha fonseca</title>
    <description>The latest articles on Forem by Tanisha fonseca (@tanisha_fonseca).</description>
    <link>https://forem.com/tanisha_fonseca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3822717%2Fdd1c1711-c737-42e7-a9de-c42ad309f783.png</url>
      <title>Forem: Tanisha fonseca</title>
      <link>https://forem.com/tanisha_fonseca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tanisha_fonseca"/>
    <language>en</language>
    <item>
      <title>Solana's Account Model Explained for Web2 Developers</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Mon, 18 May 2026 03:36:32 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/solanas-account-model-explained-for-web2-developers-10a2</link>
      <guid>https://forem.com/tanisha_fonseca/solanas-account-model-explained-for-web2-developers-10a2</guid>
      <description>&lt;h2&gt;
  
  
  Solana's Account Model Explained for Web2 Developers
&lt;/h2&gt;

&lt;p&gt;If you're coming from web development, Solana's account model is probably the first thing that will genuinely confuse you and then, once it clicks, the first thing that will genuinely impress you.&lt;/p&gt;

&lt;p&gt;Let me save you a few days of head-scratching.&lt;/p&gt;




&lt;h2&gt;
  
  
  Think of It Like a Filesystem
&lt;/h2&gt;

&lt;p&gt;Before diving into the technical details, here's an analogy that will carry you a long way:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solana is a filesystem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every account is a file. Each file has metadata (owner, permissions, size) and contents (data). Programs are like executable files &lt;code&gt;chmod +x&lt;/code&gt; style. Data accounts are the documents those programs read and write. And the System Program? That's the kernel. It creates files, manages ownership, and handles the lowest-level operations.&lt;/p&gt;

&lt;p&gt;Keep that mental model handy. We'll come back to it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Everything is an Account
&lt;/h2&gt;

&lt;p&gt;In Ethereum, there's a distinction between "externally owned accounts" (wallets controlled by private keys) and "contract accounts" (smart contracts). Different types, different rules.&lt;/p&gt;

&lt;p&gt;Solana doesn't do that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everything is an account.&lt;/strong&gt; Your wallet? An account. A smart contract (called a "program" in Solana)? An account. The token balance you hold? Stored in... an account. All of this lives in one giant flat key-value store where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key&lt;/strong&gt; = a 32-byte public address&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value&lt;/strong&gt; = the account itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple. Uniform. Powerful.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Five Fields Every Account Has
&lt;/h2&gt;

&lt;p&gt;Pop open a Solana account in the CLI and you'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;solana account 11111111111111111111111111111111
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lamports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NativeLoader1111111111111111111111111111111"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"executable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rentEpoch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18446744073709551615&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every single account on Solana — whether it's a wallet, a program, or a piece of stored state — has exactly these five fields:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;lamports&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the SOL balance, but measured in the smallest unit: lamports. &lt;strong&gt;1 SOL = 1,000,000,000 lamports&lt;/strong&gt; (one billion). Think of it like wei to ether, or satoshis to bitcoin. It's just a &lt;code&gt;u64&lt;/code&gt; integer under the hood.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;data&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A raw byte array. This is where the interesting stuff lives. For wallets, it's empty. For programs, it holds compiled bytecode. For data accounts (more on those in a moment), it holds whatever state the owning program decides to write there.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;owner&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The program address that "owns" this account. This is the key to Solana's security model: &lt;strong&gt;only the owner program can modify this account's data or debit its lamports.&lt;/strong&gt; Anyone can add lamports (credit), but only the owner can take them away or change the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;code&gt;executable&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A boolean. &lt;code&gt;true&lt;/code&gt; means this account holds a deployable program. &lt;code&gt;false&lt;/code&gt; means it's a data/state account. Once set to &lt;code&gt;true&lt;/code&gt;, the data becomes immutable — you can't accidentally overwrite a running program.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;code&gt;rentEpoch&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This field is deprecated. You'll always see it set to the max &lt;code&gt;u64&lt;/code&gt; value (&lt;code&gt;18446744073709551615&lt;/code&gt;). Ignore it — it's a legacy artifact from how rent collection used to work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ownership Rules (and Why They Matter)
&lt;/h2&gt;

&lt;p&gt;Here's Solana's security model in two sentences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Only the owner program can modify an account's data or debit lamports.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anyone can credit lamports to any writable account.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. That's the whole access control system.&lt;/p&gt;

&lt;p&gt;In web2 terms: the owner is like a Unix file owner with exclusive write permissions. Other programs can look, but they can't touch unless they own it.&lt;/p&gt;

&lt;p&gt;This is why, when you interact with a Solana program (say, a DeFi protocol), you have to sign a transaction that authorizes that program to act on accounts you own. The program can only write to accounts where it is listed as &lt;code&gt;owner&lt;/code&gt; — it can never reach into your wallet uninvited.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Surprising Part: Programs Don't Store Their Own State
&lt;/h2&gt;

&lt;p&gt;This is the concept that trips up almost every Web2 developer the first time.&lt;/p&gt;

&lt;p&gt;In most backend frameworks, state lives inside the application. Your Node.js server has in-memory state, or it queries a database it owns. Either way, the program and its data are tightly coupled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Solana, programs are stateless.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Solana program stores its compiled code in one account (marked &lt;code&gt;executable: true&lt;/code&gt;). Any data it needs to track — user balances, game state, protocol configuration — lives in completely separate accounts that the program owns.&lt;/p&gt;

&lt;p&gt;The analogy: imagine a REST API (the program) that never stores anything locally. Every request reads from and writes to a database (the data accounts). The API logic and the database are cleanly separated. The "database" is owned by the API, but the data itself lives outside the code.&lt;/p&gt;

&lt;p&gt;In practice, this means when you call a Solana program, you have to tell it &lt;em&gt;which accounts&lt;/em&gt; to read and write. You pass them in as part of the transaction. The program can't go looking for its own data — you have to bring it.&lt;/p&gt;

&lt;p&gt;This feels weird at first. Then you realize it's what makes Solana blazing fast: the runtime knows exactly which accounts a transaction will touch before it even executes, so it can parallelize transactions that don't share accounts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rent Exemption: Paying for Space on Chain
&lt;/h2&gt;

&lt;p&gt;Every account occupies space on every Solana validator in the world. That's not free.&lt;/p&gt;

&lt;p&gt;To keep an account alive on-chain, it must hold a minimum lamport balance proportional to the size of its data. This is called being &lt;strong&gt;rent-exempt&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a basic account with no extra data, the threshold is roughly &lt;strong&gt;0.00089 SOL&lt;/strong&gt; (about $0.10–$0.20 at most price ranges). Accounts above this threshold stay on-chain indefinitely. Accounts below it... don't exist in the first place, because modern Solana tooling simply won't let you create underfunded accounts.&lt;/p&gt;

&lt;p&gt;You can check the exact minimum for any given data size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check rent for a 0-byte account&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;solana rent 0

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# Rent-exempt minimum: 0.00089088 SOL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or via RPC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.mainnet-beta.solana.com &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"getMinimumBalanceForRentExemption","params":[0]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The larger the account (more bytes in &lt;code&gt;data&lt;/code&gt;), the higher the minimum balance required. Close the account, reclaim the lamports — the space is yours to rent, not buy forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;Here's the mental model, fully assembled:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solana Concept&lt;/th&gt;
&lt;th&gt;Filesystem Analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Account&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;data&lt;/code&gt; field&lt;/td&gt;
&lt;td&gt;File contents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;owner&lt;/code&gt; field&lt;/td&gt;
&lt;td&gt;File owner (Unix permissions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;executable: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executable binary (&lt;code&gt;chmod +x&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Program account&lt;/td&gt;
&lt;td&gt;Application binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data account&lt;/td&gt;
&lt;td&gt;Data file read/written by the app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System Program&lt;/td&gt;
&lt;td&gt;OS kernel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lamports&lt;/td&gt;
&lt;td&gt;Disk quota&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you deploy a Solana program, you're writing an executable file to the filesystem. When that program creates accounts to track state, it's creating data files it owns. When users interact with it, they're running the binary and pointing it at the right files.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Design?
&lt;/h2&gt;

&lt;p&gt;The account model isn't arbitrary. It's designed for performance and parallelism.&lt;/p&gt;

&lt;p&gt;Because every transaction must declare its accounts upfront, the Solana runtime can look at two transactions and immediately know whether they can run in parallel (different accounts) or need to be serialized (same accounts). This is how Solana achieves the throughput it does — it's not just hardware, it's the data model.&lt;/p&gt;

&lt;p&gt;State separation also means programs are more composable. One program can pass an account it owns to another program, delegating access in controlled ways. The whole DeFi ecosystem on Solana is built on programs handing account authority back and forth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your First Mental Checkpoint
&lt;/h2&gt;

&lt;p&gt;Before you write your next line of Solana code, be able to answer these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's the difference between &lt;code&gt;executable: true&lt;/code&gt; and &lt;code&gt;executable: false&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Which account can modify which data, and why?&lt;/li&gt;
&lt;li&gt;Why do you have to pass accounts into a program call instead of letting the program find its own state?&lt;/li&gt;
&lt;li&gt;What happens to an account that falls below the rent-exempt threshold?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can answer those confidently, you understand the account model. Everything else  tokens, NFTs, PDAs, CPIs — is built on top of exactly this foundation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is part of my #100DaysOfSolana journey. If you're on a similar path, drop a comment always happy to compare notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>mlh</category>
      <category>web3</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Solana transactions expire and nobody warned me 💀</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Fri, 15 May 2026 21:47:01 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/solana-transactions-expire-and-nobody-warned-me-1cnh</link>
      <guid>https://forem.com/tanisha_fonseca/solana-transactions-expire-and-nobody-warned-me-1cnh</guid>
      <description>&lt;p&gt;okay so I need to talk about something that genuinely caught me off guard during #100DaysOfSolana and I feel like nobody explains it properly so here we go&lt;/p&gt;

&lt;p&gt;you know how in Web2, if you build an API call and it fails, you just... retry it? same request, same body, send again. worst case you get a duplicate, you handle it with idempotency keys, whatever the &lt;em&gt;request itself&lt;/em&gt; doesn't have an expiry date baked in&lt;/p&gt;

&lt;p&gt;Solana transactions have an expiry date baked in&lt;/p&gt;

&lt;p&gt;like structurally. at the protocol level. you cannot opt out of it&lt;/p&gt;

&lt;p&gt;let me explain because this is actually kind of wild&lt;/p&gt;




&lt;h2&gt;
  
  
  the blockhash thing
&lt;/h2&gt;

&lt;p&gt;every Solana transaction contains something called a &lt;strong&gt;recent blockhash&lt;/strong&gt;. it's a 32-byte hash of a recent block, and it's required you literally cannot construct a valid transaction without one&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latestBlockhash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestBlockhash&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;createTransactionMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTransactionMessageFeePayerSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTransactionMessageLifetimeUsingBlockhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latestBlockhash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// 👈 this&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that &lt;code&gt;latestBlockhash&lt;/code&gt; you fetched? it's valid for approximately &lt;strong&gt;150 slots&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;slots happen every ~400 milliseconds&lt;/p&gt;

&lt;p&gt;150 × 400ms = &lt;strong&gt;60 seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;your transaction has roughly 60-90 seconds to land on the network before it is &lt;em&gt;permanently dead&lt;/em&gt;. not retryable. not recoverable. you need to build a brand new transaction with a fresh blockhash&lt;/p&gt;




&lt;h2&gt;
  
  
  why does this exist though
&lt;/h2&gt;

&lt;p&gt;Okay here's where it gets interesting&lt;/p&gt;

&lt;p&gt;The blockhash serves two purposes simultaneously and they're both actually really smart:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. it proves the transaction is fresh&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If there was no expiry, someone could intercept your signed transaction and replay it weeks later. the blockhash ties your transaction to a specific point in time after ~150 slots, that blockhash is no longer considered valid, so the transaction dies even if someone tries to resubmit it&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It prevents literal duplicate transactions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine you send 0.1 SOL to someone. the transaction looks identical to the last time you sent 0.1 SOL to that same person. without the blockhash, the network would have no way to tell "did this already happen or is this a new send?" WITH the blockhash, every transaction is unique because it references a specific recent block&lt;/p&gt;

&lt;p&gt;so like... it's doing the job of a CSRF token AND an idempotency key AND a TTL all in one field. that's kind of a lot of work for 32 bytes&lt;/p&gt;




&lt;h2&gt;
  
  
  what this looks like when it goes wrong
&lt;/h2&gt;

&lt;p&gt;Here's the scenario that'll get you: you build a transaction, get a blockhash, do some other stuff (UI interactions, user confirmations, whatever), and THEN try to send it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ bad pattern : time passes between fetch and send&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;blockhash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestBlockhash&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForUserToClickConfirm&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// this takes 2 minutes&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;doSomeOtherAsyncThing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// and this takes another minute&lt;/span&gt;

&lt;span class="c1"&gt;// by here? blockhash might be expired already&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signedTx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// TransactionExpiredBlockheightExceededError 💀&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the fix is to fetch the blockhash as late as possible — right before you sign and send, not when you start building&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ fetch blockhash late, sign, send immediately&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildTransactionWithoutBlockhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// fetch RIGHT before sending&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latestBlockhash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestBlockhash&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txWithBlockhash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTransactionMessageLifetimeUsingBlockhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latestBlockhash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signTransactionMessageWithSigners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txWithBlockhash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// send immediately after signing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;getBase64EncodedWireTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;minimize the gap between "I got a blockhash" and "I sent the transaction"&lt;/p&gt;




&lt;h2&gt;
  
  
  and if it expires mid-flight?
&lt;/h2&gt;

&lt;p&gt;sometimes the transaction reaches the network but confirmation takes longer than the blockhash window. this happens more on mainnet under load&lt;/p&gt;

&lt;p&gt;the network will eventually return &lt;code&gt;TransactionExpiredBlockheightExceededError&lt;/code&gt; and you need to handle this explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// wait for confirmation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;waitForConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BlockheightExceeded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the transaction is dead, not pending&lt;/span&gt;
    &lt;span class="c1"&gt;// you need to rebuild with a fresh blockhash and try again&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Transaction expired rebuilding with fresh blockhash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// retry the whole thing&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// different error, rethrow&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the key thing: &lt;code&gt;TransactionExpiredBlockheightExceededError&lt;/code&gt; is NOT a "try again with the same transaction" situation. that transaction is gone. you build a new one&lt;/p&gt;




&lt;h2&gt;
  
  
  the thing that makes this different from everything else
&lt;/h2&gt;

&lt;p&gt;In literally every other system I've worked with, "retry" means "resend the same thing"&lt;/p&gt;

&lt;p&gt;In Solana, retry means "construct a new thing that does the same action"&lt;/p&gt;

&lt;p&gt;your signed transaction isn't a reusable object : it's a snapshot tied to a specific moment in time. once that moment passes, the snapshot is invalid. you're not retrying a request, you're making a new one that happens to do the same thing&lt;/p&gt;

&lt;p&gt;This is a mental model shift that took me a minute to actually internalize because it runs against every HTTP instinct I have&lt;/p&gt;




&lt;h2&gt;
  
  
  tldr
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;every Solana transaction embeds a recent blockhash that expires in ~60-90 seconds&lt;/li&gt;
&lt;li&gt;this is on purpose, it prevents replay attacks and duplicate transactions&lt;/li&gt;
&lt;li&gt;fetch the blockhash as late as possible before signing and sending&lt;/li&gt;
&lt;li&gt;if a transaction expires, don't retry it, rebuild it from scratch with a fresh blockhash&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TransactionExpiredBlockheightExceededError&lt;/code&gt; = make a new transaction, not send the old one again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the more I learn about Solana's design decisions the more I realize how much thought went into things that feel annoying at first, this being a perfect example. The expiry isn't a bug, it's load-bearing&lt;/p&gt;

&lt;p&gt;Anyway that's what got me this week 🙂&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Epoch 1 of &lt;a href="https://dev.to/t/100daysofsolana"&gt;#100DaysOfSolana&lt;/a&gt;. Writing about the things that actually surprised me so the next person is slightly less confused.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#100daysofsolana&lt;/code&gt; &lt;code&gt;#solana&lt;/code&gt; &lt;code&gt;#web3&lt;/code&gt; &lt;code&gt;#blockchain&lt;/code&gt;&lt;/p&gt;

</description>
      <category>mlh</category>
      <category>100daysofsolana</category>
      <category>blockchain</category>
      <category>learning</category>
    </item>
    <item>
      <title>Your try/catch Won't Save You on Solana (and That Cost Me Lamports to Learn)</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Fri, 15 May 2026 21:38:28 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/your-trycatch-wont-save-you-on-solana-and-that-cost-me-lamports-to-learn-4n7m</link>
      <guid>https://forem.com/tanisha_fonseca/your-trycatch-wont-save-you-on-solana-and-that-cost-me-lamports-to-learn-4n7m</guid>
      <description>&lt;p&gt;In Web2, failed requests are free.&lt;/p&gt;

&lt;p&gt;You misconfigure a Stripe call, Stripe says &lt;code&gt;400 Bad Request&lt;/code&gt;, you fix it, you try again. No charge. The server rejected your request before doing any real work, or if it did do work, the rollback is Stripe's problem, not yours.&lt;/p&gt;

&lt;p&gt;I assumed Solana worked the same way. It doesn't. And I found out the expensive way , well, "expensive" in a devnet sense, but the lesson transferred.&lt;/p&gt;




&lt;h2&gt;
  
  
  The day I broke things on purpose
&lt;/h2&gt;

&lt;p&gt;On Day 19 of #100DaysOfSolana, the challenge was to deliberately trigger failed transactions and inspect them. Not handle them gracefully. Break them &lt;em&gt;intentionally&lt;/em&gt;, on purpose, and then read the wreckage.&lt;/p&gt;

&lt;p&gt;I generated a fresh keypair with zero SOL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana-keygen new &lt;span class="nt"&gt;--outfile&lt;/span&gt; /tmp/broke-wallet.json &lt;span class="nt"&gt;--no-bip39-passphrase&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then tried to send from it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana transfer &lt;span class="nt"&gt;--keypair&lt;/span&gt; /tmp/broke-wallet.json &lt;span class="si"&gt;$(&lt;/span&gt;solana address&lt;span class="si"&gt;)&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; devnet &lt;span class="nt"&gt;--allow-unfunded-recipient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected: error message, nothing happens.&lt;br&gt;&lt;br&gt;
Got: error message, nothing happens. ✓&lt;/p&gt;

&lt;p&gt;Okay, that one the CLI caught locally before anything reached the network. Let me try something that actually hits the chain. I wrote a small script that skips preflight simulation the safety check that normally catches failures before sending and forces a transaction through that's guaranteed to fail on-chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createSolanaRpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devnet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createKeyPairSignerFromBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createTransactionMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setTransactionMessageFeePayerSigner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setTransactionMessageLifetimeUsingBlockhash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;appendTransactionMessageInstruction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;signTransactionMessageWithSigners&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getBase64EncodedWireTransaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;lamports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getTransferSolInstruction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana-program/system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSolanaRpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;devnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.devnet.solana.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.config/solana/id.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createKeyPairSignerFromBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;latestBlockhash&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestBlockhash&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Trying to send WAY more than the wallet holds&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;createTransactionMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTransactionMessageFeePayerSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTransactionMessageLifetimeUsingBlockhash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latestBlockhash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;appendTransactionMessageInstruction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;getTransferSolInstruction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;11111111111111111111111111111112&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lamports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// 999 SOL — I definitely don't have this&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;m&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;signTransactionMessageWithSigners&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// skipPreflight: true — bypass the safety check, let it fail on-chain&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;getBase64EncodedWireTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;skipPreflight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Signature:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script ran. It printed a signature. The transaction &lt;em&gt;landed on-chain&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I checked the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana confirm &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;THE_SIGNATURE] &lt;span class="nt"&gt;--url&lt;/span&gt; devnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction executed in slot 312847103:
  Status:   Error: InstructionError(0, Custom(1))
  Fee:      5000 lamports
  Compute:  3436 units consumed

  Account 0 (sender):
    Before: 1,800,000,000 lamports
    After:  1,794,995,000 lamports  ← wait
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transfer didn't go through. But my balance dropped by &lt;strong&gt;5,000 lamports&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this happens (and why it matters more than you think)
&lt;/h2&gt;

&lt;p&gt;When your transaction reaches the Solana network, validators do real work. They:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify your signature&lt;/li&gt;
&lt;li&gt;Check that the recent blockhash is valid&lt;/li&gt;
&lt;li&gt;Load all the accounts referenced in the transaction&lt;/li&gt;
&lt;li&gt;Execute the instructions&lt;/li&gt;
&lt;li&gt;If execution fails, roll back state changes — but the fee is already gone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fee compensates validators for steps 1–4. They did that work. They get paid. Your instruction failing at step 4 doesn't undo steps 1–3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is fundamentally different from a failed HTTP request.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When your API call to Stripe returns a 400, Stripe either rejected it before doing anything (client-side validation) or caught it in a transaction and rolled back (database atomicity). Either way, you don't get billed &lt;em&gt;per attempt&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On Solana, you do. Every failed on-chain transaction costs a fee. On mainnet, the base fee is still only ~0.000005 SOL per transaction — not a lot. But if you're running a production app that sends transactions in a loop and you have a bug that causes systematic failures? That adds up. And you can't get it back.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing that actually saves you: preflight simulation
&lt;/h2&gt;

&lt;p&gt;Solana's RPC has a simulation endpoint. Before sending a transaction for real, you can run it in a sandboxed environment validators simulate execution, return what &lt;em&gt;would&lt;/em&gt; happen (including errors), and charge you nothing.&lt;/p&gt;

&lt;p&gt;This is what &lt;code&gt;skipPreflight: false&lt;/code&gt; (the default) does automatically when you call &lt;code&gt;sendTransaction&lt;/code&gt;. It's why my first attempt (the CLI transfer from an empty wallet) failed locally  the SDK simulated it first and caught the error before it ever touched the network.&lt;/p&gt;

&lt;p&gt;By setting &lt;code&gt;skipPreflight: true&lt;/code&gt; in my script, I deliberately bypassed that safety net.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;preflight simulation is not optional on Solana. It's the economic gatekeeper.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Web2 terms: it's like client-side form validation. Yes, you should also validate on the server (the network will reject truly invalid transactions regardless). But the form validation is what stops you from wasting a round-trip  and on Solana, that round-trip has a real lamport cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Always let preflight run (this is the default don't override it unless you know why)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;getBase64EncodedWireTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// skipPreflight defaults to false ✓&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What &lt;code&gt;meta.err&lt;/code&gt; actually tells you
&lt;/h2&gt;

&lt;p&gt;When a transaction fails on-chain, the error lives in the &lt;code&gt;meta.err&lt;/code&gt; field of the transaction record. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;InstructionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Custom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;InstructionError&lt;/code&gt;  the failure was in instruction execution (not signature verification or blockhash expiry)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt;  it was the first instruction (zero-indexed) that failed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Custom(1)&lt;/code&gt;  a program-specific error code. For the System Program, code 1 is &lt;code&gt;InsufficientFunds&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As you start writing your own programs, you define your own custom error codes. When something fails in production, this field tells you exactly which instruction, in which program, threw which error. It's your stack trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model shift
&lt;/h2&gt;

&lt;p&gt;Here's the reframe that made everything click for me:&lt;/p&gt;

&lt;p&gt;In Web2, &lt;strong&gt;errors are free information&lt;/strong&gt;. You throw them, catch them, log them, retry. The cost is compute time, which is cheap and yours.&lt;/p&gt;

&lt;p&gt;On Solana, &lt;strong&gt;errors that reach the chain have a price tag&lt;/strong&gt;. Not a punishing one, we're talking fractions of a cent. But the architecture forces you to think about correctness &lt;em&gt;before&lt;/em&gt; submission, not just after.&lt;/p&gt;

&lt;p&gt;This changes how you write Solana code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate before submitting.&lt;/strong&gt; Check balances, check account state, check instruction data, off-chain, for free, before sending anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust preflight.&lt;/strong&gt; If simulation catches an error, fix it. Don't route around it with &lt;code&gt;skipPreflight: true&lt;/code&gt; unless you're doing something very deliberate (like stress-testing failure modes on devnet).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat failed transactions in your logs as real events.&lt;/strong&gt; They cost real fees. They're worth understanding, not just re-trying blindly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failure type&lt;/th&gt;
&lt;th&gt;Reaches chain?&lt;/th&gt;
&lt;th&gt;Fee charged?&lt;/th&gt;
&lt;th&gt;How to catch it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Invalid signature&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;SDK throws before sending&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expired blockhash&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Preflight simulation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insufficient funds&lt;/td&gt;
&lt;td&gt;No (if preflight on)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Preflight simulation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Program execution error&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;meta.err&lt;/code&gt; after confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute budget exceeded&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;meta.err&lt;/code&gt; after confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;The most useful debugging habit I've built from Epoch 1: whenever a transaction does something unexpected, paste the signature into &lt;a href="https://explorer.solana.com?cluster=devnet" rel="noopener noreferrer"&gt;Solana Explorer&lt;/a&gt;, switch to devnet, and read &lt;code&gt;meta.err&lt;/code&gt; and the program logs. It's all there, permanently, for free. The blockchain is a better debugger than console.log ever was.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Day 19 of &lt;a href="https://dev.to/t/100daysofsolana"&gt;#100DaysOfSolana&lt;/a&gt;. Breaking things on purpose so you don't have to break them by accident.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#100daysofsolana&lt;/code&gt; &lt;code&gt;#solana&lt;/code&gt; &lt;code&gt;#web3&lt;/code&gt; &lt;code&gt;#blockchain&lt;/code&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>mlh</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Solana Transactions Are Not API Calls (But They Kind of Are)</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Thu, 14 May 2026 00:08:48 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/solana-transactions-are-not-api-calls-but-they-kind-of-are-mdb</link>
      <guid>https://forem.com/tanisha_fonseca/solana-transactions-are-not-api-calls-but-they-kind-of-are-mdb</guid>
      <description>&lt;p&gt;&lt;strong&gt;Week 3 of #100DaysOfSolana: on anatomy, failures, fees, and why I now read error logs differently.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;I've been staring at HTTP request anatomy for years. Headers, body, method, auth token. When someone told me a Solana transaction has a similar structure, I nodded politely and assumed I'd get it quickly.&lt;/p&gt;

&lt;p&gt;I did not get it quickly.&lt;/p&gt;

&lt;p&gt;Here's what I understand now that I didn't at the start of this arc.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Structure That Surprised Me
&lt;/h2&gt;

&lt;p&gt;A Solana transaction has two parts: &lt;strong&gt;signatures&lt;/strong&gt; and a &lt;strong&gt;message&lt;/strong&gt;. Simple enough. But the message has a header that does something I hadn't seen before three single-byte numbers that partition the account keys array into permission groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many signatures are required&lt;/li&gt;
&lt;li&gt;How many signing accounts are read-only&lt;/li&gt;
&lt;li&gt;How many non-signing accounts are read-only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Three numbers, and the runtime knows the entire permission structure for every account in the transaction. No per-account metadata flags. No separate auth table. The ordering of accounts in the array &lt;em&gt;is&lt;/em&gt; the access control.&lt;/p&gt;

&lt;p&gt;When I ran &lt;code&gt;solana confirm -v&lt;/code&gt; on my first transfer and actually read through the output, this was the part I had to re-read three times. The account keys list isn't just a list of addresses its &lt;em&gt;position&lt;/em&gt; carries meaning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account Keys:
  [0] Writable, Signer, Fee Payer  → your wallet
  [1] Writable                     → recipient
  [2] Read-only                    → System Program
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a positional encoding of permissions. Elegant, and also deeply non-obvious the first time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 1,232-Byte Wall
&lt;/h2&gt;

&lt;p&gt;Every serialized Solana transaction must fit in 1,232 bytes. This comes from the IPv6 minimum MTU (1,280 bytes) minus 48 bytes of network headers. It is the tightest constraint I've hit so far in this challenge.&lt;/p&gt;

&lt;p&gt;Each account key is 32 bytes. A transaction with 10 unique accounts is already using 320 bytes just on addresses. Add a signature (64 bytes), a recent blockhash (32 bytes), instruction headers, and actual instruction data, and you run out of space faster than you'd expect.&lt;/p&gt;

&lt;p&gt;This is why Address Lookup Tables exist they let you reference on-chain tables of addresses using 1-byte indexes instead of 32-byte keys. I haven't built with them yet, but now I understand &lt;em&gt;why they exist&lt;/em&gt;. That feels like progress.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failed Transactions Are the Most Useful Thing I Did All Week
&lt;/h2&gt;

&lt;p&gt;Day 19 was "explore failed transactions on purpose" and honestly it should have been day 1.&lt;/p&gt;

&lt;p&gt;Here's the thing I didn't know before: &lt;strong&gt;failed transactions still cost fees.&lt;/strong&gt; The network processed your transaction. Validators did the work. They get paid regardless.&lt;/p&gt;

&lt;p&gt;This is completely unlike a failed HTTP request. If your API call errors out, you don't get billed per attempt. On Solana, every failed transaction is real lamports gone. The economics of error handling are different.&lt;/p&gt;

&lt;p&gt;When I ran &lt;code&gt;solana confirm -v&lt;/code&gt; on a deliberately-failed transfer, I saw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status: Error: InstructionError(0, Custom(1))
Fee: 5000 lamports charged
Compute Units: 3436 consumed before failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 5000 lamports is gone. And the balance changes still show up  the fee payer lost lamports even though the transfer did nothing.&lt;/p&gt;

&lt;p&gt;Production Solana apps use &lt;strong&gt;preflight simulation&lt;/strong&gt; to catch failures before submitting. It's the blockchain equivalent of validating a form before POSTing it to your server. I understand that pattern now in a way I couldn't have without watching money disappear on devnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Commitment Model is Not Binary
&lt;/h2&gt;

&lt;p&gt;When you send a transaction on Solana, it doesn't go "pending" → "done." It moves through three stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processed&lt;/strong&gt; a validator included it in a block. Like your POST reaching the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Confirmed&lt;/strong&gt; 66%+ of validators voted on the block. Like getting a 200 OK from a properly load-balanced API. In Solana's entire history, no confirmed transaction has ever been reversed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finalized&lt;/strong&gt; 31+ more confirmed blocks have been built on top. Irreversible by any reasonable threat model.&lt;/p&gt;

&lt;p&gt;I added real-time confirmation tracking to my CLI transfer tool on Day 18 — breaking out the stages and reporting each one as it happened. On devnet, processed → confirmed takes about 400ms. Confirmed → finalized takes 6-12 seconds. Watching it happen in the terminal made the whole model concrete in a way reading the docs didn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Web2 Comparison That Actually Holds (and Where It Breaks)
&lt;/h2&gt;

&lt;p&gt;Solana transaction ↔ HTTP request mapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solana&lt;/th&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Message header&lt;/td&gt;
&lt;td&gt;Request headers (metadata, permissions)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account keys&lt;/td&gt;
&lt;td&gt;URL paths + query params (resources)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instructions&lt;/td&gt;
&lt;td&gt;Request body (operations)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signatures&lt;/td&gt;
&lt;td&gt;Auth tokens (proof of authorization)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recent blockhash&lt;/td&gt;
&lt;td&gt;CSRF token with expiry (~60-90 seconds)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The analogy holds pretty well until you hit atomicity. HTTP requests are processed by one server. If the server errors mid-request, partial state changes can persist. Solana transactions are atomic across all instructions — if any instruction fails, all of them roll back. And the transaction is validated by every validator in the network independently before anything changes.&lt;/p&gt;

&lt;p&gt;Oh, and if your recent blockhash expires (~150 slots, roughly 60-90 seconds after the block it references), your transaction is dead. You'd need to rebuild it with a fresh blockhash. There's no such thing as "my API call timing out and retrying with the same request", a retry here is a new transaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Week 3 Left Me With
&lt;/h2&gt;

&lt;p&gt;I came into this arc thinking transactions were "the thing you do to move tokens." I'm leaving it understanding that they're signed, atomic, time-bounded, fee-always-charged state transitions with a 1,232-byte budget and a positional permission model.&lt;/p&gt;

&lt;p&gt;That's a lot more interesting than "the thing you do to move tokens."&lt;/p&gt;

&lt;p&gt;Week 4 is tokens. I'll be back.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Week 3 of my #100DaysOfSolana journey. Writing about what I'm learning so I actually learn it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#100daysofsolana&lt;/code&gt; &lt;code&gt;#solana&lt;/code&gt; &lt;code&gt;#web3&lt;/code&gt; &lt;code&gt;#blockchain&lt;/code&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>100daysofcode</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>I Queried a Blockchain Like a Database and It Kind of Broke My Brain</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Thu, 14 May 2026 00:05:39 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/i-queried-a-blockchain-like-a-database-and-it-kind-of-broke-my-brain-e3c</link>
      <guid>https://forem.com/tanisha_fonseca/i-queried-a-blockchain-like-a-database-and-it-kind-of-broke-my-brain-e3c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Week 2 of #100DaysOfSolana: on reading public data, comparing accounts to tables, and the moment the "everything is public" thing got real.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Let me tell you about the exact moment week 2 of this challenge stopped feeling theoretical.&lt;/p&gt;

&lt;p&gt;I had just written my first &lt;code&gt;getBalance()&lt;/code&gt; call. Simple stuff paste an address, get a number back. And then I thought: what if I paste &lt;em&gt;someone else's&lt;/em&gt; address?&lt;/p&gt;

&lt;p&gt;So I did. I pasted the Token-2022 program address (&lt;code&gt;TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb&lt;/code&gt;) into my script. Same code, different address. And it just... worked. No API key. No authentication. No &lt;code&gt;403 Forbidden&lt;/code&gt;. Full balance, full transaction history, returned immediately.&lt;/p&gt;

&lt;p&gt;That's when the "Solana is a public database" framing stopped being a metaphor and started being a fact I could feel.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Actually Built This Week
&lt;/h2&gt;

&lt;p&gt;Week 2 was the &lt;strong&gt;Reading the Blockchain&lt;/strong&gt; arc. Seven days of querying data, building UIs around it, and then staring at the account model until it made sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Days 8–9: Reading balances and transaction history&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The RPC pattern is genuinely familiar if you've worked with any REST API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSolanaRpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;devnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.devnet.solana.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetAddress&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. That's the whole thing. &lt;code&gt;getBalance&lt;/code&gt; returns lamports, you divide by &lt;code&gt;1_000_000_000&lt;/code&gt;, you're done. &lt;code&gt;getSignaturesForAddress&lt;/code&gt; is just as clean, you get back a list of transaction signatures with slots, timestamps, and status. Felt immediately comfortable coming from a REST background.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 10: Moving it to the browser&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Same SDK, same calls, just inside a Vite project instead of a Node script. The &lt;code&gt;@solana/kit&lt;/code&gt; package bundles fine for the browser, Vite handles it. The interesting part was error handling  in a terminal script you can let an error crash and read the stack trace. In a web app, you're catching bad addresses and network failures and actually telling the user something useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 11: Accounts vs. databases: the comparison that changed how I think&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the one that took the longest to sit with. I built a comparison table. Here's the short version of what surprised me most:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thing I assumed&lt;/th&gt;
&lt;th&gt;How it actually works&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"Storage costs are in my hosting bill"&lt;/td&gt;
&lt;td&gt;You deposit lamports proportional to bytes stored. It's refundable. It's &lt;em&gt;per record&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Access control is my app's job"&lt;/td&gt;
&lt;td&gt;The runtime enforces it. Only the owning program can modify an account.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Data is private by default"&lt;/td&gt;
&lt;td&gt;Everything is public by default. Opt-out doesn't exist at the protocol level.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"Code and data are separate systems"&lt;/td&gt;
&lt;td&gt;Both live in the account model. Programs are just accounts with &lt;code&gt;executable: true&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last one genuinely surprised me. When I ran &lt;code&gt;solana account TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA&lt;/code&gt; and saw &lt;code&gt;executable: true&lt;/code&gt; and a BPF Loader as the owner, I had to sit with it for a minute. The thing that &lt;em&gt;runs&lt;/em&gt; your code and the thing that &lt;em&gt;stores&lt;/em&gt; your data are the same kind of object, just with different contents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Days 12: Devnet vs. mainnet&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two RPC connections, one URL difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;devnetRpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSolanaRpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;devnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.devnet.solana.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mainnetRpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createSolanaRpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mainnet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.mainnet-beta.solana.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same queries, completely different data. Different balances, different transaction histories, different activity. It's what I'd expect from staging vs. production databases : same schema, separate state — except here "staging" is a whole separate validator network, not a different database on the same server. And the type system in &lt;code&gt;@solana/kit&lt;/code&gt; enforces this: you can't accidentally pass a devnet RPC where a mainnet one is expected. That's actually a nice developer ergonomics detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Thing That Still Feels Weird
&lt;/h2&gt;

&lt;p&gt;There are no JOINs. There's no server-side filtering.&lt;/p&gt;

&lt;p&gt;In a traditional database, I write a query, the server processes it, and I get back exactly the rows I asked for. On Solana, programs receive accounts as &lt;em&gt;inputs to instructions&lt;/em&gt;. If I want to filter or combine data, I do that off-chain via RPC and assemble results myself.&lt;/p&gt;

&lt;p&gt;Coming from SQL, this feels backwards. But I think I'm starting to understand why it has to be this way: when thousands of validators are independently processing transactions, you can't have them running arbitrary compute-heavy queries. You need deterministic, bounded operations. The query layer is the developer's problem. The blockchain's job is to execute and record.&lt;/p&gt;

&lt;p&gt;It's a constraint that's also the entire point.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Week 3 is transactions actually &lt;em&gt;writing&lt;/em&gt; state changes instead of reading them. I've been querying this global database for a week. Now I get to write to it.&lt;/p&gt;

&lt;p&gt;If you're doing #100DaysOfSolana: what was the hardest mental model shift in week 2 for you? For me it was definitely the rent model: the idea that storage cost is attached to the record itself, not abstracted into infrastructure pricing. Still wrapping my head around the implications.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Week 2 of my #100DaysOfSolana journey. Building in public, one day at a time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#100daysofsolana&lt;/code&gt; &lt;code&gt;#solana&lt;/code&gt; &lt;code&gt;#web3&lt;/code&gt; &lt;code&gt;#blockchain&lt;/code&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>learning</category>
    </item>
    <item>
      <title>Connecting Phantom to a Web App Changed How I Think About "Login"</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Tue, 28 Apr 2026 01:05:30 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/connecting-phantom-to-a-web-app-changed-how-i-think-about-login-3p0h</link>
      <guid>https://forem.com/tanisha_fonseca/connecting-phantom-to-a-web-app-changed-how-i-think-about-login-3p0h</guid>
      <description>&lt;p&gt;On Day 4 of #100DaysOfSolana, I built a small Vite app that connects to a browser wallet. The feature itself is maybe 30 lines of code. The mindset shift it triggered is harder to measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;A minimal web app that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detects installed wallets using &lt;code&gt;getWallets()&lt;/code&gt; from &lt;code&gt;@wallet-standard/app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Filters for Solana-compatible wallets by checking &lt;code&gt;wallet.chains&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prompts Phantom to connect with one button click&lt;/li&gt;
&lt;li&gt;Displays the connected address and devnet balance
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getWallets&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@wallet-standard/app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWallets&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solanaWallets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;solana:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API keys. No backend. No session management. The wallet handles all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Surprised Me
&lt;/h2&gt;

&lt;p&gt;In Web2, "Sign in with Google" feels like a convenience feature, you're still handing your identity to Google, who hands a token to the app. The app trusts Google. You trust Google. Everything flows through Google.&lt;/p&gt;

&lt;p&gt;With Phantom, the app never sees your private key. It never talks to a central auth server. It sends a connection request to &lt;em&gt;your&lt;/em&gt; wallet extension, which prompts &lt;em&gt;you&lt;/em&gt; to approve it. If you approve, the app gets your public address, nothing more. Every subsequent action that needs your signature goes through the same approval flow.&lt;/p&gt;

&lt;p&gt;The trust model is completely inverted. The app doesn't authenticate you. &lt;strong&gt;You authenticate yourself&lt;/strong&gt;, and the app just observes the result.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Sign in with Phantom" isn't "Sign in with Google for Web3." It's closer to showing your passport, the app sees proof of who you are, but it never holds the passport.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Week 2 is about writing transactions and moving tokens on-chain. I'm looking forward to seeing how that signing flow works end-to-end from the user clicking a button, to Phantom prompting for approval, to the transaction landing on devnet.&lt;/p&gt;

&lt;p&gt;If you're following along, drop your wallet setup experience below. Did anything about the browser wallet model surprise you?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part of my #100DaysOfSolana journey.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>web3</category>
      <category>beginners</category>
    </item>
    <item>
      <title>You Already Understand Solana Identity: You Just Don't Know It Yet</title>
      <dc:creator>Tanisha fonseca</dc:creator>
      <pubDate>Tue, 28 Apr 2026 00:59:53 +0000</pubDate>
      <link>https://forem.com/tanisha_fonseca/you-already-understand-solana-identity-you-just-dont-know-it-yet-5563</link>
      <guid>https://forem.com/tanisha_fonseca/you-already-understand-solana-identity-you-just-dont-know-it-yet-5563</guid>
      <description>&lt;p&gt;&lt;strong&gt;A Web2 developer's guide to on-chain identity, from SSH keys to self-sovereignty&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Let me tell you about the moment it clicked for me.&lt;/p&gt;

&lt;p&gt;I was on day three of learning Solana, staring at a terminal that had just printed a 44-character string: my new wallet address. I ran one function, and suddenly I &lt;em&gt;existed&lt;/em&gt; on a global, permissionless network.&lt;/p&gt;

&lt;p&gt;That felt like magic. But it's not magic. It's math you've already worked with before.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SSH Key You've Been Using Without Realizing It
&lt;/h2&gt;

&lt;p&gt;If you've ever set up a GitHub account with SSH access, you've done this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your_email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command creates two files: a private key (which you protect carefully) and a public key (which you paste into GitHub). GitHub stores your public key. When you &lt;code&gt;git push&lt;/code&gt;, your computer signs the request with your private key, GitHub verifies it with the public key you gave them, and you're authenticated no password needed.&lt;/p&gt;

&lt;p&gt;Solana identity works on the &lt;em&gt;exact same algorithm&lt;/em&gt;. Ed25519. Two keys, one pair.&lt;/p&gt;

&lt;p&gt;The difference? On Solana, there's no GitHub in the middle. You don't give your public key to anyone. You broadcast it to a network of thousands of validators, and your identity is established by mathematical proof alone. The "server" that recognizes your key is the entire Solana blockchain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateKeyPairSigner&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateKeyPairSigner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Your address &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One function call. No server. No company. You just created a valid identity on a global financial network, entirely offline if you wanted to.&lt;/p&gt;




&lt;h2&gt;
  
  
  Your Username Lives in Someone Else's Database
&lt;/h2&gt;

&lt;p&gt;Here's a thought experiment: open Twitter, GitHub, and your bank in three browser tabs.&lt;/p&gt;

&lt;p&gt;In each one, there's a record in a database somewhere that says "this email + this password = this user." That's your identity. And here's the uncomfortable truth: it's not really &lt;em&gt;yours&lt;/em&gt;. It's theirs. They let you use it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twitter can ban your account. Your followers, your handle, your history — gone.&lt;/li&gt;
&lt;li&gt;GitHub can suspend your repositories.&lt;/li&gt;
&lt;li&gt;Your bank can freeze your funds pending review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't hypothetical edge cases. They happen regularly. The companies aren't necessarily being malicious they're following their own rules, regulations, or algorithms. But the point stands: you have access &lt;em&gt;because they allow it&lt;/em&gt;, not because you cryptographically own anything.&lt;/p&gt;

&lt;p&gt;A Solana address is different at a fundamental level. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That 44-character string (a 32-byte Ed25519 public key encoded in Base58) &lt;em&gt;is&lt;/em&gt; your identity. Not a pointer to a record in a database ,the identity itself. The Base58 encoding is a thoughtful detail: it deliberately excludes characters like &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;O&lt;/code&gt;, &lt;code&gt;I&lt;/code&gt;, and &lt;code&gt;l&lt;/code&gt; that look similar in most fonts, because humans are the ones copying and pasting these addresses.&lt;/p&gt;

&lt;p&gt;And ownership of everything associated with that address is proven by one thing only: possession of the corresponding private key. There is no admin panel that can override this. There is no customer support workflow. No one can reset your account because there is no account, there's a cryptographic relationship between a public and private key.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Unlocks
&lt;/h2&gt;

&lt;p&gt;At this point, a reasonable Web2 developer might say: "Okay, so it's a decentralized username. Cool, I guess."&lt;/p&gt;

&lt;p&gt;But on-chain identity isn't a replacement for usernames. It's a &lt;em&gt;foundation&lt;/em&gt; that enables things that simply aren't possible in Web2 without enormous coordination overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token ownership&lt;/strong&gt; is the obvious one. When you hold SOL or a token in your wallet, you hold it directly. There's no custodian, no brokerage, no settlement delay. The network's state says your address owns those tokens, and only your private key can authorize moving them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Program interactions&lt;/strong&gt; work the same way. Every time you interact with a Solana program (think: a smart contract, a DeFi protocol, an NFT marketplace), you sign the transaction with your private key. The program knows exactly who called it. There are no sessions, no cookies, no JWT tokens to steal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Governance&lt;/strong&gt; becomes meaningful. When a DAO votes on a proposal, each vote is a signed transaction. You can verify on-chain that exactly the right addresses cast exactly the right votes, and that no one voted twice. The auditability is free and automatic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reputation is portable&lt;/strong&gt;. In Web2, your reputation on GitHub doesn't follow you to LinkedIn, which doesn't follow you to Twitter. On Solana, your address accumulates history everywhere, every protocol you've used, every NFT you've minted, every DAO you've participated in — and it's all publicly readable by anyone building on the network.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tradeoff Worth Naming
&lt;/h2&gt;

&lt;p&gt;None of this is free. The tradeoff for removing the middleman is that &lt;em&gt;you&lt;/em&gt; are now responsible for your private key.&lt;/p&gt;

&lt;p&gt;In Web2, "I forgot my password" is a solvable problem. There's a reset flow, a support ticket, a backup email. On Solana, if you lose your private key and your seed phrase, your funds are gone. The network isn't being cruel it genuinely cannot help you, because there's no back door to an account that doesn't exist as a record in anyone's database.&lt;/p&gt;

&lt;p&gt;This is why wallet UX matters so much. Browser extensions like Phantom and Solflare exist to manage this complexity, they store your private key encrypted behind a password, derive it from a human-readable seed phrase, and let you sign transactions without ever exposing the raw key to the applications you use. Hardware wallets go further, keeping the key inside a physical device that never lets it leave.&lt;/p&gt;

&lt;p&gt;The underlying math is the same everywhere. The security model just shifts where you place your trust: in a company's servers, or in your own custody of a string of words.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Week In
&lt;/h2&gt;

&lt;p&gt;Seven days ago, I couldn't generate a keypair without following a tutorial. Now I understand that a Solana address isn't some exotic blockchain thing it's an Ed25519 public key, the same algorithm your SSH config uses, deployed on a global network instead of a single server.&lt;/p&gt;

&lt;p&gt;The cryptography didn't change. What changed is the architecture around it: no central authority, no database, no company that can revoke your access. Just math, validators, and the private key only you hold.&lt;/p&gt;

&lt;p&gt;That, I think, is what "decentralized identity" actually means not a buzzword, but a very specific shift in &lt;em&gt;who controls the proof of ownership&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And it started with one function call that printed 44 characters to my terminal.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of my #100DaysOfSolana series. Follow along as I go from zero to building on Solana, one day at a time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;100daysofsolana&lt;/code&gt; &lt;code&gt;solana&lt;/code&gt; &lt;code&gt;web3&lt;/code&gt; &lt;code&gt;blockchain&lt;/code&gt; &lt;code&gt;beginners&lt;/code&gt;*&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
  </channel>
</rss>
