<?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>DEV Community: Prasiddh Naik</title>
    <description>The latest articles on DEV Community by Prasiddh Naik (@prasiddhnaik).</description>
    <link>https://dev.to/prasiddhnaik</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3896171%2F3f38eb74-b076-4e13-808a-ac3a7d7d080a.jpeg</url>
      <title>DEV Community: Prasiddh Naik</title>
      <link>https://dev.to/prasiddhnaik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prasiddhnaik"/>
    <language>en</language>
    <item>
      <title>How I built a counter program in Anchor and learned to trust my tests</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Tue, 23 Jun 2026 11:06:38 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/how-i-built-a-counter-program-in-anchor-and-learned-to-trust-my-tests-37g8</link>
      <guid>https://dev.to/prasiddhnaik/how-i-built-a-counter-program-in-anchor-and-learned-to-trust-my-tests-37g8</guid>
      <description>&lt;p&gt;The counter program is small, but the useful part was not the counter. The useful part was learning where state lives, how Anchor turns account validation into Rust types, and how a test suite can prove that a permission check is actually doing work.&lt;/p&gt;

&lt;p&gt;Here is the first piece that made Anchor feel different from a normal Web2 backend:&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="nd"&gt;#[derive(Accounts)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[account(init,&lt;/span&gt; &lt;span class="nd"&gt;payer&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;authority,&lt;/span&gt; &lt;span class="nd"&gt;space&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="nd"&gt;Counter::INIT_SPACE)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[account(mut)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Signer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;system_program&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;&amp;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;In a Web2 handler, I am used to receiving a request body and then deciding what database rows to read or write. In Anchor, the accounts are part of the request. This struct says the &lt;code&gt;counter&lt;/code&gt; account should be created, the &lt;code&gt;authority&lt;/code&gt; signer pays for that account, and the System Program is available because account creation needs it. The &lt;code&gt;space&lt;/code&gt; value reserves enough bytes for Anchor's discriminator plus the fields in my &lt;code&gt;Counter&lt;/code&gt; account.&lt;/p&gt;

&lt;p&gt;The account itself is tiny: it stores the wallet allowed to update the counter and the current number.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initialize&lt;/code&gt; handler is short because the account plumbing already happened before the body runs:&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="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.authority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.authority&lt;/span&gt;&lt;span class="nf"&gt;.key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.count&lt;/span&gt; &lt;span class="o"&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;Ok&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;&lt;code&gt;ctx.accounts&lt;/code&gt; gives me typed access to the accounts from the &lt;code&gt;Initialize&lt;/code&gt; struct. At this point, Anchor has already checked that &lt;code&gt;authority&lt;/code&gt; signed, that the counter account can be initialized, and that the right programs are present. My code only has to write the starting state.&lt;/p&gt;

&lt;p&gt;The second instruction increments the counter. This is the important part:&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="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Increment&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[account(mut,&lt;/span&gt; &lt;span class="nd"&gt;has_one&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;authority)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Signer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'info&lt;/span&gt;&lt;span class="o"&gt;&amp;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;The key line is &lt;code&gt;has_one = authority&lt;/code&gt;. It tells Anchor to compare the &lt;code&gt;authority&lt;/code&gt; field stored inside the &lt;code&gt;Counter&lt;/code&gt; account with the signer passed into this instruction. If they do not match, the handler body never gets to run.&lt;/p&gt;

&lt;p&gt;That was the permission check. The next question was whether my tests could prove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Happy Path
&lt;/h2&gt;

&lt;p&gt;The first test creates a fresh LiteSVM instance, initializes a counter, increments it, then reads the account back. The whole point is this final check:&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="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="py"&gt;.count&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;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="py"&gt;.authority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="nf"&gt;.pubkey&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test would fail if &lt;code&gt;initialize&lt;/code&gt; stored the wrong authority, if &lt;code&gt;increment&lt;/code&gt; changed the wrong amount, or if the account could not be deserialized back into &lt;code&gt;Counter&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;But happy paths are not enough.&lt;/p&gt;

&lt;p&gt;If I only test that the right wallet can increment, I have not proved that the wrong wallet cannot. That is the Solana version of testing that an admin endpoint works but never testing that a non-admin gets rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Failure Path
&lt;/h2&gt;

&lt;p&gt;So I added a test where &lt;code&gt;authority_a&lt;/code&gt; creates the counter and &lt;code&gt;authority_b&lt;/code&gt; tries to increment it. The important assertion is:&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="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="s"&gt;"increment should fail when signed by the wrong authority"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test would fail if I accidentally removed the &lt;code&gt;has_one = authority&lt;/code&gt; constraint or otherwise allowed any signer to increment someone else's counter.&lt;/p&gt;

&lt;p&gt;That sentence is the whole reason the test exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking It On Purpose
&lt;/h2&gt;

&lt;p&gt;The most useful thing I did was break the program intentionally.&lt;/p&gt;

&lt;p&gt;For one experiment, I changed the increment logic from "add one" to "add two":&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;.checked_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction still succeeded, but the test caught the wrong state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thread 'initialize_then_increment' panicked at programs/counter/tests/counter.rs:86:5:
assertion `left == right` failed
  left: 2
 right: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the kind of failure I want. It is clear, specific, and tied directly to the behavior I care about.&lt;/p&gt;

&lt;p&gt;I also removed &lt;code&gt;has_one = authority&lt;/code&gt; once. Before the failure test existed, the suite stayed green. After the failure test existed, the suite failed exactly where it should:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;increment should fail when signed by the wrong authority
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the moment the permission check stopped being a line I trusted and became a line I could audit by running tests.&lt;/p&gt;

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

&lt;p&gt;Anchor programs are small on the surface, but a lot happens before a handler body runs. The account structs are not just TypeScript-style types or documentation. They are validation rules. They decide which accounts are writable, which accounts must sign, which accounts get initialized, and which relationships must already be true.&lt;/p&gt;

&lt;p&gt;I also learned that a green test suite only means something if it tries to prove the program wrong. The happy path proved the counter could work. The failure path proved the authority gate was real. The mutation experiments proved both assertions were load-bearing.&lt;/p&gt;

&lt;p&gt;If I had another week, I would add one more failure test for double initialization, tighten the failure assertions to check the exact Anchor error, and then move the counter account to a PDA so the client does not have to create a random keypair for it.&lt;/p&gt;

&lt;p&gt;Small program. Real lesson.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>rust</category>
      <category>anchor</category>
    </item>
    <item>
      <title>Three Token-2022 mints in one week: fees, yield, and soul-bound</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Tue, 16 Jun 2026 12:13:34 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/three-token-2022-mints-in-one-week-fees-yield-and-soul-bound-41hn</link>
      <guid>https://dev.to/prasiddhnaik/three-token-2022-mints-in-one-week-fees-yield-and-soul-bound-41hn</guid>
      <description>&lt;p&gt;If you know SPL tokens but not &lt;strong&gt;Token Extensions&lt;/strong&gt;, here is the short version: &lt;strong&gt;Token-2022&lt;/strong&gt; is the upgraded Solana token program. At mint creation, you can opt into extensions: fees, interest, transfer rules, and other behaviors that live directly on the mint account. Think of it like middleware baked into the asset. Every wallet and program that touches the token sees the same rules, not a wrapper I wrote on the side.&lt;/p&gt;

&lt;p&gt;I shipped three mints on &lt;strong&gt;devnet&lt;/strong&gt; over this arc. Same CLI, three different behaviors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mint 1: Transfer Fee
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Days:&lt;/strong&gt; 50 and 51&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;8gSexQr1JF4NeCKRR72nckSUdexmN5ZCuBTVuTfsJdPp&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Explorer:&lt;/strong&gt; &lt;a href="https://explorer.solana.com/address/8gSexQr1JF4NeCKRR72nckSUdexmN5ZCuBTVuTfsJdPp?cluster=devnet" rel="noopener noreferrer"&gt;View on Solana Explorer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Extension:&lt;/strong&gt; Transfer fee, configured at 100 basis points, with a max fee of 1,000,000 tokens.&lt;/p&gt;

&lt;p&gt;This was the first mint where the token itself started enforcing behavior. I configured a 1% transfer fee and then sent 1000 tokens to a fresh recipient wallet. The recipient received 990 spendable tokens, while 10 tokens sat in the account's withheld fee field until I withdrew them with the fee authority.&lt;/p&gt;

&lt;p&gt;The create command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb &lt;span class="se"&gt;\&lt;/span&gt;
  create-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-basis-points&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-maximum-fee&lt;/span&gt; 1000000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--decimals&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I created my token account and minted supply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-account 8gSexQr1JF4NeCKRR72nckSUdexmN5ZCuBTVuTfsJdPp
spl-token mint 8gSexQr1JF4NeCKRR72nckSUdexmN5ZCuBTVuTfsJdPp 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fee lifecycle looked 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;spl-token transfer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expected-fee&lt;/span&gt; 10 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;$MINT&lt;/span&gt; 1000 &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow-unfunded-recipient&lt;/span&gt;

spl-token display &lt;span class="nv"&gt;$RECIPIENT_TA&lt;/span&gt;
spl-token withdraw-withheld-tokens &lt;span class="nv"&gt;$MY_TA&lt;/span&gt; &lt;span class="nv"&gt;$RECIPIENT_TA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where I would use it: a creator token with royalties on secondary movement, a protocol treasury skim, or a community currency where every transfer contributes a small amount back to the project. The important part is that the fee is not enforced by a web server. The Token-2022 program enforces it during transfer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mint 2: Transfer Fee Plus Interest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day:&lt;/strong&gt; 52&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;6KHkyuc1v8BqZySkEp6wGdAiG26TJnH6h5JGSU91FD7s&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Explorer:&lt;/strong&gt; &lt;a href="https://explorer.solana.com/address/6KHkyuc1v8BqZySkEp6wGdAiG26TJnH6h5JGSU91FD7s?cluster=devnet" rel="noopener noreferrer"&gt;View on Solana Explorer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Extensions:&lt;/strong&gt; Transfer fee and interest-bearing.&lt;/p&gt;

&lt;p&gt;This mint stacked two behaviors onto the same asset: the same 1% transfer fee, plus an interest-bearing configuration at 5000 basis points. In CLI terms, that is a 50% interest rate.&lt;/p&gt;

&lt;p&gt;The create command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--decimals&lt;/span&gt; 6 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-basis-points&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-maximum-fee&lt;/span&gt; 1000000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interest-rate&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creation, &lt;code&gt;spl-token display&lt;/code&gt; showed both extensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extensions
  Interest-bearing:
    Current rate: 5000bps
    Average rate: 5000bps
  Transfer fees:
    Current fee: 100bps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One subtle thing matters here: the interest-bearing extension changes the &lt;strong&gt;displayed UI amount&lt;/strong&gt; over time. It does not mint new supply into existence. After minting 1,000,000 tokens, I watched the account's displayed balance creep upward over about 30 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000004.991023
1000005.482204
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction is easy to miss. It feels like yield, but it is a Token-2022 display behavior based on the configured rate, not a custom staking program minting rewards.&lt;/p&gt;

&lt;p&gt;Where I would use it: savings-style balances, loyalty points that visibly grow, or any token where a wallet-facing balance should accrue over time without a custom program.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mint 3: Non-Transferable
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day:&lt;/strong&gt; 54&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;DRVQaZxsixMWiUrJxUmwXq6eY6vj1ZjEUz5epgoc25fk&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Explorer:&lt;/strong&gt; &lt;a href="https://explorer.solana.com/address/DRVQaZxsixMWiUrJxUmwXq6eY6vj1ZjEUz5epgoc25fk?cluster=devnet" rel="noopener noreferrer"&gt;View on Solana Explorer&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Extension:&lt;/strong&gt; Non-transferable.&lt;/p&gt;

&lt;p&gt;This one flipped the mental model. Instead of making transfers more complex, it disabled transfers entirely.&lt;/p&gt;

&lt;p&gt;The create command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-token &lt;span class="nt"&gt;--program-2022&lt;/span&gt; &lt;span class="nt"&gt;--enable-non-transferable&lt;/span&gt;
spl-token create-account &lt;span class="nv"&gt;$MINT&lt;/span&gt;
spl-token mint &lt;span class="nv"&gt;$MINT&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I created a recipient account and tried to transfer the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-account &lt;span class="nv"&gt;$MINT&lt;/span&gt; &lt;span class="nt"&gt;--owner&lt;/span&gt; &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; &lt;span class="nt"&gt;--fee-payer&lt;/span&gt; ~/.config/solana/id.json
spl-token transfer &lt;span class="nv"&gt;$MINT&lt;/span&gt; 1 &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; &lt;span class="nt"&gt;--allow-unfunded-recipient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program rejected it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program log: Instruction: TransferChecked
Program log: Transfer is disabled for this mint
Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: custom program error: 0x25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;spl-token display&lt;/code&gt; confirmed the mint-level rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extensions
  Non-transferable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where I would use it: certificates, proof-of-attendance badges, reputation credentials, or any token that should stay attached to the original holder instead of becoming a tradable asset.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Audit Habit
&lt;/h2&gt;

&lt;p&gt;Before writing this, I ran &lt;code&gt;spl-token display&lt;/code&gt; on each mint and checked the &lt;code&gt;Extensions&lt;/code&gt; block. That felt like doing &lt;code&gt;DESCRIBE table&lt;/code&gt; after a database migration. I was not trusting the commands I remembered typing. I was reading the public account state that wallets, explorers, and other programs read too.&lt;/p&gt;

&lt;p&gt;One practical note: these mints live under the Token-2022 program. If &lt;code&gt;spl-token display &amp;lt;MINT&amp;gt;&lt;/code&gt; says an account does not exist, try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token display &amp;lt;MINT&amp;gt; &lt;span class="nt"&gt;--program-2022&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hit that exact confusion while testing.&lt;/p&gt;




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

&lt;p&gt;The surprising part is how much product behavior can live on the mint itself. Transfer fees felt like something I would normally write in backend code. Interest felt like something I would expect from a staking program. Non-transferability felt like an app-level rule. In Token-2022, all three are mint configuration.&lt;/p&gt;

&lt;p&gt;In a real product, I would reach for &lt;strong&gt;transfer fees&lt;/strong&gt; when the asset needs a treasury or royalty mechanism, &lt;strong&gt;interest-bearing tokens&lt;/strong&gt; when the displayed balance should grow over time, and &lt;strong&gt;non-transferable tokens&lt;/strong&gt; when the point is reputation or identity rather than liquidity.&lt;/p&gt;

&lt;p&gt;That is the arc I wanted to understand this week: not just how to create a token, but how much behavior can be attached to the token before writing a custom program.&lt;/p&gt;

&lt;p&gt;All mints and transactions above are on Solana devnet.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>web3</category>
      <category>solana</category>
    </item>
    <item>
      <title>What I learned minting NFTs on Solana with Token Extensions published: true</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Wed, 10 Jun 2026 11:48:12 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/what-i-learned-minting-nfts-on-solana-with-token-extensionspublished-true-50i8</link>
      <guid>https://dev.to/prasiddhnaik/what-i-learned-minting-nfts-on-solana-with-token-extensionspublished-true-50i8</guid>
      <description>&lt;p&gt;Before this week I thought a Solana NFT was a Metaplex thing. It turns out you can mint a full NFT — with metadata, a collection, and live updates — using just the &lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;Token Extensions program&lt;/a&gt; and the &lt;code&gt;spl-token&lt;/code&gt; CLI.&lt;/p&gt;

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

&lt;p&gt;If you already understand SPL tokens, an NFT is the same program with tighter rules: &lt;strong&gt;0 decimals&lt;/strong&gt; (nothing to split), &lt;strong&gt;supply capped at 1&lt;/strong&gt;, and &lt;strong&gt;mint authority disabled&lt;/strong&gt; when you are done so no one can ever inflate it. Uniqueness is enforced by token math, not by a &lt;code&gt;is_nft&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://solana.com/docs/tokens/extensions/metadata" rel="noopener noreferrer"&gt;Metadata extension&lt;/a&gt; stores name, symbol, and URI directly on the mint account — no separate Metaplex metadata account. The URI points at a JSON file hosted somewhere public; that JSON holds the image URL and attributes. Wallets and &lt;a href="https://explorer.solana.com/?cluster=devnet" rel="noopener noreferrer"&gt;Solana Explorer&lt;/a&gt; read the on-chain fields, fetch the JSON, render the art.&lt;/p&gt;

&lt;p&gt;Collections use the &lt;a href="https://solana.com/docs/tokens/extensions/group-member" rel="noopener noreferrer"&gt;Group and Member extensions&lt;/a&gt;: one mint is the collection (group), each NFT mint points back to it (member). Web2 analogy: a &lt;code&gt;collection_id&lt;/code&gt; foreign key stamped into the row — except there is no central database to join against.&lt;/p&gt;

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

&lt;p&gt;Over Days 44–47 on devnet I went from a named NFT to a collection I could inspect and patch live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. NFT with on-chain metadata.&lt;/strong&gt; I minted &lt;strong&gt;Black Box Oracle&lt;/strong&gt; (&lt;code&gt;ORCL&lt;/code&gt;) on Token-2022 with &lt;code&gt;--enable-metadata&lt;/code&gt;, pointed the URI at a gist JSON file, minted one token, and disabled mint authority.&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;TOKENZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

spl-token create-token &lt;span class="nt"&gt;--program-id&lt;/span&gt; &lt;span class="nv"&gt;$TOKENZ&lt;/span&gt; &lt;span class="nt"&gt;--enable-metadata&lt;/span&gt; &lt;span class="nt"&gt;--decimals&lt;/span&gt; 0 ./nft....json
spl-token initialize-metadata &amp;lt;MINT&amp;gt; &lt;span class="s2"&gt;"Black Box Oracle"&lt;/span&gt; &lt;span class="s2"&gt;"ORCL"&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;GIST_RAW_URL&amp;gt;"&lt;/span&gt;
spl-token mint &amp;lt;MINT&amp;gt; 1
spl-token authorize &amp;lt;MINT&amp;gt; mint &lt;span class="nt"&gt;--disable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mint: &lt;a href="https://explorer.solana.com/address/Nft5CnhQbXUjvce9RGux9BVJwdH8QNGZgqoQtQ2KJeJ?cluster=devnet" rel="noopener noreferrer"&gt;&lt;code&gt;Nft5CnhQbXUjvce9RGux9BVJwdH8QNGZgqoQtQ2KJeJ&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A collection with two members.&lt;/strong&gt; &lt;strong&gt;Solana Sketchbook&lt;/strong&gt; (&lt;code&gt;SKTCH&lt;/code&gt;) uses &lt;code&gt;--enable-group&lt;/code&gt;; each sketch uses &lt;code&gt;--enable-member&lt;/code&gt;. Both flags must be set at mint creation — you cannot bolt them on later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token initialize-group &amp;lt;COLLECTION&amp;gt; 3
spl-token initialize-member &amp;lt;MEMBER&amp;gt; &amp;lt;COLLECTION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Collection: &lt;a href="https://explorer.solana.com/address/6CBzxZ96JX87hhVbrr7NdyiXSneeTbVW2mTHCExGFVbg?cluster=devnet" rel="noopener noreferrer"&gt;&lt;code&gt;6CBzxZ96JX87hhVbrr7NdyiXSneeTbVW2mTHCExGFVbg&lt;/code&gt;&lt;/a&gt; · Members: &lt;a href="https://explorer.solana.com/address/CXr5TwDWQz72W77F4YRZV2HKUQDP33tVnhbhqTL9GjuY?cluster=devnet" rel="noopener noreferrer"&gt;Sketch #1&lt;/a&gt; · &lt;a href="https://explorer.solana.com/address/8R5BfGpvCDc5n9o4YdsrCFqaMdu5z45vw93gKQBTJfv8?cluster=devnet" rel="noopener noreferrer"&gt;Sketch #2&lt;/a&gt;. Group shows &lt;code&gt;size: 2&lt;/code&gt;, &lt;code&gt;max_size: 3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. An on-chain audit.&lt;/strong&gt; I ran &lt;code&gt;spl-token display&lt;/code&gt; on the member and collection mints and verified extension blocks, supply, and that &lt;code&gt;TokenGroupMember → Group&lt;/code&gt; matched the collection address byte-for-byte. Same habit as querying a row after a migration — trust the CLI output, not just the Explorer UI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Live metadata edits.&lt;/strong&gt; While I still held update authority, I renamed Sketch #1 to &lt;strong&gt;Field Notes&lt;/strong&gt;, added and removed a custom &lt;code&gt;rarity&lt;/code&gt; field, and pointed the URI at new JSON — each change one transaction, visible in Explorer within a slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The surprising part
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Extensions are schema decisions.&lt;/strong&gt; Group and member must be declared when you create the mint. I could not retro-fit my metadata NFT into the Sketchbook collection afterward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three layers, three speeds.&lt;/strong&gt; Name and URI update on chain immediately. The image lives off chain and wallets cache it aggressively — Explorer showed "Field Notes" while Phantom still showed the old art.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No separate NFT program.&lt;/strong&gt; Collectibles are SPL Token plus a small set of extensions. &lt;a href="https://developers.metaplex.com/core" rel="noopener noreferrer"&gt;Metaplex Core&lt;/a&gt; is still the common marketplace path; Token Extensions is the native SPL stack when you want metadata and grouping inside the token program itself.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
    </item>
    <item>
      <title>What Token Extensions Finally Made Me Understand</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Thu, 04 Jun 2026 11:09:02 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/what-token-extensions-finally-made-me-understand-36p7</link>
      <guid>https://dev.to/prasiddhnaik/what-token-extensions-finally-made-me-understand-36p7</guid>
      <description>&lt;p&gt;&lt;strong&gt;Subtitle:&lt;/strong&gt; Token-2022 showed me that tokens can have rules, not just balances.&lt;/p&gt;

&lt;p&gt;This week I moved past basic SPL tokens and started using Token Extensions.&lt;/p&gt;

&lt;p&gt;At first, I thought a token was mostly just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a mint&lt;/li&gt;
&lt;li&gt;token accounts&lt;/li&gt;
&lt;li&gt;balances&lt;/li&gt;
&lt;li&gt;transfers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But Token-2022 adds something more interesting: behavior.&lt;/p&gt;

&lt;p&gt;A token can charge transfer fees.&lt;br&gt;
A token can display interest over time.&lt;br&gt;
A token can start every account frozen.&lt;br&gt;
A token can be non-transferable.&lt;br&gt;
A token can have a permanent delegate for revocation.&lt;/p&gt;

&lt;p&gt;That made tokens feel less like simple coins and more like product systems.&lt;/p&gt;
&lt;h2&gt;
  
  
  Interest-bearing tokens
&lt;/h2&gt;

&lt;p&gt;The first extension that clicked was interest-bearing tokens.&lt;/p&gt;

&lt;p&gt;The weird part is that the raw balance does not actually grow. If I mint 1000 tokens, the account still holds 1000 tokens. The interest-bearing extension changes the UI amount by applying an interest formula over time.&lt;/p&gt;

&lt;p&gt;So it feels like a savings account, but without a backend cron job constantly minting more tokens.&lt;/p&gt;

&lt;p&gt;That was the first “ohhh” moment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Multi-extension tokens
&lt;/h2&gt;

&lt;p&gt;Then I tried combining extensions.&lt;/p&gt;

&lt;p&gt;One mint could have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transfer fees&lt;/li&gt;
&lt;li&gt;interest&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the token can charge a fee when it moves, display an interest-adjusted balance, and carry its name, symbol, and URI.&lt;/p&gt;

&lt;p&gt;The important lesson: extensions must be planned before the mint is created. You cannot just bolt them on later like a random npm package. Annoying, but fair.&lt;/p&gt;

&lt;p&gt;Here is the kind of command I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb &lt;span class="se"&gt;\&lt;/span&gt;
  create-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--decimals&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-basis-points&lt;/span&gt; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-maximum-fee&lt;/span&gt; 500 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interest-rate&lt;/span&gt; 5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-metadata&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a Token-2022 mint with multiple extensions enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default frozen accounts
&lt;/h2&gt;

&lt;p&gt;The default frozen account extension felt like a compliance system.&lt;/p&gt;

&lt;p&gt;Every new token account starts frozen. The authority has to thaw an account before it can receive, send, or burn tokens.&lt;/p&gt;

&lt;p&gt;In Web2 terms, it is like saying:&lt;/p&gt;

&lt;p&gt;“You can have an account, but you cannot use it until you are approved.”&lt;/p&gt;

&lt;p&gt;That makes sense for regulated assets, gated communities, or tokens where both sender and receiver need permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspecting mints
&lt;/h2&gt;

&lt;p&gt;One of the best lessons was not building, but reading.&lt;/p&gt;

&lt;p&gt;Using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token display &lt;span class="o"&gt;[&lt;/span&gt;MINT_ADDRESS] &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could inspect which extensions were active on a mint.&lt;/p&gt;

&lt;p&gt;That made me realize extension configuration is like reading a project’s config file. Before trusting a token, you should check its rules.&lt;/p&gt;

&lt;p&gt;Does it have fees?&lt;br&gt;
Who controls the fee authority?&lt;br&gt;
Is there a freeze authority?&lt;br&gt;
Is there a permanent delegate?&lt;/p&gt;

&lt;p&gt;The token’s behavior is part of the trust model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revocable credentials
&lt;/h2&gt;

&lt;p&gt;My favorite idea was combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;non-transferable tokens&lt;/li&gt;
&lt;li&gt;permanent delegate&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That creates something like a certificate.&lt;/p&gt;

&lt;p&gt;The token cannot be transferred, so the holder cannot sell it or send it to someone else. But the issuer can still revoke it by burning it through the permanent delegate.&lt;/p&gt;

&lt;p&gt;That makes sense for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;course certificates&lt;/li&gt;
&lt;li&gt;badges&lt;/li&gt;
&lt;li&gt;memberships&lt;/li&gt;
&lt;li&gt;licenses&lt;/li&gt;
&lt;li&gt;reputation tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the moment Token Extensions felt less like “crypto stuff” and more like actual product design.&lt;/p&gt;

&lt;p&gt;Token Extensions are not random features.&lt;/p&gt;

&lt;p&gt;They are rules.&lt;/p&gt;

&lt;p&gt;Transfer fees decide what happens when value moves.&lt;br&gt;
Interest-bearing extensions decide how balances are displayed.&lt;br&gt;
Default frozen accounts decide who is allowed in.&lt;br&gt;
Non-transferable tokens decide whether something can be sold.&lt;br&gt;
Permanent delegates decide who can revoke or control tokens.&lt;/p&gt;

&lt;p&gt;The big lesson: with Token-2022, the token program can enforce behavior that an app would normally need to manage itself.&lt;/p&gt;

&lt;p&gt;That is powerful.&lt;/p&gt;

&lt;p&gt;Also slightly terrifying.&lt;/p&gt;

&lt;p&gt;Because once these rules exist, users need to know what they are holding.&lt;/p&gt;

&lt;p&gt;Tags: #solana #web3 #learning #100daysofsolana&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
    </item>
    <item>
      <title>Solana Token Extensions: Fees and Metadata</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Tue, 26 May 2026 14:48:24 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/solana-token-extensions-fees-and-metadata-2500</link>
      <guid>https://dev.to/prasiddhnaik/solana-token-extensions-fees-and-metadata-2500</guid>
      <description>&lt;p&gt;Over five days on devnet I went from a basic mint to on-chain metadata, transfer fees enforced by the Token-2022 program, a full lifecycle run without notes, and a non-transferable credential token. The throughline: &lt;strong&gt;token behavior is configured at the mint&lt;/strong&gt;, not patched in later.&lt;/p&gt;

&lt;p&gt;This post is what I wish I had read before I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting point
&lt;/h2&gt;

&lt;p&gt;I already had Solana accounts, wallets, and SOL transfers down. Tokens were the next layer.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;Token Extensions Program&lt;/a&gt; (Token-2022) attaches capabilities to the mint account at creation time. Transfer fees, metadata pointers, non-transferability — you opt in with CLI flags when you run &lt;code&gt;create-token&lt;/code&gt;. You cannot add those extensions after the fact.&lt;/p&gt;

&lt;p&gt;Plan the token's rules before you mint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token Program vs Token Extensions
&lt;/h2&gt;

&lt;p&gt;Solana has two token programs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Program&lt;/th&gt;
&lt;th&gt;Address (short)&lt;/th&gt;
&lt;th&gt;What I used it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original SPL Token&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TokenkegQfeZyiNw...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Classic fungible tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token Extensions (Token-2022)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Metadata, transfer fees, non-transferable mints&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you want extensions, opt in at mint creation:&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="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson I learned the hard way:&lt;/strong&gt; after creating a Token-2022 mint, pass &lt;code&gt;--program-2022&lt;/code&gt; (or &lt;code&gt;--program-id TokenzQd...&lt;/code&gt;) on every follow-up &lt;code&gt;spl-token&lt;/code&gt; command. I hit &lt;code&gt;AccountInvalidOwner&lt;/code&gt; on &lt;code&gt;initialize-metadata&lt;/code&gt; until I added that flag. The CLI defaults to the original Token Program unless you tell it otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 29–30: Mint and metadata
&lt;/h2&gt;

&lt;p&gt;The first step is boring on purpose — create a mint, create an associated token account, mint supply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana config &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--url&lt;/span&gt; devnet
solana balance

spl-token create-token &lt;span class="nt"&gt;--decimals&lt;/span&gt; 9
spl-token create-account &amp;lt;MINT&amp;gt;
spl-token mint &amp;lt;MINT&amp;gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you a fungible token. Wallets and explorers can show a balance, but without metadata it is just an address with decimals.&lt;/p&gt;

&lt;p&gt;Token-2022 lets you attach identity on-chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-metadata&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--decimals&lt;/span&gt; 9

spl-token initialize-metadata &amp;lt;MINT&amp;gt; &lt;span class="s2"&gt;"MyToken"&lt;/span&gt; &lt;span class="s2"&gt;"MTK"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://example.com/metadata.json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-2022&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name, symbol, and URI live in the mint's extension data. Explorers and wallets read them directly from chain state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Order matters:&lt;/strong&gt; initialize metadata before you mint large supply if your workflow depends on a fully configured mint from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 31: Transfer fees at the protocol level
&lt;/h2&gt;

&lt;p&gt;This is where Token Extensions stopped feeling like "SPL Token plus extras" and started feeling like a different design surface.&lt;/p&gt;

&lt;p&gt;I created &lt;strong&gt;ReinforceCoin (RFC)&lt;/strong&gt; with a 2% transfer fee (200 basis points):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-basis-points&lt;/span&gt; 200 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-maximum-fee&lt;/span&gt; 5000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-metadata&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--decimals&lt;/span&gt; 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mint: &lt;code&gt;8rVUkh7tbh1uR9GMbAZYZeFQ3EZ8Tkqu5nh3kToSAZ7a&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After metadata, I minted 1000 tokens to my wallet, created a token account for a second devnet wallet (&lt;code&gt;second-wallet.json&lt;/code&gt;), and transferred 100 tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token transfer &lt;span class="nt"&gt;--fund-recipient&lt;/span&gt; &amp;lt;MINT&amp;gt; 100 &amp;lt;RECIPIENT_PUBKEY&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expected-fee&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow-unfunded-recipient&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-2022&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recipient balance was &lt;strong&gt;98&lt;/strong&gt;, not 100. Two tokens were withheld as fees — enforced by the Token-2022 program during &lt;code&gt;TransferChecked&lt;/code&gt;, not by anything I wrote in a script.&lt;/p&gt;

&lt;p&gt;Every transfer through this mint pays the same fee. Wallets, DEXs, and CLI tools all hit the same on-chain rule.&lt;/p&gt;

&lt;p&gt;To collect withheld fees, the mint authority withdraws from the recipient's token account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token withdraw-withheld-tokens &amp;lt;MY_TOKEN_ACCOUNT&amp;gt; &amp;lt;RECIPIENT_TOKEN_ACCOUNT&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-2022&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fees accrue on transfer and get collected in a separate step. That two-phase flow was not obvious from the docs alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 32: Reinforcing the full lifecycle
&lt;/h2&gt;

&lt;p&gt;Day 32 was deliberately repetitive: run the entire flow in one sitting without notes. Create mint → metadata → mint supply → fund recipient → transfer with fee → withdraw withheld → verify with &lt;code&gt;spl-token display&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Muscle memory, not trivia.&lt;/p&gt;

&lt;p&gt;The commands that stuck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spl-token display &amp;lt;MINT&amp;gt; --program-2022&lt;/code&gt; — extensions, fee config, authority&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--expected-fee&lt;/code&gt; on transfer — CLI sanity-checks your math against on-chain rules&lt;/li&gt;
&lt;li&gt;Token &lt;strong&gt;account&lt;/strong&gt; addresses vs &lt;strong&gt;mint&lt;/strong&gt; addresses — see below&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Day 33: Non-transferable tokens (soulbound credentials)
&lt;/h2&gt;

&lt;p&gt;For credentials, diplomas, or achievement badges, you want the chain to reject transfers — not a UI warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-non-transferable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mint: &lt;code&gt;Gvztbeuf4Szs9KDvXxHkzRGPEsnf895FozQPoTnUPipu&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I minted 10 tokens to my ATA (&lt;code&gt;5abnDEFHy5Btm2kyazagTaiaQaXUv4bStZusw69jndS3&lt;/code&gt;) and tried to send 5 to an experiment wallet. The transaction failed in simulation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program log: Transfer is disabled for this mint
custom program error: 0x25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That failure was success. The Token-2022 program enforces non-transferability — no workaround at the client level.&lt;/p&gt;

&lt;p&gt;Burning still worked. I reduced my balance from 10 to 7 by burning from the &lt;strong&gt;token account&lt;/strong&gt;, not the mint:&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;# Wrong — mint address is not a token account&lt;/span&gt;
spl-token burn Gvztbeuf4Szs9KDvXxHkzRGPEsnf895FozQPoTnUPipu 3 ...

&lt;span class="c"&gt;# Right — pass the ATA that holds your balance&lt;/span&gt;
spl-token burn 5abnDEFHy5Btm2kyazagTaiaQaXUv4bStZusw69jndS3 3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--program-id&lt;/span&gt; TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Gvztbeuf...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mint — token definition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5abnDEFH...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;My associated token account — holds balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5K6a2t28...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Experiment wallet's token account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Confusing mint with a token account is an easy mistake to make. The CLI error messages nudge you toward the right object, but knowing the account model saves time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rules are chosen at birth.&lt;/strong&gt; Extensions are set when the mint is created. Plan the economics before you mint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fees are withheld, not instantly routed.&lt;/strong&gt; Transfer fees sit in recipient token accounts until someone with authority runs &lt;code&gt;withdraw-withheld-tokens&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Non-transferable does not mean frozen.&lt;/strong&gt; I can still mint and burn. I just cannot move balances between owners — which is exactly what you want for credentials.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The &lt;code&gt;--program-2022&lt;/code&gt; flag is load-bearing.&lt;/strong&gt; Same CLI, different program owner. Forgetting the flag produces errors that look like corruption but are just wrong program IDs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write down addresses as you go.&lt;/strong&gt; Mint, your ATA, recipient ATA — each step prints new ones. I lost time re-deriving them from transaction logs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Useful references while you work: &lt;a href="https://solana.com/docs/tokens" rel="noopener noreferrer"&gt;Tokens on Solana&lt;/a&gt;, &lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;Token Extensions&lt;/a&gt;, and the &lt;a href="https://spl.solana.com/token" rel="noopener noreferrer"&gt;SPL Token CLI&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>blockchain</category>
      <category>100daysofsolana</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Solana Mental Model for Web2 Developers</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Sun, 17 May 2026 12:02:14 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/solana-mental-model-for-web2-developers-2c02</link>
      <guid>https://dev.to/prasiddhnaik/solana-mental-model-for-web2-developers-2c02</guid>
      <description>&lt;p&gt;If you come from Web2 development, the easiest way to understand Solana is to stop thinking about contracts first and start thinking about accounts.&lt;/p&gt;

&lt;p&gt;On Solana, almost everything is an account. A wallet is an account. A token balance is an account. A program which is Solana's version of a smart contract is also an account. Instead of storing application state in a database table or inside a contract object, Solana stores state in accounts that live on-chain.&lt;/p&gt;

&lt;p&gt;A Solana account is like a file in a filesystem. It has an address, some metadata, and some contents. The address is a 32-byte public key. The metadata tells you who owns it, whether it is executable, and how much SOL it holds. The contents are just raw bytes.&lt;/p&gt;

&lt;p&gt;Every Solana account has five core fields:&lt;br&gt;
&lt;/p&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;"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;"11111111111111111111111111111111"&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;false&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="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="p"&gt;[]&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;&lt;code&gt;lamports&lt;/code&gt; are the account's SOL balance. One SOL equals one billion lamports.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data&lt;/code&gt; is a byte array. This is where programs store custom state. A normal wallet account usually has zero bytes of data. A token account or NFT metadata account has structured data inside this field.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;owner&lt;/code&gt; is the program that controls the account. This is one of the most important ideas in Solana. Only the owner program can modify an account's data or debit its lamports.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;executable&lt;/code&gt; tells you whether the account is a program. If it is &lt;code&gt;true&lt;/code&gt;, the account contains executable program code. If it is &lt;code&gt;false&lt;/code&gt;, it is a regular data account.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rentEpoch&lt;/code&gt; is a legacy field. Today, accounts are generally rent-exempt, and this field is usually set to the maximum value.&lt;/p&gt;

&lt;p&gt;The ownership rule is Solana's security model in one sentence: only the owning program can change an account's data. For example, your wallet is owned by the System Program, so the System Program handles SOL transfers. Token accounts are owned by the Token Program, so only the Token Program can move tokens or update token balances.&lt;/p&gt;

&lt;p&gt;The part that surprises many Web2 developers is that Solana programs do not store their own state. Programs are stateless. A program account stores executable code, while separate data accounts store the actual application state.&lt;/p&gt;

&lt;p&gt;A useful analogy is a web server and a database. The program is like the backend server: it contains the logic. The accounts are like database records: they contain the data. When a transaction runs it tells the program which accounts it wants to read or write.&lt;/p&gt;

&lt;p&gt;This is different from Ethereum, where a smart contract usually owns both the code and the state together. On Solana code and state are separated. That separation is one reason Solana can process many transactions in parallel. If two transactions touch different accounts, they can often run at the same time.&lt;/p&gt;

&lt;p&gt;There is also the idea of rent exemption. Because account data takes space on-chain accounts must hold a minimum SOL balance based on their size. This keeps the account alive. For a basic account with no extra data, the rent-exempt amount is small roughly around 0.00089 SOL. You can calculate exact values with the Solana CLI or the &lt;code&gt;getMinimumBalanceForRentExemption&lt;/code&gt; RPC method.&lt;/p&gt;

&lt;p&gt;So the mental model is simple:&lt;br&gt;
Solana is a giant public key-value store. The key is an account address. The value is an account object with balance, owner, executable status, and raw data. Programs are accounts that contain code. Data accounts are accounts that contain state. The owner field decides who is allowed to modify what.&lt;/p&gt;

&lt;p&gt;Once this clicks, the rest of Solana starts to make more sense. Tokens, NFTs, DeFi protocols, staking, governance, and games all use the same basic building block: accounts.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Solana Transactions Cost Money Even When They Fail</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Sat, 09 May 2026 11:50:55 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/solana-transactions-cost-money-even-when-they-fail-2md6</link>
      <guid>https://dev.to/prasiddhnaik/solana-transactions-cost-money-even-when-they-fail-2md6</guid>
      <description>&lt;p&gt;Here's what I learned in 3 weeks: &lt;strong&gt;failed transactions on Solana cost real SOL&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Surprise
&lt;/h2&gt;

&lt;p&gt;As a backend dev, I'm used to failed requests being free. A 500 error? Retry it. It's no big deal.&lt;/p&gt;

&lt;p&gt;Solana is different. When you send a transaction, three things can happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;CLI catches it&lt;/strong&gt; → Free (local balance check)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulation catches it&lt;/strong&gt; → Free (RPC preflight check)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-chain fails&lt;/strong&gt; → &lt;strong&gt;Costs 0.000005 SOL&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Watching It Happen
&lt;/h2&gt;

&lt;p&gt;I created a broke wallet (0 SOL) and tried to transfer:&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;--from&lt;/span&gt; /tmp/broke-wallet.json &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; 1
&lt;span class="c"&gt;# Error: insufficient funds (caught locally, free)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I used a funded wallet but tried to send more than my balance:&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;// With skipPreflight = true (dangerous)&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Signature: 5NWqdwSCmc3vz2PAwMmBQppL6uogPB7qkRWzUnCFtA9...
Status: Error processing Instruction 0: custom program error: 0x1
Fee: ◎0.000005
&lt;/span&gt;&lt;span class="gp"&gt;Balance: 8.39795 -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;8.397945 SOL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction failed. The fee was still charged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;In Web2, validation happens &lt;em&gt;before&lt;/em&gt; the operation:&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Solana, validation happens &lt;em&gt;inside&lt;/em&gt; the transaction. The blockchain doesn't check first—it tries to execute and fails if conditions aren't met. The attempt itself is a processed transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;Always simulate before sending:&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;// Default: skipPreflight = false (simulates first)&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="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;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And check balances client-side:&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;balance&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;sender&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;if &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="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Insufficient funds&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;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;On Solana, there's no "try it and see." Every attempt that reaches the network costs money. Design your error handling accordingly.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Day 19 of 100 Days of Solana. Full code: [&lt;a href="https://github.com/prasiddhnaik/solana-100-days" rel="noopener noreferrer"&gt;https://github.com/prasiddhnaik/solana-100-days&lt;/a&gt;]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; #solana #web3 #blockchain #backend&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>cryptocurrency</category>
      <category>programming</category>
      <category>web3</category>
    </item>
    <item>
      <title>BharatZero</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Fri, 08 May 2026 12:34:30 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/bharatzero-9ck</link>
      <guid>https://dev.to/prasiddhnaik/bharatzero-9ck</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Build with Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;BharatZero is an India-focused legislative explorer that aggregates parliamentary data spanning 1947 to the present into a unified, searchable platform. The app solves a critical transparency problem: India's legislative information is scattered across dozens of government portals (Sansad, PRS Legislative Research, Parliament Digital Library, Data.gov.in), hidden in PDFs, and nearly impossible to navigate comprehensively.&lt;/p&gt;

&lt;p&gt;BharatZero brings it all together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4,708 bill records from three different official sources&lt;/li&gt;
&lt;li&gt;7,253 timeline events tracking parliamentary activity&lt;/li&gt;
&lt;li&gt;2,560 sitting days with chronological browsing&lt;/li&gt;
&lt;li&gt;Prime Minister term filtering—see what legislation passed during any PM's tenure&lt;/li&gt;
&lt;li&gt;Complete source attribution for data provenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform features eight sections: Overview, Houses (with Lok Sabha power charts), Timeline, Bills, Committees, Debates, Acts, and Sources. Every record shows exactly where it came from—critical for democratic transparency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://bharatzero.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;bharatzero.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/prasiddhnaik" rel="noopener noreferrer"&gt;
        prasiddhnaik
      &lt;/a&gt; / &lt;a href="https://github.com/prasiddhnaik/Bharat0" rel="noopener noreferrer"&gt;
        Bharat0
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;BharatZero&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;BharatZero is an India-focused legislative explorer for bills, Acts, Parliament timelines, House power, Prime Minister terms, and official source coverage. It is built as a Vite/React frontend with a small Node API server, Prisma, and PostgreSQL/Neon.&lt;/p&gt;
&lt;p&gt;The current app is no longer just a static prototype. It uses generated legislative datasets plus a Prisma-backed repository path for runtime data.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Current Coverage&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Generated local datasets currently include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;4,708&lt;/code&gt; Bill records&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7,268&lt;/code&gt; Bill action records&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7,253&lt;/code&gt; timeline events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2,560&lt;/code&gt; sitting days&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;217&lt;/code&gt; Act records&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;15&lt;/code&gt; Prime Minister profile records&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;16&lt;/code&gt; Lok Sabha power snapshots&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Main source families:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sansad legislation data, as of &lt;code&gt;2026-04-25&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;PRS historical bill data, &lt;code&gt;1992-2019&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parliament Digital Library bill/proceeding data, &lt;code&gt;1947-2003&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Curated Prime Minister profile data from PM India&lt;/li&gt;
&lt;li&gt;Curated Lok Sabha power snapshots from ECI/IPU-style election summaries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The PDL sync now covers older Prime Ministers, including Nehru, Shastri, Nanda, and Indira Gandhi-era windows where source records exist.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;…&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/prasiddhnaik/Bharat0" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Used Gemma 4
&lt;/h2&gt;

&lt;p&gt;I chose Gemma 4 31B Dense (gemma-4-31b-it) via the Gemini/OpenAI-compatible API for bill analysis generation. Legislative bills are dense, technical documents—often 50+ pages of legal language. Citizens, journalists, and researchers need accessible summaries, but manual summarization doesn't scale across 4,700+ bills.&lt;/p&gt;

&lt;p&gt;BharatZero uses Gemma 4 to generate plain-language summaries, economic impact analysis, key provisions extraction, and social implications analysis. The model handles the bilingual nature of Indian legislation seamlessly—bill titles and summaries work in both English and Hindi contexts without separate model tuning.&lt;/p&gt;

&lt;p&gt;Analysis is expensive, so BharatZero implements a persistent cache layer. Once a bill is analyzed, results are stored in PostgreSQL. Subsequent requests serve cached results instantly.&lt;/p&gt;

&lt;p&gt;Why Gemma 4 31B Dense was the right fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long context: Bills are lengthy documents; Gemma 4 handles full text input&lt;/li&gt;
&lt;li&gt;Structured output: Returns consistent JSON for parsing into the UI&lt;/li&gt;
&lt;li&gt;Factual accuracy: Critical for legislative analysis—hallucinations are dangerous&lt;/li&gt;
&lt;li&gt;Cost efficiency: More economical than alternatives for high-volume batch analysis
&amp;lt;!-- Don't forget to add a cover image if you want! --&amp;gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
    </item>
    <item>
      <title>solana notes - week 2</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Wed, 06 May 2026 14:26:30 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/solana-notes-week-2-40bi</link>
      <guid>https://dev.to/prasiddhnaik/solana-notes-week-2-40bi</guid>
      <description>&lt;h1&gt;
  
  
  100DaysOfSolana
&lt;/h1&gt;

&lt;p&gt;i've used normal databases for years, so i kept trying to force solana into that shape. tables, rows, queries. some of that helps, a lot of it breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  what surprised me
&lt;/h2&gt;

&lt;p&gt;reading a balance is stupidly simple:&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;balanceInLamports&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="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;the code is easy. the mental model is what's hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  the click moment
&lt;/h2&gt;

&lt;p&gt;ran &lt;code&gt;solana account $(solana address)&lt;/code&gt; and saw my wallet data. then realized: anyone can do this. no api keys, no auth. just the public key and you're reading someone's balance.&lt;/p&gt;

&lt;p&gt;that's uncomfortable in a good way. in web2 data is hidden unless i expose it. here it's visible unless i design around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  the sdk
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@solana/kit&lt;/code&gt; feels normal. not crusty like i expected. async/await, clean imports. the &lt;code&gt;devnet()&lt;/code&gt; and &lt;code&gt;mainnet()&lt;/code&gt; wrappers aren't just for show—they actually tag the network for type safety. someone thought about DX.&lt;/p&gt;

&lt;h2&gt;
  
  
  where the database comparison breaks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;normal thinking&lt;/th&gt;
&lt;th&gt;solana reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sql joins&lt;/td&gt;
&lt;td&gt;lol no. fetch signatures, then fetch details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;private by default&lt;/td&gt;
&lt;td&gt;public by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i control who reads&lt;/td&gt;
&lt;td&gt;everyone reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;alter table, migrations&lt;/td&gt;
&lt;td&gt;nope. design upfront, serialize to bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;i keep wanting &lt;code&gt;SELECT * FROM transactions WHERE user_id = 123&lt;/code&gt;. instead:&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;signatures&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;getSignaturesForAddress&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&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="c1"&gt;// then fetch each one i guess&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;more manual. that's the tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  devnet vs mainnet hit different
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- Devnet ---
Balance : 0.001159846 SOL

--- Mainnet ---
Balance : 0.069875097 SOL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;same address, same code, different world. staging vs production except anyone can view production.&lt;/p&gt;

&lt;h2&gt;
  
  
  still fuzzy on
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDAs&lt;/strong&gt; - when do i actually use them vs regular accounts?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rent&lt;/strong&gt; - deposit not fee, but the exact economics? need to build something that creates/closes accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cross-program calls&lt;/strong&gt; - just words to me right now&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;writing transactions&lt;/strong&gt; - reading is safe. writing feels serious. nervous about it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;send a simple transaction&lt;/li&gt;
&lt;li&gt;one PDA example end to end&lt;/li&gt;
&lt;li&gt;build something that actually writes, not just reads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  where i'm at
&lt;/h2&gt;

&lt;p&gt;day 12. not an expert. but less intimidated.&lt;/p&gt;

&lt;p&gt;the code looks familiar while the assumptions underneath are totally different. public data by default. accounts instead of tables. pay for storage not queries. still rewiring my brain.&lt;/p&gt;

&lt;p&gt;if you're also confused by accounts, PDAs, or why everything is public: same. i think that's just part of it.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>devjournal</category>
      <category>javascript</category>
      <category>web3</category>
    </item>
    <item>
      <title>What Does "Identity" Actually Mean on a Blockchain?</title>
      <dc:creator>Prasiddh Naik</dc:creator>
      <pubDate>Sat, 25 Apr 2026 11:17:13 +0000</pubDate>
      <link>https://dev.to/prasiddhnaik/what-does-identity-actually-mean-on-a-blockchain-3l69</link>
      <guid>https://dev.to/prasiddhnaik/what-does-identity-actually-mean-on-a-blockchain-3l69</guid>
      <description>&lt;p&gt;If you've been writing code for a while, you've probably used SSH keys. You generate a key pair, drop the public key on a server, and from that point on, you prove who you are by holding the private key. No username, no password reset. You either have the key, or you don't&lt;/p&gt;

&lt;p&gt;Solana identity works on the same principle: you have the key, or you don't — except that, instead of a single server, it's the entire network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem with Web2 identity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Web2, you have no power; you're just renting space. You don't control your GitHub or Google accounts; the company does. They give you access, but they can also take it away.&lt;/p&gt;

&lt;p&gt;Even when it feels like yours, it isn't. Your email address exists because Google or Microsoft allows it to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Solana does it differently&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On Solana, your identity starts with a single cryptographic keypair — a public key and a private key. The public key is your address, something like &lt;code&gt;14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5&lt;/code&gt;. That's what the world sees. The private key never leaves your wallet, and it's the only thing that can authorize actions on your behalf.&lt;/p&gt;

&lt;p&gt;No company issues this key pair. No server stores it. You generate it yourself, you hold it, and it works across every application on the Solana network without asking anyone's permission. There's no sign-up form, no email verification, no "log in with Google." Your keypair is your identity, and it's yours in a way that a Web2 account never is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ownership that actually means something&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the part that takes a moment to sink in: on Solana, only the holder of the private key can sign transactions for an account. Not a company. Not an admin. Not a support team. There's no password reset flow, no "verify your identity" email, no override. If you hold the private key, you control the account. If you lose it, it can't be recovered.&lt;/p&gt;

&lt;p&gt;That sounds scary, and it is if you're careless. But it also means no one can lock you out of your own account. No platform ban, no policy change, no company going bankrupt takes your identity with it. The tradeoff is real responsibility — but so is real ownership.&lt;br&gt;
What this identity actually unlocks&lt;/p&gt;

&lt;p&gt;On-chain identity isn't just a replacement for a username. Because it's cryptographic, it becomes the foundation for everything else on the network. Token ownership is tied to your keypair. When you vote in a DAO governance proposal, your keypair is the vote. When you interact with a program, your keypair authorizes it. When you build a reputation across DeFi protocols or NFT platforms, it all traces back to the same address.&lt;/p&gt;

&lt;p&gt;In Web2, your reputation on GitHub doesn't carry over to LinkedIn, which doesn't carry over to your bank. Each platform starts from zero. On Solana, your keypair is consistent across the entire ecosystem. Any application can read your on-chain history without you having to re-verify anything.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>beginners</category>
      <category>web3</category>
      <category>solana</category>
    </item>
  </channel>
</rss>
