<?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: Gopichand</title>
    <description>The latest articles on DEV Community by Gopichand (@gopichand_dev).</description>
    <link>https://dev.to/gopichand_dev</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%2F3882708%2F5d2325f7-46e0-4bd0-a0a5-2d36935df10a.jpg</url>
      <title>DEV Community: Gopichand</title>
      <link>https://dev.to/gopichand_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gopichand_dev"/>
    <language>en</language>
    <item>
      <title>CPI on Solana: The Mental Model I Wish I Had on Day 71</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Thu, 02 Jul 2026 07:01:17 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/cpi-on-solana-the-mental-model-i-wish-i-had-on-day-71-ceh</link>
      <guid>https://dev.to/gopichand_dev/cpi-on-solana-the-mental-model-i-wish-i-had-on-day-71-ceh</guid>
      <description>&lt;p&gt;I stared at CpiContext::new(...) for a solid ten minutes on Day 71. I knew &lt;br&gt;
the arguments existed. I had no idea what they meant. I copied working code, &lt;br&gt;
ran the test, watched it go green, and moved on. Three days later a PDA-signed &lt;br&gt;
withdraw blew up on me and I realised I had been cargo-culting the pattern &lt;br&gt;
without actually understanding it.&lt;/p&gt;

&lt;p&gt;Here is everything I wish someone had told me before I wrote a single line of CPI code.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one sentence that unlocks all of it
&lt;/h2&gt;

&lt;p&gt;A CPI is a function call with a guest list. You name the program you want to &lt;br&gt;
call, you pass it the accounts it needs, and you prove who is allowed to sign.&lt;/p&gt;

&lt;p&gt;Every line of CPI code is doing one of those three things.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the three pieces actually are
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The program being called&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every program on Solana lives at a public key. When you write &lt;br&gt;
&lt;code&gt;CpiContext::new(ctx.accounts.system_program.key(), ...)&lt;/code&gt;, that first argument &lt;br&gt;
is the on-chain address of the program you want to hand execution over to. You &lt;br&gt;
are not importing a library. You are calling an address that holds executable &lt;br&gt;
bytecode. Once that framing clicked for me, the rest of the pattern made sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The accounts it needs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The callee has its own &lt;code&gt;#[derive(Accounts)]&lt;/code&gt; struct with expectations. Your job &lt;br&gt;
as the caller is to satisfy those expectations by passing the right accounts &lt;br&gt;
through. Anchor ships typed structs for common programs — &lt;code&gt;Transfer { from, to }&lt;/code&gt; &lt;br&gt;
for the System Program, &lt;code&gt;MintTo { mint, to, authority }&lt;/code&gt; for Token-2022. You &lt;br&gt;
pull those from your own &lt;code&gt;ctx.accounts&lt;/code&gt;, fill in the struct, and bundle it into &lt;br&gt;
a &lt;code&gt;CpiContext&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rule I missed on Day 71: &lt;strong&gt;every program you CPI into must appear as an &lt;br&gt;
account in your own &lt;code&gt;#[derive(Accounts)]&lt;/code&gt;&lt;/strong&gt;. It is not enough to know the &lt;br&gt;
address. You need &lt;code&gt;pub system_program: Program&amp;lt;'info, System&amp;gt;&lt;/code&gt; in your struct &lt;br&gt;
or Anchor will not compile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is authorised to sign&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are exactly two cases.&lt;/p&gt;

&lt;p&gt;Case one is a real user wallet. The user already signed the outer transaction &lt;br&gt;
and the Solana runtime carries that authority down into every CPI automatically. &lt;br&gt;
You write zero extra code.&lt;/p&gt;

&lt;p&gt;Case two is a PDA. A PDA has no private key so it cannot sign the normal way. &lt;br&gt;
Instead you re-supply the same seeds you used to derive the PDA. The runtime &lt;br&gt;
re-derives the address from those seeds, checks it against the account you &lt;br&gt;
passed, and if they match that counts as the signature. Seeds stand in for a &lt;br&gt;
private key. That is the entire trick behind &lt;code&gt;CpiContext::new_with_signer&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 71: The smallest possible CPI — SOL transfer to the System Program
&lt;/h2&gt;

&lt;p&gt;This is the complete &lt;code&gt;sol-mover&lt;/code&gt; handler from my repo. This is already as &lt;br&gt;
small as a CPI gets.&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;use&lt;/span&gt; &lt;span class="nn"&gt;anchor_lang&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anchor_lang&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;system_program&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Transfer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;declare_id!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2RuhecMfTQqGwfgEC47ca965VqGUbTGefypkSY5Re6ob"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nd"&gt;#[program]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;sol_mover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;sol_transfer&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;SolTransfer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&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;cpi_accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transfer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.sender&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.recipient&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&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;cpi_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CpiContext&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.system_program&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;cpi_accounts&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;cpi_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&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;span class="p"&gt;}&lt;/span&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;SolTransfer&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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;sender&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="nd"&gt;#[account(mut)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SystemAccount&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;Map it to the three pieces: &lt;code&gt;system_program.key()&lt;/code&gt; is the program, &lt;br&gt;
&lt;code&gt;Transfer { from: sender, to: recipient }&lt;/code&gt; is the guest list, &lt;code&gt;sender: Signer&lt;/code&gt; &lt;br&gt;
is the authority — the user signed the outer transaction so the runtime forwards &lt;br&gt;
it automatically.&lt;/p&gt;

&lt;p&gt;Full source: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-71-sol-cpi" rel="noopener noreferrer"&gt;day-71-sol-cpi&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 72: The same pattern, different callee — Token-2022
&lt;/h2&gt;

&lt;p&gt;Day 72 proved that &lt;code&gt;CpiContext&lt;/code&gt; is callee-agnostic. Instead of calling the &lt;br&gt;
System Program, this calls Token-2022's &lt;code&gt;mint_to&lt;/code&gt; instruction. The struct &lt;br&gt;
changes to &lt;code&gt;MintTo { mint, to, authority }&lt;/code&gt; and the program account becomes &lt;br&gt;
&lt;code&gt;Interface&amp;lt;'info, TokenInterface&amp;gt;&lt;/code&gt; — which accepts both classic SPL Token and &lt;br&gt;
Token-2022 at runtime without a code change.&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;use&lt;/span&gt; &lt;span class="nn"&gt;anchor_lang&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anchor_spl&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;token_interface&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="n"&gt;mint_to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MintTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TokenAccount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TokenInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[program]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;token_cpi&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;mint_tokens&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;MintTokens&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&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;cpi_accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MintTo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.mint&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.token_account&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&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;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.authority&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&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;cpi_ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CpiContext&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.token_program&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;cpi_accounts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;mint_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpi_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&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;span class="p"&gt;}&lt;/span&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;MintTokens&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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;mint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InterfaceAccount&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;Mint&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;token_account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InterfaceAccount&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;TokenAccount&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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;token_program&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Interface&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;TokenInterface&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 test minted &lt;code&gt;1_000_000_000&lt;/code&gt; base units and confirmed the balance. &lt;br&gt;
Same &lt;code&gt;CpiContext::new&lt;/code&gt; shape, different program and accounts struct.&lt;/p&gt;

&lt;p&gt;Full source: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-72-token-cpi" rel="noopener noreferrer"&gt;day-72-token-cpi&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 73: PDA-signed CPI — the vault withdraw
&lt;/h2&gt;

&lt;p&gt;Day 73 introduced &lt;code&gt;CpiContext::new_with_signer&lt;/code&gt;. The vault PDA holds SOL &lt;br&gt;
and the program needs to sign for it without a private key. The only thing &lt;br&gt;
that changes from Day 71 is &lt;code&gt;new&lt;/code&gt; becomes &lt;code&gt;new_with_signer&lt;/code&gt; and you pass &lt;br&gt;
&lt;code&gt;signer_seeds&lt;/code&gt;.&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;withdraw&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;Withdraw&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&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;user_key&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.user&lt;/span&gt;&lt;span class="nf"&gt;.key&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;bump&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;.bumps.vault&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;signer_seeds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"vault"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_key&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bump&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;cpi_ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CpiContext&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_with_signer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.system_program&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;Transfer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.vault&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.user&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;signer_seeds&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;cpi_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&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;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;Withdraw&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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;user&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="nd"&gt;#[account(&lt;/span&gt;
        &lt;span class="nd"&gt;mut,&lt;/span&gt;
        &lt;span class="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"vault"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="p"&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;vault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SystemAccount&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;The seeds in &lt;code&gt;signer_seeds&lt;/code&gt; and the seeds in &lt;code&gt;#[account(seeds = [...])]&lt;/code&gt; must &lt;br&gt;
match byte for byte. The bump comes from &lt;code&gt;ctx.bumps.vault&lt;/code&gt; — Anchor finds the &lt;br&gt;
canonical bump during account validation and stores it there. Never hardcode it.&lt;/p&gt;

&lt;p&gt;Test output: vault balance after deposit was &lt;code&gt;500_000_000&lt;/code&gt; lamports, vault &lt;br&gt;
balance after withdraw was &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Full source: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-73-vault" rel="noopener noreferrer"&gt;day-73-vault&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 74: Program calling another program — compose-lab
&lt;/h2&gt;

&lt;p&gt;Day 74 pushed the pattern one step further. Instead of calling a system &lt;br&gt;
program, &lt;code&gt;compose-lab&lt;/code&gt; calls another Anchor program I wrote called &lt;code&gt;counter&lt;/code&gt;. &lt;br&gt;
Anchor 1.0's &lt;code&gt;declare_program!&lt;/code&gt; macro imports the callee's full type system &lt;br&gt;
from its IDL — accounts structs, CPI modules, and program type — giving &lt;br&gt;
compile-time safety with no raw instruction building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;counter/src/lib.rs&lt;/strong&gt; (the callee)&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;use&lt;/span&gt; &lt;span class="nn"&gt;anchor_lang&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;declare_id!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"8GM63Fe2dFnGEhxhJLh162GJ4cSpWgb927uW6sd2vzbx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nd"&gt;#[program]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;increment&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;Increment&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="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.tally.count&lt;/span&gt; &lt;span class="o"&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;msg!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"counter is now {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.tally.count&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;span class="p"&gt;}&lt;/span&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;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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;tally&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;Tally&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;span class="nd"&gt;#[account]&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(InitSpace)]&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;Tally&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;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&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;strong&gt;compose-lab/src/lib.rs&lt;/strong&gt; (the caller)&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;use&lt;/span&gt; &lt;span class="nn"&gt;anchor_lang&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;declare_program!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Tally&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;cpi&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nn"&gt;program&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;declare_id!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Gs4H4zaqUtRC1iPJhcvYxcQwpq4sakkHoiJBiRpCRALM"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nd"&gt;#[program]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;compose_lab&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;bump&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;Bump&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;cpi_ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CpiContext&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.counter_program&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;Increment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;tally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.tally&lt;/span&gt;&lt;span class="nf"&gt;.to_account_info&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nn"&gt;cpi&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpi_ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&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;span class="p"&gt;}&lt;/span&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;Bump&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="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;tally&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;Tally&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;counter_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;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two independent programs composing atomically. If the CPI fails, the whole &lt;br&gt;
outer transaction rolls back — no partial state. The caller never owns the &lt;br&gt;
&lt;code&gt;Tally&lt;/code&gt; account. It just passes it through. Ownership stays with &lt;code&gt;counter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Test output: &lt;code&gt;counter value set by the caller: 1&lt;/code&gt; — one passing test.&lt;/p&gt;

&lt;p&gt;Full source: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-74-compose-lab" rel="noopener noreferrer"&gt;day-74-compose-lab&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 75: The error that taught me the most
&lt;/h2&gt;

&lt;p&gt;On Day 75 I deliberately broke three CPIs to learn how to read the logs. &lt;br&gt;
The most useful failure was changing &lt;code&gt;b"vault"&lt;/code&gt; to &lt;code&gt;b"vaultX"&lt;/code&gt; in the &lt;br&gt;
signer seeds on the Day 73 vault withdraw. My terminal printed:&lt;br&gt;
Program log: AnchorError caused by account: vault.&lt;br&gt;
Error Code: ConstraintSeeds&lt;br&gt;
Error Number: 2006&lt;br&gt;
Error Message: A seeds constraint was violated.&lt;/p&gt;

&lt;p&gt;Program 11111111111111111111111111111111 invoke&lt;br&gt;
Program 11111111111111111111111111111111 failed: privilege escalation&lt;br&gt;&lt;br&gt;
&lt;code&gt;ConstraintSeeds&lt;/code&gt; is Anchor telling you the seeds you passed do not reproduce &lt;br&gt;
the PDA it expects. The &lt;code&gt;privilege escalation&lt;/code&gt; below it is the System Program &lt;br&gt;
refusing to move lamports from an account you do not control. These two errors &lt;br&gt;
arrive together every time this happens. Start by checking that every byte in &lt;br&gt;
your &lt;code&gt;signer_seeds&lt;/code&gt; matches the corresponding byte in your &lt;br&gt;
&lt;code&gt;#[account(seeds = [...])]&lt;/code&gt; attribute, including the bump, and that the bump &lt;br&gt;
comes from &lt;code&gt;ctx.bumps&lt;/code&gt; not a hardcoded value.&lt;/p&gt;

&lt;p&gt;The three-category mental model I built from that session:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;Where to look&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ConstraintSeeds&lt;/code&gt; + &lt;code&gt;privilege escalation&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Seeds or bump mismatch in &lt;code&gt;signer_seeds&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ConstraintHasOne&lt;/code&gt; with an account name&lt;/td&gt;
&lt;td&gt;Caller did not satisfy callee's &lt;code&gt;has_one&lt;/code&gt; constraint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;invalid instruction data&lt;/code&gt; from a wrong program&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CpiContext&lt;/code&gt; is pointing at the wrong program ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full source: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-75-cpi-failures" rel="noopener noreferrer"&gt;day-75-cpi-failures&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern is the same every time
&lt;/h2&gt;

&lt;p&gt;After five days across four different callees the &lt;code&gt;CpiContext&lt;/code&gt; shape never &lt;br&gt;
changed. What changed was which accounts struct you fill in and whether you &lt;br&gt;
use &lt;code&gt;new&lt;/code&gt; or &lt;code&gt;new_with_signer&lt;/code&gt;. That is the whole surface area of the API.&lt;/p&gt;

&lt;p&gt;If you want the full picture from the official sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://solana.com/docs/core/cpi" rel="noopener noreferrer"&gt;Solana docs: Cross-Program Invocations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.anchor-lang.com/docs/basics/cpi" rel="noopener noreferrer"&gt;Anchor docs: CPI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.anchor-lang.com/docs/references/macros#declare_program" rel="noopener noreferrer"&gt;Anchor docs: declare_program!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/anchor-lang/latest/anchor_lang/system_program/index.html" rel="noopener noreferrer"&gt;anchor_lang::system_program&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.solana-program.com/docs/token-2022" rel="noopener noreferrer"&gt;Token-2022 docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This post draws from Days 71 through 75 of my #100DaysOfSolana journey. &lt;br&gt;
Day 71 was the first CPI to the System Program, Day 72 was Token-2022, &lt;br&gt;
Day 73 was the PDA vault with a PDA-signed withdraw, Day 74 was one Anchor &lt;br&gt;
program calling another via &lt;code&gt;declare_program!&lt;/code&gt;, and Day 75 was breaking each &lt;br&gt;
of those deliberately and reading the logs. All the code is at &lt;br&gt;
&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;.&lt;/em&gt;                        &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>rust</category>
      <category>anchor</category>
      <category>web3</category>
    </item>
    <item>
      <title>What I Learned About PDAs in a Week of Building on Solana</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Sat, 27 Jun 2026 09:23:41 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/what-i-learned-about-pdas-in-a-week-of-building-on-solana-4n9b</link>
      <guid>https://dev.to/gopichand_dev/what-i-learned-about-pdas-in-a-week-of-building-on-solana-4n9b</guid>
      <description>&lt;p&gt;I spent five days building with PDAs on Solana — Day 64 through Day 68 of my #100DaysOfSolana challenge. By the end I had a working counter program, a config singleton, a close instruction that returned real lamports, and a seed collision experiment where Anchor rejected a spoofed transaction live in my terminal.&lt;/p&gt;

&lt;p&gt;This post is the explainer I wish I had on Day 1. It is aimed at backend engineers who know databases but have never touched Solana.&lt;/p&gt;

&lt;p&gt;All the code is real. Every terminal output below came from my actual runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem PDAs Solve
&lt;/h2&gt;

&lt;p&gt;On Solana, programs are stateless. They hold no memory between calls. Every time your program runs, it starts fresh.&lt;/p&gt;

&lt;p&gt;So if your program needs to remember something — a user's score, a game state, a global config — it has to store that data in a separate &lt;strong&gt;account&lt;/strong&gt; on-chain. And to find that account again later, it needs a deterministic address it can recompute without storing it anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDAs are that address.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Program Derived Address is a 32-byte public key that your program derives on demand from a set of seeds. Give it the same seeds tomorrow, six months from now, from any machine in the world, and you get the same address back. No lookup table. No database. Pure computation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;

&lt;p&gt;Here is the Web2 analogy that finally made it click for me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 A PDA is like a database primary key you compute from the row's logical identity — except the "database" is the entire Solana account model, and the program ID is baked into every key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a relational database you might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- primary key computed from logical identity&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;program_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'my_program'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'alice'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'counter'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Solana that becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA256(
  "counter"                     ← static seed / namespace
  + alice_wallet_pubkey         ← dynamic seed / row identity
  + program_id                  ← always included automatically
  + "ProgramDerivedAddress"     ← Solana's magic suffix
)
→ 32-byte address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where the analogy breaks — and you must know this:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDAs are &lt;strong&gt;not rows in a table&lt;/strong&gt;. The address exists mathematically before any account is created at it. Deriving and creating are two separate steps.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;program ID is baked in&lt;/strong&gt;. Same seeds, different program → completely different address. Your seeds are never globally unique.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No private key can ever exist for a PDA&lt;/strong&gt;. PDAs land off the Ed25519 elliptic curve on purpose. Only your program can authorize actions on their behalf.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Anatomy of a Derivation
&lt;/h2&gt;

&lt;p&gt;Here is the exact Anchor constraint from my counter program:&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;#[account(&lt;/span&gt;
    &lt;span class="nd"&gt;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;user,&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="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bump&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;b"counter"&lt;/code&gt; — the static seed prefix
&lt;/h3&gt;

&lt;p&gt;A hardcoded byte string that acts as a namespace. It says: &lt;em&gt;"this PDA belongs to the counter feature."&lt;/em&gt; If the same program also has a vault PDA, it uses &lt;code&gt;b"vault"&lt;/code&gt; — different prefix, guaranteed no collision.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;user.key().as_ref()&lt;/code&gt; — the dynamic seed
&lt;/h3&gt;

&lt;p&gt;The wallet public key of whoever is calling the instruction. Including it means every wallet gets its own independent PDA. Alice's counter and Bob's counter will always be at different addresses.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually gets hashed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA256(seeds[0] + seeds[1] + ... + program_id + "ProgramDerivedAddress")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program ID is always appended automatically. You never pass it manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;bump&lt;/code&gt; — not magic, just one byte
&lt;/h3&gt;

&lt;p&gt;This is the part that confused me most. The derivation needs to land &lt;strong&gt;off&lt;/strong&gt; the Ed25519 curve, because on-curve addresses could theoretically have private keys.&lt;/p&gt;

&lt;p&gt;But most SHA256 outputs land on the curve. So Solana iterates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bump = 255 → hash → on curve?  ❌ try again
bump = 254 → hash → on curve?  ❌ try again
bump = 253 → hash → off curve? ✅ canonical bump found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bump is simply the first value that produced a valid off-curve address. Anchor calls &lt;code&gt;find_program_address&lt;/code&gt; automatically and stores the result when you write &lt;code&gt;bump&lt;/code&gt; in the constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Seeds Matter: Every Byte Counts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Per-user PDAs: include the wallet pubkey
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pdaA&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;walletA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="nx"&gt;programId&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="nx"&gt;pdaB&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;walletB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="nx"&gt;programId&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;Terminal output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A PDA : 6khGZheFP1Yn61p7VJmwQtPd5gc5S2BHru8nugJFBHXy
Wallet B PDA : EEwiF4AFLd5nEFgYJA6SN6bRQPEaH9kHj4a8GtkdkyJx
Same address?: false ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two wallets, two completely separate accounts. Use this pattern for per-user counters, inventories, profiles — any data that belongs to one wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global singleton PDA: omit the wallet pubkey
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;globalPDA_fromA&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="nx"&gt;programId&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="nx"&gt;globalPDA_fromB&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
  &lt;span class="nx"&gt;programId&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;Terminal output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Derived from A: 4f9BPE2BaRMG7SH339sw6dTigKqGJdeSKrtfaiZJXhmc
Derived from B: 4f9BPE2BaRMG7SH339sw6dTigKqGJdeSKrtfaiZJXhmc
Same address?: true ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same address from every wallet. Use this for a &lt;strong&gt;global config singleton&lt;/strong&gt; — one account the whole program shares. My Day 66 config PDA used exactly 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="nd"&gt;#[account(&lt;/span&gt;
    &lt;span class="nd"&gt;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;admin,&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;Config::INIT_SPACE,&lt;/span&gt;
    &lt;span class="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"config"&lt;/span&gt;&lt;span class="nd"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bump&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;config&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;Config&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Near-miss variants — one byte, totally different address
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seeds = ["counter",   walletA]  →  6khGZheFP1Yn61p7VJmwQtPd5gc5S2BHru8nugJFBHXy
seeds = ["counters",  walletA]  →  A2EKGjtfrPaHWjfoBLaMVtMu7drnAnhUgRZAKh4PmRBa
seeds = ["counter\0", walletA]  →  FfcErcB7U2FkY3YJNiC5UbejQZ7wH8uUEasK6DBxUkkd
seeds = ["Counter",   walletA]  →  FuR4MUZVwDK5qrKA8dpxHH6PY5Xo4uhGSXQLbEeTLS64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One extra letter. One null byte. One capital C. Four completely different addresses. There is no "close enough" in PDA space. Your seeds are an exact hash input.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Bump Buys You
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Always use the canonical bump
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;find_program_address&lt;/code&gt; tries bumps from 255 downward and returns the first one that works. That is the canonical bump — the only safe one to use, because it is deterministic. Everyone who derives this PDA will always get the same bump.&lt;/p&gt;

&lt;p&gt;There is a lower-level function called &lt;code&gt;create_program_address&lt;/code&gt; that lets you pass any bump. Do not use it unless you have a specific reason. If you pass the wrong bump, you silently derive a different address and the program rejects the account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store the bump once, re-pass it forever
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[account]&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(InitSpace)]&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;Counter&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;Pubkey&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;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nb"&gt;u64&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;bump&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// ← stored at init time, never re-derived&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At &lt;code&gt;init&lt;/code&gt;, Anchor stores the canonical bump:&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.user&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="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.bump&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;.bumps.counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ← store it here&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;On every subsequent instruction, re-pass the stored bump:&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="c1"&gt;// ✅ Correct — reads 1 stored byte, free&lt;/span&gt;
&lt;span class="nd"&gt;#[account(&lt;/span&gt;
    &lt;span class="nd"&gt;mut,&lt;/span&gt;
    &lt;span class="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.bump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&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;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="c1"&gt;// ❌ Avoid — Anchor re-runs find_program_address, loops up to 256 times&lt;/span&gt;
&lt;span class="nd"&gt;#[account(&lt;/span&gt;
    &lt;span class="nd"&gt;mut,&lt;/span&gt;
    &lt;span class="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="p"&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Full Lifecycle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Derive (off-chain, zero cost, no transaction)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;counterPDA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="dl"&gt;"&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;publicKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
  &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;programId&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// The address exists mathematically. No account at this address yet.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 — Init (creates the account on-chain, pays rent)
&lt;/h3&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.user&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="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.bump&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;.bumps.counter&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;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(&lt;/span&gt;
        &lt;span class="nd"&gt;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;user,&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="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bump&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;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;user&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;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is rent?&lt;/strong&gt; On Solana, storing data on-chain requires a lamport deposit. A &lt;strong&gt;lamport&lt;/strong&gt; is the smallest unit of SOL — 1 SOL equals 1,000,000,000 lamports. If an account holds enough lamports to cover ~2 years of storage, it is "rent-exempt" and lives forever. That deposit is fully yours — you get every lamport back when you close the account.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My counter account cost &lt;strong&gt;1,231,920 lamports&lt;/strong&gt; to create.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Mutate (update data, no new account creation)
&lt;/h3&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;increment&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;Increment&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="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.counter.count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;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;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(&lt;/span&gt;
        &lt;span class="nd"&gt;mut,&lt;/span&gt;
        &lt;span class="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.bump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// asserts counter.authority == authority.key()&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;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;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;SystemAccount&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;&lt;code&gt;has_one = authority&lt;/code&gt; is Anchor shorthand that verifies &lt;code&gt;counter.authority == ctx.accounts.authority.key()&lt;/code&gt; before any logic runs. No custom auth code needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — Close (reclaim rent, mark for garbage collection)
&lt;/h3&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;CloseCounter&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(&lt;/span&gt;
        &lt;span class="nd"&gt;mut,&lt;/span&gt;
        &lt;span class="nd"&gt;seeds&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="err"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="nd"&gt;user&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;key()&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;as_ref()]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;bump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.bump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;has_one&lt;/span&gt; &lt;span class="o"&gt;=&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;close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authority&lt;/span&gt;      &lt;span class="c1"&gt;// zeroes data, transfers lamports back&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;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;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;SystemAccount&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;&lt;strong&gt;&lt;code&gt;close&lt;/code&gt; is not &lt;code&gt;DELETE FROM table&lt;/code&gt;.&lt;/strong&gt; Here is exactly what happens at end of transaction:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Account data is zeroed&lt;/li&gt;
&lt;li&gt;Lamport balance is transferred to &lt;code&gt;authority&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Account is marked for garbage collection by the runtime&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The PDA address still exists mathematically. You could &lt;code&gt;init&lt;/code&gt; a new account at the same address tomorrow. A closed-then-reopened account is called a &lt;strong&gt;reinitialization attack&lt;/strong&gt; — Anchor's &lt;code&gt;init&lt;/code&gt; constraint protects against it by checking the 8-byte account discriminator on every init.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terminal output after close:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Counter closed ✅
Lamports returned : 1,231,920
getAccountInfo    : null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1,231,920 lamports back in my wallet. Real money returned from storage I no longer needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Spoof Test: Where ConstraintSeeds Earns Its Name
&lt;/h2&gt;

&lt;p&gt;On Day 68 I tried to pass Wallet B's counter PDA to a transaction signed by Wallet A:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// walletA is the signer&lt;/span&gt;
&lt;span class="c1"&gt;// pdaB is wallet B's counter — wrong PDA for this signer&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;methods&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nx"&gt;pdaB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="c1"&gt;// ← attacker passes wrong PDA&lt;/span&gt;
    &lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;walletA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&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;signers&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;walletA&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&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;Terminal output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AnchorError caused by account: counter.
Error Code: ConstraintSeeds. Error Number: 2006.
Error Message: A seeds constraint was violated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is exactly what Anchor did under the hood:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Instruction arrives: &lt;code&gt;counter = pdaB&lt;/code&gt;, &lt;code&gt;authority = walletA&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Anchor re-derives: &lt;code&gt;seeds = [b"counter", walletA.key()]&lt;/code&gt; → gets &lt;code&gt;pdaA&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pdaA ≠ pdaB&lt;/code&gt; → throws &lt;code&gt;ConstraintSeeds&lt;/code&gt; immediately&lt;/li&gt;
&lt;li&gt;My business logic never ran&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You did not write that security check. Anchor did. &lt;strong&gt;The seeds are the access control.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Would Tell Past Me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🔑 The program ID is always part of the derivation.&lt;/strong&gt;&lt;br&gt;
Same seeds in a different program → completely different PDA. Your seeds are only unique within your specific program. This is a feature — it means no other program can collide with your PDAs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔑 PDAs cannot sign transactions on their own.&lt;/strong&gt;&lt;br&gt;
They have no private key by design. Only programs can sign on a PDA's behalf by passing &lt;code&gt;signer_seeds&lt;/code&gt; to &lt;code&gt;invoke_signed&lt;/code&gt;. If you ever need a PDA to transfer lamports or call another program, this is the mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔑 Always store and re-pass the canonical bump.&lt;/strong&gt;&lt;br&gt;
Find it once at &lt;code&gt;init&lt;/code&gt; with &lt;code&gt;ctx.bumps.counter&lt;/code&gt;, store it as a &lt;code&gt;u8&lt;/code&gt; in your account struct, and pass &lt;code&gt;bump = counter.bump&lt;/code&gt; on every subsequent instruction. Re-derivation loops up to 256 times. Reading one stored byte is free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔑 &lt;code&gt;init_if_needed&lt;/code&gt; is a footgun.&lt;/strong&gt;&lt;br&gt;
It is convenient for accounts that should be created on first use. But if an attacker can trigger it after you expect the account to exist, they can reset your state. Only reach for it deliberately, with extra constraints guarding against reinitialisation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔑 Closing is not deleting.&lt;/strong&gt;&lt;br&gt;
The PDA address is a derivation — it always exists mathematically. &lt;code&gt;close&lt;/code&gt; returns the lamports and zeroes the data, but the address is re-derivable forever. Build your program assuming a closed account can be reopened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources and Code
&lt;/h2&gt;

&lt;p&gt;Everything I cited in this post is verifiable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://solana.com/docs/core/pda" rel="noopener noreferrer"&gt;Solana PDA documentation&lt;/a&gt; — canonical explanation of off-curve derivation and &lt;code&gt;find_program_address&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anchor-lang.com/docs/basics/pda" rel="noopener noreferrer"&gt;Anchor PDA guide&lt;/a&gt; — constraints, bump storage, &lt;code&gt;init_if_needed&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.rs/anchor-lang/latest/anchor_lang/" rel="noopener noreferrer"&gt;anchor-lang crate docs&lt;/a&gt; — full API reference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My code from this week — all five days, live on GitHub:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Day 64&lt;/td&gt;
&lt;td&gt;Derive PDA&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-64-derive-pda" rel="noopener noreferrer"&gt;day-64-derive-pda&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 65&lt;/td&gt;
&lt;td&gt;PDA Counter&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-65-pda-counter" rel="noopener noreferrer"&gt;day-65-pda-counter&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 66&lt;/td&gt;
&lt;td&gt;Config PDA&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-66-config-pda" rel="noopener noreferrer"&gt;day-66-config-pda&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 67&lt;/td&gt;
&lt;td&gt;Close PDA&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-67-close-pda" rel="noopener noreferrer"&gt;day-67-close-pda&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 68&lt;/td&gt;
&lt;td&gt;PDA Collision Explorer&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-68-pda-collisions" rel="noopener noreferrer"&gt;day-68-pda-collisions&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full repo: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is Day 69 of my &lt;a href="https://dev.to/t/100daysofsolana"&gt;#100DaysOfSolana&lt;/a&gt; build log. If something here is wrong or unclear, tell me in the comments — I am still learning and I will fix it.&lt;/p&gt;




</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>rust</category>
      <category>anchor</category>
    </item>
    <item>
      <title>How I Built a Counter Program in Anchor and Learned to Trust My Tests</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Sat, 20 Jun 2026 05:53:59 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/how-i-built-a-counter-program-in-anchor-and-learned-to-trust-my-tests-4afi</link>
      <guid>https://dev.to/gopichand_dev/how-i-built-a-counter-program-in-anchor-and-learned-to-trust-my-tests-4afi</guid>
      <description>&lt;p&gt;I spent Days 57–61 building the same counter program over and over.&lt;br&gt;
Not because I kept breaking it (well, I did), but because each day&lt;br&gt;
revealed something about Anchor that the previous day's green tests&lt;br&gt;
had been quietly hiding.&lt;/p&gt;

&lt;p&gt;This is that story.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Accounts Struct — Where Anchor Earns Its Name
&lt;/h2&gt;

&lt;p&gt;Every Anchor instruction starts with an accounts struct. Here's mine&lt;br&gt;
for &lt;code&gt;Initialize&lt;/code&gt;:&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="mi"&gt;40&lt;/span&gt;&lt;span class="nd"&gt;)]&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;Three fields, three jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;counter&lt;/code&gt;&lt;/strong&gt; — the new on-chain account being created. Anchor's
&lt;code&gt;init&lt;/code&gt; constraint handles the &lt;code&gt;create_account&lt;/code&gt; CPI to the System
Program automatically. &lt;code&gt;space = 8 + 40&lt;/code&gt; is the discriminator (8
bytes) plus the actual struct data (40 bytes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;authority&lt;/code&gt;&lt;/strong&gt; — the wallet paying for the account and signing
the transaction. &lt;code&gt;mut&lt;/code&gt; is required because its lamport balance
will decrease.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;system_program&lt;/code&gt;&lt;/strong&gt; — Solana requires you to pass the System
Program explicitly whenever you're creating accounts. Nothing
happens if it's missing — the transaction just fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coming from backend development, this was the first thing that&lt;br&gt;
genuinely surprised me. In Web2 you pass data to a function. In&lt;br&gt;
Anchor you first declare every account your instruction will touch,&lt;br&gt;
with its permissions, and Anchor verifies all of it before your&lt;br&gt;
handler code runs even one line.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Handlers — Short by Design
&lt;/h2&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;.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="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="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 you typed, validated access to every account&lt;br&gt;
declared in the struct above. The handler is three lines because&lt;br&gt;
Anchor already did the hard work — account creation, lamport&lt;br&gt;
deduction, ownership checks — before this function was called.&lt;/p&gt;

&lt;p&gt;Now &lt;code&gt;increment&lt;/code&gt;:&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;increment&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;Increment&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;.count&lt;/span&gt; &lt;span class="o"&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="nf"&gt;.checked_add&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="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Overflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&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;And its accounts struct:&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;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;&lt;code&gt;has_one = authority&lt;/code&gt; is the constraint that does the real work.&lt;br&gt;
Before &lt;code&gt;increment&lt;/code&gt; runs, Anchor checks that &lt;code&gt;counter.authority ==&lt;br&gt;
authority.key()&lt;/code&gt;. If a different wallet signs, the transaction&lt;br&gt;
reverts with &lt;code&gt;ConstraintHasOne&lt;/code&gt; (Error 2001) before a single line&lt;br&gt;
of my handler executes. The constraint is a pre-condition, not a&lt;br&gt;
runtime check I have to write myself.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Tests — Two Paths That Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Happy path:&lt;/strong&gt;&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;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize_then_increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... setup ...&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svm&lt;/span&gt;&lt;span class="nf"&gt;.send_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;increment_tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="nf"&gt;.is_ok&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="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svm&lt;/span&gt;&lt;span class="nf"&gt;.get_account_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;counter_kp&lt;/span&gt;&lt;span class="nf"&gt;.pubkey&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;counter&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What would have to go wrong for this to fail? The &lt;code&gt;increment&lt;/code&gt;&lt;br&gt;
handler would have to not add 1 — either wrong arithmetic, a&lt;br&gt;
missing account write-back, or a serialization error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure path — wrong authority:&lt;/strong&gt;&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;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;increment_fails_when_wrong_authority_signs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// wrong_wallet is a fresh keypair, not the counter's authority&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;svm&lt;/span&gt;&lt;span class="nf"&gt;.send_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad_tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&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 wrong authority"&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;What would have to go wrong for &lt;em&gt;this&lt;/em&gt; to fail? The &lt;code&gt;has_one&lt;/code&gt;&lt;br&gt;
constraint would have to be missing or wrong. Without it, any&lt;br&gt;
wallet could increment anyone's counter — which is exactly the&lt;br&gt;
exploit this test exists to prevent.&lt;/p&gt;


&lt;h2&gt;
  
  
  Day 61 — Mutation Testing: Proving the Tests Are Real
&lt;/h2&gt;

&lt;p&gt;Green tests don't prove your code is correct. They prove your code&lt;br&gt;
satisfies your tests. Those are different things if your tests are&lt;br&gt;
wrong.&lt;/p&gt;

&lt;p&gt;So on Day 61 I intentionally broke the program three ways and&lt;br&gt;
watched the test suite respond.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 2: I changed &lt;code&gt;checked_add(1)&lt;/code&gt; to &lt;code&gt;checked_add(2)&lt;/code&gt;&lt;/strong&gt;&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="c1"&gt;// Before&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="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.count&lt;/span&gt;&lt;span class="nf"&gt;.checked_add&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="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Overflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// After (intentional bug)&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="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.count&lt;/span&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;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Overflow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test output:&lt;br&gt;
thread 'initialize_then_increment' panicked at programs/counter/tests/counter.rs:97:5:&lt;br&gt;
assertion left == right failed&lt;br&gt;
left: 2&lt;br&gt;
right: 1&lt;br&gt;&lt;br&gt;
The test caught it immediately. The assertion &lt;code&gt;assert_eq!(counter.count, 1)&lt;/code&gt; exists precisely because of off-by-one errors like this. Before this experiment I had pattern-matched that assertion from a tutorial. After the experiment I understood &lt;em&gt;why&lt;/em&gt; it has to be exactly 1 and not "nonzero" or "greater than 0."&lt;/p&gt;

&lt;p&gt;I ran &lt;code&gt;git restore&lt;/code&gt; and all three tests went green again.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Writing This Revealed
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;has_one&lt;/code&gt; constraint took me three drafts to explain cleanly.&lt;br&gt;
I had been thinking of it as "check the authority" — vague enough&lt;br&gt;
that I couldn't have told you exactly when it runs or what it&lt;br&gt;
compares. Writing forced precision: it compares &lt;code&gt;counter.authority&lt;/code&gt;&lt;br&gt;
(stored on-chain at init time) against the &lt;code&gt;authority&lt;/code&gt; account&lt;br&gt;
passed in at increment time, and it runs before the handler, not&lt;br&gt;
inside it.&lt;/p&gt;

&lt;p&gt;That distinction — constraint vs. runtime check — is the thing&lt;br&gt;
I would have gotten wrong in a code review before this week.&lt;/p&gt;




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

&lt;p&gt;Week 10 will push past single-account programs. I want to build&lt;br&gt;
something with multiple accounts interacting — a vault, an escrow,&lt;br&gt;
or a simple DEX instruction — where the account constraints do&lt;br&gt;
more than guard a single field. That's where I expect the next&lt;br&gt;
gap to show up.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-61-mutation-testing" rel="noopener noreferrer"&gt;Full code on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 62 of 100. Building daily.&lt;/em&gt;                   &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>rust</category>
      <category>anchor</category>
      <category>testing</category>
    </item>
    <item>
      <title>Three Token-2022 Mints in One Week: Fees, Yield, and Soul-Bound</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Fri, 12 Jun 2026 09:10:31 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/three-token-2022-mints-in-one-week-fees-yield-and-soul-bound-2b5k</link>
      <guid>https://dev.to/gopichand_dev/three-token-2022-mints-in-one-week-fees-yield-and-soul-bound-2b5k</guid>
      <description>&lt;p&gt;If you have built middleware in Web2, you already understand Token-2022 extensions.&lt;br&gt;
The old SPL token program is like a plain Express router. Token-2022 is the same&lt;br&gt;
router with a plugin system baked in. You opt a single mint into behaviors — fees,&lt;br&gt;
interest, transfer locks — at creation time, and the protocol enforces them forever.&lt;br&gt;
No forking, no custom Rust, no deploying your own program. Just flags.&lt;/p&gt;

&lt;p&gt;I spent Days 50–54 of my #100DaysOfSolana challenge shipping three mints that each&lt;br&gt;
demonstrate one of those behaviors. Here is what I built, the exact commands I ran,&lt;br&gt;
and when you would actually reach for each extension.&lt;/p&gt;


&lt;h2&gt;
  
  
  Mint 1 — Transfer Fee (Days 50 &amp;amp; 51)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;HxDYFvcXnLuy4VdxXCooUXrch8DZW34oUteQ6N2EFxEr&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Explorer:&lt;/strong&gt; &lt;a href="https://explorer.solana.com/address/HxDYFvcXnLuy4VdxXCooUXrch8DZW34oUteQ6N2EFxEr?cluster=devnet" rel="noopener noreferrer"&gt;View on Devnet&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Extension:&lt;/strong&gt; &lt;code&gt;TransferFeeConfig&lt;/code&gt;&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--transfer-fee-basis-points 100&lt;/code&gt; means 1% of every transfer is withheld before&lt;br&gt;
the recipient gets credited. The withheld amount sits in the recipient's token&lt;br&gt;
account until a privileged instruction sweeps it to the treasury.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When would you use this?&lt;/strong&gt;&lt;br&gt;
Creator royalties on a community token. A protocol fee on a stablecoin. A DAO&lt;br&gt;
treasury skim that funds development every time the token changes hands. The key&lt;br&gt;
insight: the fee is enforced by the Token-2022 program itself, not by your API.&lt;br&gt;
Nobody can route around it by calling the program directly.&lt;/p&gt;

&lt;p&gt;On Day 51 I ran the full lifecycle — transfer, inspect the withheld amount, then&lt;br&gt;
sweep it back:&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="nv"&gt;$MINT&lt;/span&gt; 1000 &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; &lt;span class="nt"&gt;--expected-fee&lt;/span&gt; 10
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;The &lt;code&gt;--expected-fee 10&lt;/code&gt; flag is a safety assertion. If the mint's fee math&lt;br&gt;
doesn't produce exactly 10 tokens withheld, the instruction aborts. It is the&lt;br&gt;
on-chain equivalent of an idempotency check.&lt;/p&gt;


&lt;h2&gt;
  
  
  Mint 2 — Transfer Fee + Interest Stacked (Day 52)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;A6TAeNgxBVwYna8NqQVmBpQzjVYKoZA3e68yMvoVVUva&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Explorer:&lt;/strong&gt; &lt;a href="https://explorer.solana.com/address/A6TAeNgxBVwYna8NqQVmBpQzjVYKoZA3e68yMvoVVUva?cluster=devnet" rel="noopener noreferrer"&gt;View on Devnet&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Extensions:&lt;/strong&gt; &lt;code&gt;TransferFeeConfig&lt;/code&gt; + &lt;code&gt;InterestBearingConfig&lt;/code&gt;&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;One command, two TLV entries, two completely different mechanics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the interest extension does — and does NOT do:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the subtlety most tutorials skip. The &lt;code&gt;InterestBearingConfig&lt;/code&gt; extension&lt;br&gt;
does &lt;strong&gt;not&lt;/strong&gt; mint new tokens. The raw amount stored on-chain never changes between&lt;br&gt;
transactions. What changes is the &lt;em&gt;UI amount&lt;/em&gt; — the number your wallet displays.&lt;/p&gt;

&lt;p&gt;The formula is: &lt;code&gt;UI amount = raw_amount × e^(rate × time_elapsed)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The network clock and the rate stored on the mint are all the CLI needs to compute&lt;br&gt;
a growing display number every time you query. I proved this by reading the balance&lt;br&gt;
twice with a 30-second sleep between them, with zero transactions in between:&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 accounts &lt;span class="nv"&gt;$MINT&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==3'&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;30
spl-token accounts &lt;span class="nv"&gt;$MINT&lt;/span&gt; &lt;span class="nt"&gt;--verbose&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'NR==3'&lt;/span&gt;
&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# 999032.271358&lt;/span&gt;
&lt;span class="c"&gt;# 999032.762062   ← +0.49 tokens, no transaction fired&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of it like a savings account display ticker. The number on screen grows.&lt;br&gt;
The ledger entry does not change until a real transaction touches it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two extensions, zero conflict:&lt;/strong&gt; TransferFeeConfig operates on raw amounts at&lt;br&gt;
transfer time. InterestBearingConfig operates on the display layer at query time.&lt;br&gt;
They are orthogonal, which is why they compose cleanly on a single mint.&lt;/p&gt;


&lt;h2&gt;
  
  
  Mint 3 — Non-Transferable / Soul-Bound (Day 54)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;BQzJeZVZgkSvAYPj9f1M1apv56P7kggAgPNc9uSq7T5m&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Explorer:&lt;/strong&gt; &lt;a href="https://explorer.solana.com/address/BQzJeZVZgkSvAYPj9f1M1apv56P7kggAgPNc9uSq7T5m?cluster=devnet" rel="noopener noreferrer"&gt;View on Devnet&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Extension:&lt;/strong&gt; &lt;code&gt;Non-transferable&lt;/code&gt;&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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I minted one badge token to myself, then deliberately tried to send it to a throwaway wallet. Here is the exact runtime rejection:&lt;br&gt;
Program log: Instruction: TransferChecked&lt;br&gt;
Program log: Transfer is disabled for this mint&lt;br&gt;
Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: custom program error: 0x25&lt;br&gt;&lt;br&gt;
Error &lt;code&gt;0x25&lt;/code&gt; is decimal 37, the &lt;code&gt;NonTransferable&lt;/code&gt; entry in the Token-2022 error&lt;br&gt;
enum. The rejection came from the validator, not from the CLI or the RPC layer.&lt;br&gt;
There is no way to call the program "around" the extension. The rule lives on the&lt;br&gt;
asset.&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="nv"&gt;$MINT&lt;/span&gt;
&lt;span class="c"&gt;# Extensions&lt;/span&gt;
&lt;span class="c"&gt;#   Non-transferable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When would you use this?&lt;/strong&gt;&lt;br&gt;
Completion certificates. Event attendance proofs. KYC credentials tied to a&lt;br&gt;
specific wallet. Anything where the point is that the credential belongs to the&lt;br&gt;
holder and cannot be resold or delegated.&lt;/p&gt;

&lt;p&gt;In Web2 you would enforce this with a database constraint or an API check. The&lt;br&gt;
risk is that anyone who can reach the database directly can break the rule. On&lt;br&gt;
Solana the rule is in the program. The program is in the validator. There is no&lt;br&gt;
around.&lt;/p&gt;




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

&lt;p&gt;I expected the stacking to be complicated. It wasn't. Two flags at creation time,&lt;br&gt;
two TLV entries in the same byte buffer, and the CLI just prints both in the&lt;br&gt;
&lt;code&gt;Extensions&lt;/code&gt; block of &lt;code&gt;spl-token display&lt;/code&gt;. The Token-2022 design is genuinely&lt;br&gt;
composable in a way that feels like it was designed by someone who got burned by&lt;br&gt;
non-composable systems before.&lt;/p&gt;

&lt;p&gt;The interest extension surprised me most. I came in expecting it to mint tokens.&lt;br&gt;
It doesn't. It's a view function disguised as a balance. Once I understood that,&lt;br&gt;
I stopped thinking of it as a financial primitive and started thinking of it as&lt;br&gt;
a display configuration. That reframe changed how I'd use it in a real product —&lt;br&gt;
probably for a points or loyalty system where the raw backing supply is fixed but&lt;br&gt;
the displayed "value" grows over time.&lt;/p&gt;

&lt;p&gt;If I were building a real product today, I'd reach for TransferFeeConfig for any&lt;br&gt;
token that needs sustainable protocol revenue, and NonTransferable for any&lt;br&gt;
credential or badge that should be identity-bound. The interest extension I'd hold&lt;br&gt;
for a specific display-layer use case where the raw supply needs to stay auditable&lt;br&gt;
but the user-facing number should grow.&lt;/p&gt;

&lt;p&gt;All code and terminal output is in my public build log:&lt;br&gt;
👉 &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;https://github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 55 of #100DaysOfSolana&lt;/em&gt;                  &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>web3</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
    <item>
      <title>Solana NFTs Without Metaplex: What I Learned Building with Token Extensions published: true</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Tue, 09 Jun 2026 05:22:24 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/solana-nfts-without-metaplex-what-i-learned-building-with-token-extensionspublished-true-1070</link>
      <guid>https://dev.to/gopichand_dev/solana-nfts-without-metaplex-what-i-learned-building-with-token-extensionspublished-true-1070</guid>
      <description>&lt;p&gt;Before this week I thought you needed Metaplex to build a real Solana NFT. It turns out you can mint a full NFT, stamp metadata directly onto it, group it inside a collection, audit every byte, and mutate it live — using only the Token Extensions program and the SPL token CLI. No third-party framework required.&lt;/p&gt;

&lt;p&gt;This post covers what I built during Days 44–47 of &lt;a href="https://mlh.link/solana-100" rel="noopener noreferrer"&gt;#100DaysOfSolana&lt;/a&gt;, what surprised me coming from a Web2 background, and what I would build next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model: What a Solana NFT Actually Is
&lt;/h2&gt;

&lt;p&gt;Before writing a single command, I had to unlearn something. In Web2, an NFT feels like a record in a database that points to a JPEG. On Solana, it is simpler and more precise than that.&lt;/p&gt;

&lt;p&gt;A Solana NFT is just a &lt;strong&gt;mint account&lt;/strong&gt; with three properties set just right:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Supply&lt;/td&gt;
&lt;td&gt;1 (exactly one token exists)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decimals&lt;/td&gt;
&lt;td&gt;0 (cannot be split)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mint authority&lt;/td&gt;
&lt;td&gt;(not set) — disabled forever&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is it. The "NFT-ness" is not a special program. It is a configuration of the same &lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;SPL Token primitives&lt;/a&gt; that power every fungible token on the network.&lt;/p&gt;

&lt;p&gt;What makes a &lt;em&gt;modern&lt;/em&gt; Solana NFT different from a bare 1-of-1 token is the &lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;Token Extensions program (Token-2022)&lt;/a&gt;. Instead of storing metadata in a separate account managed by Metaplex, Token Extensions lets you stamp the name, symbol, URI, and custom fields directly onto the mint account itself. Everything lives in one place. Any wallet, any marketplace, any RPC node can read it without trusting a third-party indexer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Built: Four Days, Four Commands
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Day 44 — First NFT with on-chain metadata
&lt;/h3&gt;

&lt;p&gt;I minted a 1-of-1 token using the Token-2022 program, initialized a &lt;a href="https://solana.com/docs/tokens/extensions/metadata" rel="noopener noreferrer"&gt;Metadata Pointer extension&lt;/a&gt; on it, and attached a name, symbol, and URI pointing to a JSON file on GitHub Gist.&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;# Create the mint with metadata extension enabled&lt;/span&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; 0 &lt;span class="se"&gt;\&lt;/span&gt;
  ./nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF.json

&lt;span class="c"&gt;# Stamp name, symbol, and URI directly onto the mint&lt;/span&gt;
spl-token initialize-metadata &lt;span class="se"&gt;\&lt;/span&gt;
  nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"First Light"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"LIGHT"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://gist.githubusercontent.com/gopichandchalla16/..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mint address: &lt;code&gt;nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF&lt;/code&gt;&lt;br&gt;
&lt;a href="https://explorer.solana.com/address/nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF?cluster=devnet" rel="noopener noreferrer"&gt;View on Solana Explorer (devnet)&lt;/a&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  Day 45 — NFT collection using Group and Member extensions
&lt;/h3&gt;

&lt;p&gt;I created a separate collection mint using the &lt;a href="https://solana.com/docs/tokens/extensions/group-member" rel="noopener noreferrer"&gt;Group extension&lt;/a&gt;, then minted two member NFTs and registered each one under the collection using the Member extension.&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;# Register a member NFT into the collection&lt;/span&gt;
spl-token group-member-initialize &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;MEMBER_MINT&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;COLLECTION_MINT&amp;gt; &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;The collection mint ended up with &lt;code&gt;Size: 2, Max Size: 3&lt;/code&gt;. Each member NFT carries a &lt;code&gt;Group&lt;/code&gt; field that points byte-for-byte to the collection mint — the on-chain equivalent of a foreign key.&lt;/p&gt;




&lt;h3&gt;
  
  
  Day 46 — Auditing every byte on devnet
&lt;/h3&gt;

&lt;p&gt;Instead of trusting that everything worked, I read both mints back from devnet the same way a senior engineer reviews their own work before shipping.&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 nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF
spl-token display AC3peC3tdZUnLY44zGYC7YAuEaPknkMcRqsmAyvXJtMx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key check was this line inside &lt;code&gt;Token Group Member&lt;/code&gt; on each member NFT:&lt;br&gt;
Group: AC3peC3tdZUnLY44zGYC7YAuEaPknkMcRqsmAyvXJtMx ✅&lt;br&gt;&lt;br&gt;
That address matches the collection mint exactly. Phantom and every marketplace reads this field to verify collection membership — without trusting any off-chain index. That is what "verifiable provenance" actually means. Not marketing. Just two byte arrays comparing equal.&lt;/p&gt;


&lt;h3&gt;
  
  
  Day 47 — Mutating metadata live
&lt;/h3&gt;

&lt;p&gt;This was the day I stopped treating the NFT like a fragile artifact and started treating it like a live row of data.&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;# Rename it&lt;/span&gt;
spl-token update-metadata nftTnVuy... name &lt;span class="s2"&gt;"Field Notes"&lt;/span&gt;

&lt;span class="c"&gt;# Add a custom field — the schema is completely open&lt;/span&gt;
spl-token update-metadata nftTnVuy... rarity legendary

&lt;span class="c"&gt;# Remove it&lt;/span&gt;
spl-token update-metadata nftTnVuy... rarity &lt;span class="nt"&gt;--remove&lt;/span&gt;

&lt;span class="c"&gt;# Swap the URI to point at a new metadata JSON&lt;/span&gt;
spl-token update-metadata nftTnVuy... uri https://gist.githubusercontent.com/janvinsha/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every command was one transaction. Every change appeared in Explorer within seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Surprising Part
&lt;/h2&gt;

&lt;p&gt;Three things hit differently than I expected coming from Web2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The on-chain layer and the off-chain layer move at different speeds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I renamed the NFT to "Field Notes", Explorer showed the new name in seconds. But the image — which lives at the URI I pointed at, hosted on GitHub Gist — stayed cached in wallets for much longer. The on-chain pointer updates instantly. The thing it points at does not. This is why serious NFT projects use &lt;a href="https://www.arweave.org/" rel="noopener noreferrer"&gt;Arweave&lt;/a&gt; or &lt;a href="https://ipfs.tech/" rel="noopener noreferrer"&gt;IPFS&lt;/a&gt; for permanent image hosting instead of a mutable HTTP server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. There is no separate NFT program.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I kept waiting to discover the "real" NFT layer. There is none. The same Token-2022 program that handles transfer fees, interest-bearing tokens, and compliance controls is also the program that handles NFT metadata and collections. Extensions compose on the same primitives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The metadata schema is a blank canvas.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;additional_metadata&lt;/code&gt; array accepts any key-value pair you want. I stamped &lt;code&gt;rarity: legendary&lt;/code&gt; onto a live mint in one CLI call and watched it appear on Explorer. No migrations. No schema definitions. No contract upgrades. Just one transaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Would Build Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permanent image hosting&lt;/strong&gt; — move the image URI from GitHub Gist to Arweave so the pointer and the asset are both permanent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic NFT&lt;/strong&gt; — use the mutable metadata to track something that changes over time, like XP or level in a game (Solana's official &lt;a href="https://solana.com/developers/guides/token-extensions/dynamic-meta-data-nft" rel="noopener noreferrer"&gt;Dynamic Metadata NFT guide&lt;/a&gt; covers this pattern)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metaplex comparison&lt;/strong&gt; — &lt;a href="https://developers.metaplex.com/core" rel="noopener noreferrer"&gt;Metaplex Core&lt;/a&gt; is the dominant alternative NFT standard on Solana, with a different account structure and royalty model. I want to build the same collection in both standards and write a direct comparison.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources I Actually Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;Token Extensions overview&lt;/a&gt; — the canonical reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://solana.com/docs/tokens/extensions/metadata" rel="noopener noreferrer"&gt;Metadata Pointer and Token Metadata&lt;/a&gt; — how metadata lives on the mint&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://solana.com/docs/tokens/extensions/group-member" rel="noopener noreferrer"&gt;Token Groups and Members&lt;/a&gt; — how collection membership works&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://solana.com/developers/guides/token-extensions/dynamic-meta-data-nft" rel="noopener noreferrer"&gt;Dynamic Metadata NFTs guide&lt;/a&gt; — the official Solana guide for what comes next&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://explorer.solana.com/?cluster=devnet" rel="noopener noreferrer"&gt;Solana Explorer (devnet)&lt;/a&gt; — for verifying every change live&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;All the code and terminal output from every day is in my public repo:&lt;br&gt;
👉 &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is part of #100DaysOfSolana. Follow along or jump in any day at &lt;a href="https://mlh.link/solana-100" rel="noopener noreferrer"&gt;mlh.link/solana-100&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>nft</category>
    </item>
    <item>
      <title>44 Days of Solana: From an Empty README to a Live NFT on-chain — My Finish-Up-A-Thon Story</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Thu, 04 Jun 2026 05:55:51 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/44-days-of-solana-from-an-empty-readme-to-a-live-nft-on-chain-my-finish-up-a-thon-story-fmg</link>
      <guid>https://dev.to/gopichand_dev/44-days-of-solana-from-an-empty-readme-to-a-live-nft-on-chain-my-finish-up-a-thon-story-fmg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Honestly, I almost didn't write this post.&lt;/p&gt;

&lt;p&gt;Not because I didn't have anything to show — but because I kept telling myself&lt;br&gt;
"it's not done yet." Sound familiar?&lt;/p&gt;

&lt;p&gt;Back in early 2026, I started a repo called&lt;br&gt;
&lt;strong&gt;&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;100 Days of Solana&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
The idea was simple: learn Solana development from absolute zero, build&lt;br&gt;
something on-chain every single day, and document it publicly.&lt;/p&gt;

&lt;p&gt;Day 1, I generated a keypair. That was it. One file in the repo —&lt;br&gt;
a README with a title and no code. I had no blockchain background,&lt;br&gt;
only a Web2 history in Python and JavaScript. I didn't even know&lt;br&gt;
what "rent" meant in the context of Solana accounts.&lt;/p&gt;

&lt;p&gt;But I kept showing up. 44 days later, here's what that same repo became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Wallets, airdrops, SOL transfers, SPL token creation&lt;/li&gt;
&lt;li&gt;✅ Token-2022 extensions — transfer fees, interest-bearing tokens,
default frozen, non-transferable (soulbound), permanent delegate&lt;/li&gt;
&lt;li&gt;✅ A fully named, on-chain NFT — "First Light" — with metadata,
image, and permanently locked supply. Minted using &lt;strong&gt;only&lt;/strong&gt; the Solana CLI.
No Metaplex. No framework.&lt;/li&gt;
&lt;li&gt;✅ 9 published DEV.to articles translating every concept into
Web2-friendly language&lt;/li&gt;
&lt;li&gt;✅ Every single transaction signature linked to Solana Explorer
so you can verify my work on-chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This project means more to me than any side project I've ever started.&lt;br&gt;
It's proof that 30 minutes a day, compounded over 44 days,&lt;br&gt;
produces something real and verifiable.&lt;/p&gt;

&lt;p&gt;And GitHub Copilot is a big reason I didn't quit on Day 8, Day 23, or Day 39.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔗 Repo:&lt;/strong&gt; &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;https://github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;&lt;/p&gt;




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

&lt;h3&gt;
  
  
  🌐 "First Light" — My First NFT, Live on Solana Devnet
&lt;/h3&gt;

&lt;p&gt;I built this NFT end-to-end using nothing but &lt;code&gt;spl-token&lt;/code&gt; CLI commands.&lt;br&gt;
No framework. No JS. Just me, the terminal, and a lot of patience.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First Light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Symbol&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LIGHT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mint Address&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Program&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Token-2022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Supply&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 (locked forever)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decimals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Extensions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;metadataPointer&lt;/code&gt; + &lt;code&gt;tokenMetadata&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mint Authority&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Disabled 🔒&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;🔗 &lt;a href="https://explorer.solana.com/address/nftTnVuyNU1kwTgv7edG6BPmHCtp2NMrawbw94kwZTF?cluster=devnet" rel="noopener noreferrer"&gt;View "First Light" on Solana Explorer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The vanity keypair starting with &lt;code&gt;nft&lt;/code&gt; took about 20 minutes to generate&lt;br&gt;
locally using &lt;code&gt;solana-keygen grind&lt;/code&gt;. Every character you see in that address&lt;br&gt;
was intentional.&lt;/p&gt;




&lt;h3&gt;
  
  
  📋 Every Step — Verified On-Chain
&lt;/h3&gt;

&lt;p&gt;Here are all 5 transactions, in order. You can click any of them and&lt;br&gt;
see exactly what happened on the Solana blockchain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What I Did&lt;/th&gt;
&lt;th&gt;Verified Transaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Created mint account, initialized metadata pointer, initialized mint&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.solana.com/tx/3iXwaWfk8YfVMQRvPGHdzAMKEZ2xK45EXciYt8mDc47QGGptkqpBEca2LxAup4wUHKZ1NjhWTy6RJpU6rpbjzXtz?cluster=devnet" rel="noopener noreferrer"&gt;3iXwa…jzXtz&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Initialized token metadata — name, symbol, URI&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.solana.com/tx/45PYGZMbBXBP3S1Fuo6jqNi4Uv8gq2qbQ4GFnaPEVRoY5durKMK5SiY3UHW3rESjGptPPShGWfKA37YgBuXKv2D4?cluster=devnet" rel="noopener noreferrer"&gt;45PYG…Kv2D4&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Created the associated token account&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.solana.com/tx/gWLGogf4iCfWu62iw4mVLnaEQQ3zv7pebvP4tMKGmL1UA5xttqPQnwjWHxebPpyV6L3GBPboaLXo3EGuNoYmmgj?cluster=devnet" rel="noopener noreferrer"&gt;gWLGo…Ymmgj&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Minted exactly 1 token&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.solana.com/tx/3B8MS16hQ4itmB3pr76SfGZUkhzvMFtmtGggCGzpTbeUjLDqh7n37qMb8aWugnKAFFaNKhqsFJZAHtxZYfTwrQoP?cluster=devnet" rel="noopener noreferrer"&gt;3B8MS…wrQoP&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Revoked mint authority — forever&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.solana.com/tx/3YhopR7CCQwGkShGYcTx2Rnibg8oYgQXd1HiYi8DqcpQiZre4QUHk5pBrHSdzXwkk2TVRdXyEcfVpeMuwX3kckqo?cluster=devnet" rel="noopener noreferrer"&gt;3Yhop…kckqo&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No one can ever mint another LIGHT token. That is by design.&lt;/p&gt;




&lt;h3&gt;
  
  
  📰 9 Published DEV.to Articles (300+ total reactions)
&lt;/h3&gt;

&lt;p&gt;These are not just summaries of what I did. Each one is a full&lt;br&gt;
explanation written specifically for Web2 developers entering the&lt;br&gt;
Solana ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/gopichand_dev/your-public-key-is-your-identity-what-web2-devs-need-to-know-about-solana-4lpm"&gt;Your Public Key Is Your Identity — What Web2 Devs Need to Know About Solana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/gopichand_dev/solana-transactions-explained-for-backend-developers-with-real-failures-2ido"&gt;Solana Transactions Explained for Backend Developers (With Real Failures)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/gopichand_dev/i-built-5-token-extension-combinations-on-solana-this-week-heres-what-each-one-does-4ck3"&gt;I Built 5 Token Extension Combinations on Solana This Week — Here's What Each One Does&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📊 Where the Project Stands
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟣 Daily Build Progress&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44 / 100 Days Complete&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🖤 DEV.to Articles&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9 Published&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟢 On-Chain Transactions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Live on Solana Devnet&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;📄 License&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MIT&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔒 NFT Mint Authority&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Disabled Forever&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;Let me be honest about where this project started and where it almost ended.&lt;/p&gt;

&lt;h3&gt;
  
  
  Day 1 — What the repo actually looked like
&lt;/h3&gt;

&lt;p&gt;gopichandchalla16/100-days-of-solana&lt;br&gt;
└── README.md ← literally just a title&lt;br&gt;&lt;br&gt;
That was it. I had written "100 Days of Solana — learning in public"&lt;br&gt;
and committed it at midnight. No code. No plan. Just a title and the&lt;br&gt;
pressure of having put it on GitHub.&lt;/p&gt;

&lt;p&gt;The first week was rough. The Solana docs are not beginner-friendly&lt;br&gt;
if you're coming from Web2. The Token-2022 documentation is especially&lt;br&gt;
sparse. I spent 3 hours on Day 4 just trying to understand why my&lt;br&gt;
airdrop wasn't showing up (I was checking the wrong cluster).&lt;/p&gt;

&lt;p&gt;There were three moments where I almost stopped entirely:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 8&lt;/strong&gt; — I couldn't figure out why my token transfer kept failing&lt;br&gt;
with a cryptic &lt;code&gt;0x1&lt;/code&gt; error. I had been at it for two hours and it was&lt;br&gt;
past midnight. I nearly closed the laptop and told myself I'd "come back to it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 23&lt;/strong&gt; — I hit a wall with Token-2022 extension architecture.&lt;br&gt;
I understood how individual extensions worked but not how to compose&lt;br&gt;
them safely. Nothing I read explained it in plain terms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 39&lt;/strong&gt; — The NFT build broke on step 2 of 5. My metadata wasn't&lt;br&gt;
being initialized because I ran &lt;code&gt;initialize-mint&lt;/code&gt; before&lt;br&gt;
&lt;code&gt;initialize-metadata-pointer&lt;/code&gt;. The error wasn't obvious.&lt;br&gt;
I almost started over from scratch.&lt;/p&gt;

&lt;p&gt;I didn't quit any of those nights. GitHub Copilot helped me through&lt;br&gt;
each one — and I'll explain exactly how in the Copilot section.&lt;/p&gt;




&lt;h3&gt;
  
  
  Day 44 — What the repo became
&lt;/h3&gt;

&lt;p&gt;gopichandchalla16/100-days-of-solana&lt;br&gt;
├── day-01/ through day-44/ ← 44 documented daily builds&lt;br&gt;
├── 9 DEV.to articles published&lt;br&gt;
├── Every tx signature verified on Solana Explorer&lt;br&gt;
├── Token-2022 extensions built and tested:&lt;br&gt;
│ ├── Transfer fees (compliance use case)&lt;br&gt;
│ ├── Interest-bearing tokens (DeFi use case)&lt;br&gt;
│ ├── Default frozen + thaw (regulated assets)&lt;br&gt;
│ ├── Non-transferable / soulbound (credentials)&lt;br&gt;
│ └── Permanent delegate (revocable access)&lt;br&gt;
├── NFT "First Light" — vanity keypair, Token-2022,&lt;br&gt;
│ on-chain metadata, locked supply&lt;br&gt;
└── README with live progress bar, week logs,&lt;br&gt;
all explorer links&lt;br&gt;&lt;br&gt;
The difference between Day 1 and Day 44 is not just the code.&lt;br&gt;
It's the understanding behind it.&lt;/p&gt;




&lt;h3&gt;
  
  
  The 3 moments that defined this project
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Day 13 — The account model finally made sense.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I had been running &lt;code&gt;solana balance&lt;/code&gt; and &lt;code&gt;spl-token create-account&lt;/code&gt; for&lt;br&gt;
days without really understanding &lt;em&gt;why&lt;/em&gt; Solana accounts need rent.&lt;br&gt;
Then I sat down and wrote a DEV.to article explaining it with a&lt;br&gt;
Web2 analogy: &lt;em&gt;accounts are like database rows, rent is like a monthly&lt;br&gt;
hosting fee — stop paying and the row gets deleted.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Writing that article forced me to understand it deeply enough to&lt;br&gt;
explain it simply. After Day 13, I stopped copying commands and started&lt;br&gt;
understanding what each one actually does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Days 36–40 — Five Token-2022 extension combinations in one week.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the hardest week. I built:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A transfer fee token (simulating a transaction tax)&lt;/li&gt;
&lt;li&gt;An interest-bearing token (simulating a yield-bearing asset)&lt;/li&gt;
&lt;li&gt;A default-frozen token with thaw authority (compliance gating)&lt;/li&gt;
&lt;li&gt;A non-transferable soulbound token (on-chain credential)&lt;/li&gt;
&lt;li&gt;A permanent delegate token (revocable programmatic access)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each one is a real devnet transaction. Each one has a verifiable&lt;br&gt;
signature on Solana Explorer. Each one taught me something different&lt;br&gt;
about how Token-2022 is designed to handle real-world financial&lt;br&gt;
and compliance scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Days 43–44 — My first NFT. No Metaplex. Just the CLI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted to understand NFTs at the protocol level — not through&lt;br&gt;
a framework, not through a library, but through raw &lt;code&gt;spl-token&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;I generated a vanity keypair starting with &lt;code&gt;nft&lt;/code&gt; using &lt;code&gt;solana-keygen grind&lt;/code&gt;.&lt;br&gt;
I added two Token-2022 extensions: &lt;code&gt;metadataPointer&lt;/code&gt; and &lt;code&gt;tokenMetadata&lt;/code&gt;.&lt;br&gt;
I minted exactly 1 token. I disabled the mint authority forever.&lt;/p&gt;

&lt;p&gt;"First Light" now lives on-chain permanently with its name, symbol,&lt;br&gt;
and metadata URI intact. Nobody can create another one. That's what makes it an NFT.&lt;/p&gt;




&lt;h3&gt;
  
  
  The biggest technical lesson of 44 days
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Token-2022 extensions cannot be added after mint creation. Ever.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's no workaround. No patch. No update instruction.&lt;br&gt;
You must decide your full extension set before you run&lt;br&gt;
&lt;code&gt;initialize-mint&lt;/code&gt;. It's like designing a database schema —&lt;br&gt;
you can't add a non-nullable column without a migration.&lt;/p&gt;

&lt;p&gt;I learned this the hard way on Day 38 when I tried to add&lt;br&gt;
&lt;code&gt;interest-bearing&lt;/code&gt; to an existing mint. The transaction failed&lt;br&gt;
and I had to start the token from scratch. That 30-minute mistake&lt;br&gt;
became the most important architectural lesson I've had in 44 days.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;I want to be specific here, not just say "Copilot helped a lot."&lt;br&gt;
Here are the exact moments where it made the difference.&lt;/p&gt;




&lt;h3&gt;
  
  
  When I was stuck on &lt;code&gt;0x11&lt;/code&gt; at midnight (Day 37)
&lt;/h3&gt;

&lt;p&gt;My compliance-gated token transfer failed with error &lt;code&gt;0x11&lt;/code&gt; —&lt;br&gt;
&lt;code&gt;AccountFrozen&lt;/code&gt;. I knew the token was frozen by design but I thought&lt;br&gt;
I had thawed it. The transaction kept failing anyway.&lt;/p&gt;

&lt;p&gt;I was staring at the error in my terminal. Copilot's inline suggestion&lt;br&gt;
explained what I was missing: &lt;strong&gt;both the sender's ATA and the&lt;br&gt;
recipient's ATA need to be thawed&lt;/strong&gt; — not just the sender's.&lt;br&gt;
One suggestion. One minute. Problem solved.&lt;/p&gt;

&lt;p&gt;Without Copilot, I would have been digging through the SPL Token&lt;br&gt;
source code for the next hour — or worse, I would have given up&lt;br&gt;
and moved on without truly understanding the error.&lt;/p&gt;




&lt;h3&gt;
  
  
  When soulbound tokens confused me (Day 40)
&lt;/h3&gt;

&lt;p&gt;Non-transferable tokens are conceptually simple — once minted to a&lt;br&gt;
wallet, they can never move. But when I tried to demonstrate this&lt;br&gt;
by attempting a transfer, the transaction failed with &lt;code&gt;0x25&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I didn't expect the error. Copilot explained: non-transferable tokens&lt;br&gt;
can be burned but not transferred. It then suggested I write a burn&lt;br&gt;
script to demonstrate the constraint properly — which turned into&lt;br&gt;
the best hands-on example in my Week 6 article.&lt;/p&gt;

&lt;p&gt;The bug became the feature. That happens a lot when Copilot is involved.&lt;/p&gt;




&lt;h3&gt;
  
  
  Writing CLI commands faster and correctly
&lt;/h3&gt;

&lt;p&gt;The Token-2022 program ID is 44 characters long:&lt;br&gt;
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb&lt;br&gt;&lt;br&gt;
Before Copilot, I copied this from docs and sometimes mis-pasted it.&lt;br&gt;
With Copilot, it autocompleted the entire ID, the &lt;code&gt;--program-id&lt;/code&gt; flag,&lt;br&gt;
all the relevant options, and even the correct sequence of commands.&lt;/p&gt;

&lt;p&gt;The sequence matters enormously in Token-2022. For the NFT build,&lt;br&gt;
&lt;code&gt;initialize-metadata-pointer&lt;/code&gt; &lt;strong&gt;must&lt;/strong&gt; come before &lt;code&gt;initialize-mint&lt;/code&gt;.&lt;br&gt;
The Solana docs don't emphasize this clearly for beginners.&lt;br&gt;
Copilot's autocomplete surfaced the correct order naturally,&lt;br&gt;
in context, while I was typing. That saved me from the exact error&lt;br&gt;
that had broken my build on Day 39.&lt;/p&gt;




&lt;h3&gt;
  
  
  Turning raw terminal output into readable articles
&lt;/h3&gt;

&lt;p&gt;Every DEV.to article I wrote started the same way:&lt;br&gt;
a terminal window full of transaction signatures, error codes,&lt;br&gt;
and hex-encoded account data.&lt;/p&gt;

&lt;p&gt;Copilot helped me turn that raw output into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear Web2 analogies that explain Solana concepts without jargon&lt;/li&gt;
&lt;li&gt;A consistent structure: &lt;em&gt;what I planned → what broke → what I learned&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Opening paragraphs that hook readers who have never touched blockchain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nine articles. 300+ reactions across all of them.&lt;br&gt;
That audience engagement would not exist without Copilot helping me&lt;br&gt;
bridge the gap between "developer notes" and "readable article."&lt;/p&gt;




&lt;h3&gt;
  
  
  Explaining the "why" — not just the "how"
&lt;/h3&gt;

&lt;p&gt;This is the thing I appreciate most about Copilot, and it's hard to&lt;br&gt;
quantify. When a command worked, I often didn't fully understand &lt;em&gt;why&lt;/em&gt;&lt;br&gt;
it worked. Copilot's inline comments filled those gaps constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"This flag enables close authority so you can reclaim rent later."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"The metadata pointer must be initialized first because the mint
instruction reads the extension list."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Revoking mint authority is a one-way operation — there's no
re-enable instruction in Token-2022."&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These micro-explanations compounded over 44 days into real,&lt;br&gt;
deep understanding of the protocol. I'm not just writing Solana&lt;br&gt;
commands anymore. I understand what they do and why they exist.&lt;/p&gt;




&lt;h3&gt;
  
  
  The honest summary
&lt;/h3&gt;

&lt;p&gt;GitHub Copilot didn't write this project for me.&lt;br&gt;
Every transaction on Solana Explorer is a decision I made,&lt;br&gt;
a command I typed, a concept I understood.&lt;/p&gt;

&lt;p&gt;But Copilot removed the friction that would have made me quit.&lt;br&gt;
It turned 2-hour debugging sessions into 10-minute ones.&lt;br&gt;
It turned terminal output into articles people actually read.&lt;br&gt;
It turned "I don't understand this" into "oh, that's why."&lt;/p&gt;

&lt;p&gt;44 days in. 56 to go. I'm not stopping.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;🔗 GitHub Repo:&lt;/strong&gt; &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;https://github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📰 DEV Profile:&lt;/strong&gt; &lt;a href="https://dev.to/gopichand_dev"&gt;https://dev.to/gopichand_dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐦 X / Twitter:&lt;/strong&gt; &lt;a href="https://x.com/GopichandAI" rel="noopener noreferrer"&gt;https://x.com/GopichandAI&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're a Web2 developer curious about Solana — follow the repo.&lt;br&gt;
Every day folder has the exact commands I ran, the errors I hit,&lt;br&gt;
and what I learned. It's all there.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;#100DaysOfSolana #Solana #Web3 #BuildInPublic&lt;/em&gt;             &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>githubcopilot</category>
      <category>web3</category>
    </item>
    <item>
      <title>I Built 5 Token Extension Combinations on Solana This Week — Here's What Each One Does</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Tue, 02 Jun 2026 06:04:10 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/i-built-5-token-extension-combinations-on-solana-this-week-heres-what-each-one-does-4ck3</link>
      <guid>https://dev.to/gopichand_dev/i-built-5-token-extension-combinations-on-solana-this-week-heres-what-each-one-does-4ck3</guid>
      <description>&lt;p&gt;If you have ever used a brokerage account, you already understand token extensions.&lt;/p&gt;

&lt;p&gt;Your brokerage locks your account until you verify your identity. A stock you own cannot be&lt;br&gt;
transferred to someone else without compliance checks on both sides. A professional license&lt;br&gt;
is tied to you — the issuing body can revoke it, but you cannot sell it. An interest-bearing&lt;br&gt;
savings account accrues value over time without you doing anything.&lt;/p&gt;

&lt;p&gt;All of these are real-world rules applied to value. &lt;strong&gt;Token-2022 extensions let you encode&lt;br&gt;
those same rules directly into a Solana token mint&lt;/strong&gt; — at the protocol level, not the&lt;br&gt;
application layer. No backend. No database. No API that can go down.&lt;/p&gt;

&lt;p&gt;Over the past several days I built five different token configurations as part of my&lt;br&gt;
&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;100 Days of Solana&lt;/a&gt; challenge.&lt;br&gt;
Here is what I learned.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Are Token Extensions?
&lt;/h2&gt;

&lt;p&gt;Token-2022 is Solana's upgraded token program&lt;br&gt;
(&lt;code&gt;TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb&lt;/code&gt;).&lt;br&gt;
It is backwards-compatible with the original SPL Token program, but it lets you attach&lt;br&gt;
&lt;strong&gt;extensions&lt;/strong&gt; to a mint at creation time.&lt;/p&gt;

&lt;p&gt;Extensions are stored as TLV (type-length-value) blobs in the mint account's data.&lt;br&gt;
Each extension adds bytes, and those bytes cost rent. More extensions = bigger account =&lt;br&gt;
more SOL locked at creation. That is the core tradeoff to understand before you design&lt;br&gt;
a token.&lt;/p&gt;


&lt;h2&gt;
  
  
  Extension 1 — Interest-Bearing Tokens
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Web2 analogy:&lt;/strong&gt; A savings account that accrues interest over time.&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;--interest-rate&lt;/span&gt; 500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets an initial rate of &lt;strong&gt;500 basis points (5%)&lt;/strong&gt;. The rate can be updated later by&lt;br&gt;
the rate authority:&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 set-interest-rate &amp;lt;MINT_ADDRESS&amp;gt; 15000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What surprised me:&lt;/strong&gt; The extension does NOT mint new tokens. It is purely a&lt;br&gt;
&lt;strong&gt;calculation layer&lt;/strong&gt;. Wallets and apps read the rate and elapsed time to display an&lt;br&gt;
adjusted "UI balance." The raw on-chain balance stays the same. When you run&lt;br&gt;
&lt;code&gt;spl-token display&lt;/code&gt;, you see both:&lt;br&gt;
Current rate: 15000bps&lt;br&gt;
Average rate: 500bps ← historical average is preserved&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Use cases:&lt;/strong&gt; Yield-bearing stablecoins, loyalty points that grow over time,&lt;br&gt;
reward tokens for long-term holders.&lt;/p&gt;


&lt;h2&gt;
  
  
  Extension 2 — Transfer Fees
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Web2 analogy:&lt;/strong&gt; A payment processor that takes a cut of every transaction.&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&lt;/span&gt; 100 50000
&lt;span class="c"&gt;# 100 basis points (1%) fee, max 50000 tokens per transfer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fees are collected at the &lt;strong&gt;token account level&lt;/strong&gt; (withheld in the recipient's account),&lt;br&gt;
not sent automatically. The withdrawal authority must harvest them:&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;DESTINATION&amp;gt; &lt;span class="nt"&gt;--include-mints&lt;/span&gt; &amp;lt;MINT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What surprised me:&lt;/strong&gt; The fee is enforced by the Token-2022 program itself on every&lt;br&gt;
&lt;code&gt;TransferChecked&lt;/code&gt; instruction. No smart contract needed. No way to bypass it from&lt;br&gt;
the frontend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use cases:&lt;/strong&gt; Protocol revenue, DAO treasury funding, creator royalties on every transfer.&lt;/p&gt;


&lt;h2&gt;
  
  
  Extension 3 — Default Account State (Frozen)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Web2 analogy:&lt;/strong&gt; A brokerage that freezes every new account until KYC passes.&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-freeze&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--default-account-state&lt;/span&gt; frozen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every token account created for this mint starts in a &lt;strong&gt;frozen&lt;/strong&gt; state. Nobody can&lt;br&gt;
receive, send, or burn tokens until the freeze authority explicitly thaws them:&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 thaw &amp;lt;TOKEN_ACCOUNT_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The key insight I learned the hard way:&lt;/strong&gt; It is not enough for the &lt;em&gt;sender&lt;/em&gt; to be&lt;br&gt;
thawed. The &lt;em&gt;recipient&lt;/em&gt; must also be thawed. Both sides need to pass the compliance check.&lt;/p&gt;

&lt;p&gt;When I tried to transfer from a thawed account to a still-frozen one:    Program log: Error: Account is frozen (custom program error: 0x11)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Use cases:&lt;/strong&gt; Regulated security tokens, KYC-gated stablecoins, permissioned loyalty&lt;br&gt;
programs.&lt;/p&gt;


&lt;h2&gt;
  
  
  Extension 4 — Non-Transferable (Soulbound)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Web2 analogy:&lt;/strong&gt; A professional certification that belongs to you — you cannot sell it.&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;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--decimals&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--decimals 0&lt;/code&gt; because credentials are whole units. You either have the credential or&lt;br&gt;
you don't.&lt;/p&gt;

&lt;p&gt;Any transfer attempt fails immediately:                                  Program log: Transfer is disabled for this mint (custom program error: 0x25)&lt;br&gt;&lt;br&gt;
&lt;code&gt;0x25&lt;/code&gt; = decimal 37 = &lt;code&gt;NonTransferableError&lt;/code&gt; in the Token-2022 source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use cases:&lt;/strong&gt; DAO membership badges, hackathon completion certificates,&lt;br&gt;
verified contributor status, employee access tokens.&lt;/p&gt;


&lt;h2&gt;
  
  
  Extension 5 — Permanent Delegate (Revocable)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Web2 analogy:&lt;/strong&gt; Your employer can revoke your access badge at any time, without&lt;br&gt;
asking you.&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-permanent-delegate&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-non-transferable&lt;/span&gt; &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; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combining &lt;code&gt;--enable-non-transferable&lt;/code&gt; with &lt;code&gt;--enable-permanent-delegate&lt;/code&gt; creates a&lt;br&gt;
&lt;strong&gt;revocable soulbound token&lt;/strong&gt;: the holder cannot move it, but the issuer can burn it&lt;br&gt;
from any account without the holder's signature.&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;# Revoke the credential — no holder signature needed&lt;/span&gt;
spl-token burn &amp;lt;RECIPIENT_TOKEN_ACCOUNT&amp;gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--owner&lt;/span&gt; ~/.config/solana/id.json &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;&lt;strong&gt;What surprised me:&lt;/strong&gt; The Solana runtime logs a warning whenever a token account is&lt;br&gt;
created for a mint with a permanent delegate:&lt;br&gt;
Warning: Mint has a permanent delegate,&lt;br&gt;
so tokens in this account may be seized at any time&lt;br&gt;&lt;br&gt;
Full transparency baked in at the protocol level. The holder is informed at account&lt;br&gt;
creation — not buried in a ToS.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost of Extensions
&lt;/h2&gt;

&lt;p&gt;Each extension adds bytes to the mint account. More bytes = more rent-exempt SOL:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Data Size&lt;/th&gt;
&lt;th&gt;Rent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default frozen only&lt;/td&gt;
&lt;td&gt;171 bytes&lt;/td&gt;
&lt;td&gt;~0.0021 SOL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interest-bearing only&lt;/td&gt;
&lt;td&gt;222 bytes&lt;/td&gt;
&lt;td&gt;~0.0024 SOL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer fees + Interest + Metadata&lt;/td&gt;
&lt;td&gt;599 bytes&lt;/td&gt;
&lt;td&gt;~0.0051 SOL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Metadata is the biggest cost driver because it stores variable-length strings&lt;br&gt;
(name, symbol, URI) directly inside the mint account. Every character costs rent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Cannot Be Changed After Creation
&lt;/h2&gt;

&lt;p&gt;This is the part that matters most before mainnet deployment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extensions cannot be added after mint creation.&lt;/strong&gt; The account is allocated with
exactly the space for the declared extensions. You must plan upfront.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The permanent delegate cannot be removed&lt;/strong&gt; once set (only transferred to a different
address if the current delegate signs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-transferable is permanent.&lt;/strong&gt; Once set, no authority can enable transfers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like a database schema. Define it before you insert the first row.&lt;/p&gt;




&lt;h2&gt;
  
  
  Inspecting Any Token Extension Config
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token display &amp;lt;MINT_ADDRESS&amp;gt; &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;This single command decodes every extension in the mint account and prints it in&lt;br&gt;
human-readable form. I now run this before touching any Token-2022 mint I didn't&lt;br&gt;
create myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to Go Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://solana.com/docs/tokens/extensions" rel="noopener noreferrer"&gt;Token Extensions official docs&lt;/a&gt; — the
canonical reference, well-maintained&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.solana-program.com/docs/token-2022/extensions" rel="noopener noreferrer"&gt;Token-2022 Extensions Guide&lt;/a&gt; —
deeper per-extension implementation notes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;My full 100 Days of Solana repo&lt;/a&gt; —
every command I ran, every error I hit, documented day by day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best way to learn these is to run the CLI commands yourself on devnet. Each failure&lt;br&gt;
message tells you exactly what rule was violated and which error code fired. That feedback&lt;br&gt;
loop is faster than any tutorial.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is part of my #100DaysOfSolana challenge — building and documenting in public&lt;br&gt;
every day. Follow along on &lt;a href="https://twitter.com/GopichandAI" rel="noopener noreferrer"&gt;X @GopichandAI&lt;/a&gt; or&lt;br&gt;
&lt;a href="https://github.com/gopichandchalla16" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;                                                                     &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>learning</category>
    </item>
    <item>
      <title>Transfer Fees, Metadata, and Soulbound Tokens: A Tour of Solana Token Extensions</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Sun, 24 May 2026 17:05:43 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/transfer-fees-metadata-and-soulbound-tokens-a-tour-of-solana-token-extensions-42bj</link>
      <guid>https://dev.to/gopichand_dev/transfer-fees-metadata-and-soulbound-tokens-a-tour-of-solana-token-extensions-42bj</guid>
      <description>&lt;p&gt;I spent the last five days building tokens on Solana — and it completely&lt;br&gt;
changed how I think about what a "token" actually is.&lt;/p&gt;

&lt;p&gt;Coming from Web2 and EVM development, I assumed a token was a number in a&lt;br&gt;
database with some transfer logic wrapped around it. On Solana, it's much&lt;br&gt;
closer to a configurable protocol object. Here's everything I learned across&lt;br&gt;
Days 29–33 of my #100DaysOfSolana challenge.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Starting Point: Two Token Programs
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SPL Token (original)&lt;/strong&gt; — simple, battle-tested, does the basics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token Extensions Program (Token-2022)&lt;/strong&gt; — everything the original does,
plus built-in extensions for fees, metadata, soulbound tokens, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used Token-2022 for almost everything this week because it lets you attach&lt;br&gt;
behavior directly to the mint — no separate smart contract needed.&lt;/p&gt;


&lt;h2&gt;
  
  
  Day 29 — Creating My First SPL Token
&lt;/h2&gt;

&lt;p&gt;The first thing that surprised me: a token on Solana is two separate accounts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mint account&lt;/strong&gt; — holds the token's configuration (supply, decimals,
mint authority)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token account&lt;/strong&gt; — holds a specific wallet's balance of that token
&lt;/li&gt;
&lt;/ul&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;--decimals&lt;/span&gt; 9
spl-token create-account &amp;lt;MINT_ADDRESS&amp;gt;
spl-token mint &amp;lt;MINT_ADDRESS&amp;gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In Web2, this would be one database table with a balance column. On Solana,&lt;br&gt;
the separation means the mint config is completely independent of who holds&lt;br&gt;
what — any wallet can hold any token as long as they have a token account for it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Day 30 — Token-2022 with On-Chain Metadata
&lt;/h2&gt;

&lt;p&gt;The original SPL token has no name or symbol stored on-chain. You need an&lt;br&gt;
external metadata program (Metaplex) to give it an identity. Token-2022&lt;br&gt;
changes this with the &lt;strong&gt;metadata extension&lt;/strong&gt; — name, symbol, and URI live&lt;br&gt;
directly inside the mint 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 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_ADDRESS&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"GopichandToken"&lt;/span&gt; &lt;span class="s2"&gt;"GOPI"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://your-metadata-uri.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;spl-token display &amp;lt;MINT_ADDRESS&amp;gt;&lt;/code&gt; and you'll see the name and symbol&lt;br&gt;
sitting right there in the mint account output. No external call. No&lt;br&gt;
separate metadata program. The token &lt;em&gt;is&lt;/em&gt; the metadata.&lt;/p&gt;


&lt;h2&gt;
  
  
  Day 31 — Transfer Fees at the Protocol Level
&lt;/h2&gt;

&lt;p&gt;This is where it got interesting. I attached a &lt;strong&gt;1% transfer fee&lt;/strong&gt; to a&lt;br&gt;
token mint — meaning every transfer automatically withholds 1% in the&lt;br&gt;
recipient's token account, uncollectable by the recipient, waiting for the&lt;br&gt;
mint authority to withdraw.&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; 100 &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;--decimals&lt;/span&gt; 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After transferring 100 tokens, the recipient got 99. The 1 withheld token&lt;br&gt;
sat locked in their account until I ran:&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 &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;MY_TOKEN_ACCOUNT&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;RECIPIENT_TOKEN_ACCOUNT&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Web2, building a platform fee on transfers requires intercepting every&lt;br&gt;
transaction in your backend. On Solana, it's a flag on the mint. The&lt;br&gt;
program enforces it — not your server.&lt;/p&gt;


&lt;h2&gt;
  
  
  Day 32 — The Full Lifecycle in One Sitting
&lt;/h2&gt;

&lt;p&gt;Day 32 was a consolidation day. No new concepts — just proving I could&lt;br&gt;
build the complete token lifecycle from scratch without checking notes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create Token-2022 mint with metadata + 2% transfer fee&lt;/li&gt;
&lt;li&gt;Initialize metadata (name: ReinforceCoin, symbol: RFC)&lt;/li&gt;
&lt;li&gt;Create token account and mint 1000 RFC&lt;/li&gt;
&lt;li&gt;Transfer 100 to a second wallet → recipient gets 98, 2 withheld&lt;/li&gt;
&lt;li&gt;Withdraw withheld fees → balance becomes 902
Mint: 6YnDTE8cETtvKyesVM9frSeWCfpQUx757qcCQyHaBe9T
Final balance: 902 RFC ✅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The "aha" moment: I realized the pattern is always the same regardless of&lt;br&gt;
which extensions you use. &lt;strong&gt;Create → configure → mint → transfer →&lt;br&gt;
collect.&lt;/strong&gt; The specifics change. The lifecycle doesn't.&lt;/p&gt;


&lt;h2&gt;
  
  
  Day 33 — Soulbound Tokens
&lt;/h2&gt;

&lt;p&gt;The most conceptually interesting day. I created a &lt;strong&gt;non-transferable&lt;br&gt;
token&lt;/strong&gt; — a token that is permanently locked to the wallet it's minted into.&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;I minted 10 tokens, then tried to transfer 5 to a second wallet. The&lt;br&gt;
program rejected it instantly:&lt;br&gt;
Program log: Transfer is disabled for this mint&lt;br&gt;
custom program error: 0x25&lt;br&gt;&lt;br&gt;
That error code &lt;code&gt;0x25&lt;/code&gt; (decimal 37) is &lt;code&gt;NonTransferable&lt;/code&gt; — a first-class&lt;br&gt;
error in the Token Extensions Program. This isn't application logic that&lt;br&gt;
a clever script could bypass. The program itself rejects the instruction.&lt;/p&gt;

&lt;p&gt;What's interesting is that &lt;strong&gt;burning still works&lt;/strong&gt;. The owner can destroy&lt;br&gt;
tokens they hold. They just can't send them. After burning 3, my balance&lt;br&gt;
dropped from 10 to 7 — exactly as expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world uses for non-transferable tokens:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Course completion certificates&lt;/li&gt;
&lt;li&gt;KYC / identity verification&lt;/li&gt;
&lt;li&gt;Hackathon participation badges&lt;/li&gt;
&lt;li&gt;Employee credentials&lt;/li&gt;
&lt;li&gt;On-chain reputation scores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Web2, preventing credential trading means writing application rules that&lt;br&gt;
can be worked around. On Solana, the restriction is part of the asset itself.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;The separation of mint and token accounts took longer to internalize than&lt;br&gt;
I expected.&lt;/strong&gt; I kept confusing "the mint address" with "my token account."&lt;br&gt;
They're different accounts with different purposes. The mint is the factory.&lt;br&gt;
The token account is the warehouse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The error messages are actually readable.&lt;/strong&gt; &lt;code&gt;Transfer is disabled for&lt;br&gt;
this mint&lt;/code&gt; is exactly what it sounds like. Coming from debugging opaque&lt;br&gt;
EVM reverts, this felt refreshingly honest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extensions compose.&lt;/strong&gt; You can combine metadata + transfer fees in a&lt;br&gt;
single mint creation command. Day 32 showed me that Token-2022 isn't a&lt;br&gt;
collection of separate features — it's a composable system.&lt;/p&gt;




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

&lt;p&gt;I'm on Day 33 of 100. Next up: deeper into Token-2022 extensions&lt;br&gt;
(interest-bearing tokens, confidential transfers), then moving into&lt;br&gt;
Anchor and on-chain programs.&lt;/p&gt;

&lt;p&gt;I'm building toward AI agents that can own wallets and execute Solana&lt;br&gt;
transactions autonomously — and understanding the token layer deeply is&lt;br&gt;
a prerequisite for that.&lt;/p&gt;

&lt;p&gt;If you're following along, my full build log is at:&lt;br&gt;
👉 &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;github.com/gopichandchalla16/100-days-of-solana&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Day 34 of #100DaysOfSolana — writing in public, building in public.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Week 5 on Solana: Everything I Learned About Tokens (Days 29–31)</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Thu, 21 May 2026 05:07:38 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/week-5-on-solana-everything-i-learned-about-tokens-days-29-31-2bko</link>
      <guid>https://dev.to/gopichand_dev/week-5-on-solana-everything-i-learned-about-tokens-days-29-31-2bko</guid>
      <description>&lt;p&gt;Five weeks into my #100DaysOfSolana challenge and I just crossed into &lt;br&gt;
the most exciting territory yet — Solana's token layer. This week I went &lt;br&gt;
from zero token knowledge to creating, branding, minting, distributing, &lt;br&gt;
and even charging fees on tokens — all on-chain. Here's everything I learned.&lt;/p&gt;
&lt;h2&gt;
  
  
  What "Tokens" Actually Means on Solana
&lt;/h2&gt;

&lt;p&gt;Before this week, I thought tokens were complicated. Smart contracts, &lt;br&gt;
deployment scripts, audits — the whole Web3 horror story.&lt;/p&gt;

&lt;p&gt;Turns out, Solana has a completely different model. The &lt;strong&gt;SPL Token Program&lt;/strong&gt; &lt;br&gt;
is a single shared, audited program already deployed on-chain. Every token &lt;br&gt;
on Solana — from USDC to memecoins to your own project token — is created &lt;br&gt;
using the same program. You never write token logic. You just call it.&lt;/p&gt;

&lt;p&gt;This is the Web2 equivalent of using Stripe instead of building your own &lt;br&gt;
payment processor. The infrastructure is already there.&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 29: My First Token — Raw and Nameless
&lt;/h2&gt;

&lt;p&gt;On Day 29, I ran one 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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A token appeared on-chain. Supply: 0. Decimals: 9. Mint authority: me.&lt;/p&gt;

&lt;p&gt;Then I minted 100 tokens into a 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 create-account MINT_ADDRESS
spl-token mint MINT_ADDRESS 100
spl-token display MINT_ADDRESS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:                                                                   SPL Token Mint&lt;br&gt;
Address: 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq&lt;br&gt;
Supply: 100000000000&lt;br&gt;
Decimals: 9&lt;br&gt;
Mint authority: AWKYsCGBcfGLSz6QpmXzRn7EJ9fRhiJsjYSLDV3c9L9y&lt;br&gt;
Freeze authority: (not set)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Key insight:&lt;/strong&gt; Supply shows &lt;code&gt;100000000000&lt;/code&gt; for 100 tokens because &lt;br&gt;
decimals = 9, so &lt;code&gt;100 × 10^9&lt;/code&gt;. All on-chain math is integer arithmetic — &lt;br&gt;
no floating point, no rounding errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One problem:&lt;/strong&gt; Explorer showed it as "Unknown Token." No name, no symbol. &lt;br&gt;
That sent me to Day 30.&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 30: Giving My Token an Identity with Token-2022
&lt;/h2&gt;

&lt;p&gt;The fix: switch to &lt;strong&gt;Token Extensions Program (Token-2022)&lt;/strong&gt;. This newer &lt;br&gt;
standard embeds metadata — name, symbol, URI — directly inside the mint &lt;br&gt;
account. No separate Metaplex account needed.&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; 6

spl-token initialize-metadata MINT_ADDRESS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"100DaysCoin"&lt;/span&gt; &lt;span class="s2"&gt;"HUNDO"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My token now had an identity: &lt;strong&gt;100DaysCoin (HUNDO)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then I minted 1000 HUNDO and transferred 250 to a second wallet:&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 MINT_ADDRESS 250 SECOND_WALLET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--fund-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;Final: &lt;strong&gt;750 HUNDO (me) / 250 HUNDO (second wallet)&lt;/strong&gt;. The &lt;br&gt;
&lt;code&gt;--fund-recipient&lt;/code&gt; flag automatically created the recipient's Associated &lt;br&gt;
Token Account (ATA) and covered rent. One flag replaces what would be &lt;br&gt;
multiple API calls in Web2.&lt;/p&gt;
&lt;h2&gt;
  
  
  Day 31: Built-in Transfer Fees — No Middleware Required
&lt;/h2&gt;

&lt;p&gt;This was the most mind-bending day. In Web2, charging a per-transaction &lt;br&gt;
fee means building middleware, hooking into payment flows, writing fee &lt;br&gt;
collection jobs. On Solana, it's one flag 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;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; 100 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transfer-fee-maximum-fee&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;100 basis points = 1% fee. The program enforces it on every transfer — &lt;br&gt;
no middleware, no bypass possible.&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 MINT_ADDRESS 100 SECOND_WALLET &lt;span class="nt"&gt;--expected-fee&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--expected-fee&lt;/code&gt; flag is a safety check: the transfer fails if the &lt;br&gt;
calculated fee doesn't match what you specify. After the transfer:&lt;br&gt;
My wallet: 900 tokens&lt;br&gt;
Second wallet: 99 tokens ← received 100, 1 withheld as fee&lt;br&gt;&lt;br&gt;
The withheld fee sits locked in the recipient's token account. Only the &lt;br&gt;
&lt;strong&gt;withdraw withheld authority&lt;/strong&gt; (set to my wallet at creation) can collect it:&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 MY_TOKEN_ACCOUNT SECOND_WALLET_TOKEN_ACCOUNT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final result: &lt;strong&gt;901 tokens in my wallet&lt;/strong&gt; (900 + 1 fee swept back) ✅&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model That Changed Everything
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Web2 Concept&lt;/th&gt;
&lt;th&gt;Solana Equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rewards program definition&lt;/td&gt;
&lt;td&gt;Mint account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User balance row in DB&lt;/td&gt;
&lt;td&gt;Token Account (ATA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display name / branding&lt;/td&gt;
&lt;td&gt;On-chain metadata (Token-2022)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer API endpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spl-token transfer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment processor fee&lt;/td&gt;
&lt;td&gt;Transfer fee basis points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fee collection middleware&lt;/td&gt;
&lt;td&gt;&lt;code&gt;withdraw-withheld-tokens&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin-only mint permission&lt;/td&gt;
&lt;td&gt;Mint authority (cryptographic)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  SPL Token vs Token-2022: Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;SPL Token&lt;/th&gt;
&lt;th&gt;Token-2022&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic minting &amp;amp; transfers&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-chain metadata&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer fees&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interest-bearing tokens&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confidential transfers&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For any new token in 2026: &lt;strong&gt;Token-2022 is the default.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Week 6 goes deeper — more Token Extensions, and eventually building &lt;br&gt;
tokens with real utility. The foundation is solid.&lt;/p&gt;

&lt;p&gt;31 days in. 69 to go.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Articles in this series:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
→ &lt;a href="https://dev.to/gopichand_dev/how-i-created-my-first-solana-token-from-scratch-spl-token-basics-explained-2m39"&gt;Day 29: SPL Token Basics&lt;/a&gt;&lt;br&gt;&lt;br&gt;
→ &lt;a href="https://dev.to/gopichand_dev/i-built-a-branded-token-on-solana-in-5-minutes-no-smart-contract-needed-32n"&gt;Day 30: Token-2022 Branded Token&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;🔗 GitHub: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;100-days-of-solana&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Building daily → &lt;a href="https://twitter.com/GopichandAI" rel="noopener noreferrer"&gt;@GopichandAI&lt;/a&gt; | #100DaysOfSolana                                                  &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Created My First Solana Token from Scratch (SPL Token Basics Explained)</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Thu, 21 May 2026 04:48:07 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/how-i-created-my-first-solana-token-from-scratch-spl-token-basics-explained-2m39</link>
      <guid>https://dev.to/gopichand_dev/how-i-created-my-first-solana-token-from-scratch-spl-token-basics-explained-2m39</guid>
      <description>&lt;p&gt;On Day 29 of my #100DaysOfSolana challenge, I created my first-ever token &lt;br&gt;
on Solana's devnet. Not by deploying a smart contract. Not by writing a &lt;br&gt;
single line of code. Just a CLI and 5 commands.&lt;/p&gt;

&lt;p&gt;Here's what I learned — and why Solana's token model is fundamentally &lt;br&gt;
different from anything in Web2.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Web2 Mental Model (and Why It Breaks Here)
&lt;/h2&gt;

&lt;p&gt;In Web2, when you build a rewards or credits system, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;tokens&lt;/code&gt; table in your database&lt;/li&gt;
&lt;li&gt;Write API endpoints to create, read, update balances&lt;/li&gt;
&lt;li&gt;Handle edge cases like double-spending yourself&lt;/li&gt;
&lt;li&gt;Maintain a server to keep it all running&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On Solana, &lt;strong&gt;none of that exists&lt;/strong&gt;. The SPL Token Program is a shared, &lt;br&gt;
audited, on-chain program that any developer can use — no custom backend, &lt;br&gt;
no smart contract needed. You just call it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Key Accounts You Need to Understand
&lt;/h2&gt;

&lt;p&gt;Before touching the CLI, understand this mental model:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mint Account&lt;/strong&gt; = the global definition of your token  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores total supply
&lt;/li&gt;
&lt;li&gt;Stores decimal precision
&lt;/li&gt;
&lt;li&gt;Stores who has authority to mint more
&lt;/li&gt;
&lt;li&gt;One per token type
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Token Account&lt;/strong&gt; = a wallet's individual balance for ONE specific token  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Think of your wallet as a filing cabinet
&lt;/li&gt;
&lt;li&gt;Each token account is a separate folder inside it
&lt;/li&gt;
&lt;li&gt;One folder per token type you hold
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is unusual coming from Web2 — but it's how Solana keeps &lt;br&gt;
its data organized and its runtime blazing fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step: Creating My First SPL Token
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&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 address    &lt;span class="c"&gt;# confirm your wallet&lt;/span&gt;
solana balance    &lt;span class="c"&gt;# confirm you have SOL for fees&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I had &lt;strong&gt;6.13 SOL&lt;/strong&gt; on devnet — more than enough. No airdrop needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the token mint
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:                                                                  Creating token 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq&lt;br&gt;
under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA&lt;/p&gt;

&lt;p&gt;Address: 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq&lt;br&gt;
Decimals: 9&lt;br&gt;&lt;br&gt;
This created a &lt;strong&gt;Mint account&lt;/strong&gt; on-chain. Supply is zero. Decimals default &lt;br&gt;
to 9. My wallet is the mint authority — the only one who can create more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a token account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token create-account 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:                                                                                 Creating account B4SSsjUA1fcJmjMhmYis3EpLTSBD9GGo1DBEZAwjae7c&lt;br&gt;&lt;br&gt;
You &lt;strong&gt;cannot&lt;/strong&gt; receive tokens directly into your wallet. You need a &lt;br&gt;
dedicated token account for each token type. This is the "folder in the &lt;br&gt;
filing cabinet."&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Mint some supply
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token mint 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:                                                                               Minting 100 tokens&lt;br&gt;
Token: 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq&lt;br&gt;
Recipient: B4SSsjUA1fcJmjMhmYis3EpLTSBD9GGo1DBEZAwjae7c                  &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Inspect everything
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token supply 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq
&lt;span class="c"&gt;# → 100&lt;/span&gt;

spl-token accounts
&lt;span class="c"&gt;# Token                                         Balance&lt;/span&gt;
&lt;span class="c"&gt;# 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq  100&lt;/span&gt;

spl-token display 2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq
&lt;span class="c"&gt;# SPL Token Mint&lt;/span&gt;
&lt;span class="c"&gt;#   Address:          2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq&lt;/span&gt;
&lt;span class="c"&gt;#   Program:          TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA&lt;/span&gt;
&lt;span class="c"&gt;#   Supply:           100000000000&lt;/span&gt;
&lt;span class="c"&gt;#   Decimals:         9&lt;/span&gt;
&lt;span class="c"&gt;#   Mint authority:   AWKYsCGBcfGLSz6QpmXzRn7EJ9fRhiJsjYSLDV3c9L9y&lt;/span&gt;
&lt;span class="c"&gt;#   Freeze authority: (not set)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Decimals Trick
&lt;/h2&gt;

&lt;p&gt;Notice the supply shows &lt;code&gt;100000000000&lt;/code&gt; even though I minted &lt;code&gt;100&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's because decimals = 9, so:&lt;br&gt;
100 tokens × 10^9 = 100,000,000,000 raw units&lt;br&gt;&lt;br&gt;
This is identical to how SOL works with lamports:                                       1 SOL = 1,000,000,000 lamports&lt;br&gt;&lt;br&gt;
All on-chain arithmetic is integer math — no floating point, no rounding &lt;br&gt;
errors. The CLI handles the conversion for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Mint Authority" Actually Means
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;mint authority&lt;/code&gt; field is your wallet address — meaning &lt;strong&gt;only you&lt;/strong&gt; &lt;br&gt;
can call &lt;code&gt;spl-token mint&lt;/code&gt; to create more supply. &lt;/p&gt;

&lt;p&gt;This is the on-chain equivalent of "only the admin can issue new credits" &lt;br&gt;
in a Web2 system. Except here, it's enforced by cryptography, not by &lt;br&gt;
an &lt;code&gt;if (user.role === 'admin')&lt;/code&gt; check in your backend.&lt;/p&gt;

&lt;p&gt;You can also permanently disable minting (set supply to fixed) by &lt;br&gt;
running &lt;code&gt;spl-token authorize YOUR_MINT mint --disable&lt;/code&gt;. Once done, &lt;br&gt;
it's irreversible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze Authority: What It Is and Why I Left It Unset
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Freeze authority: (not set)&lt;/code&gt; means no one can freeze token accounts &lt;br&gt;
holding this token. If freeze authority were set, the authority could &lt;br&gt;
prevent specific wallets from sending or receiving the token — useful &lt;br&gt;
for compliance in real-world asset tokens, but not needed here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;You just created a digital asset. Not a database row, not an API response &lt;br&gt;
— a real token living on a public blockchain that anyone in the world &lt;br&gt;
can verify right now:&lt;/p&gt;

&lt;p&gt;🔗 &lt;a href="https://explorer.solana.com/address/2M6t3SbJMz95mZ8nzF8MLq364v2ZQQ235BkxhE93g7hq?cluster=devnet" rel="noopener noreferrer"&gt;View on Solana Explorer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tomorrow (Day 30), I upgraded this with &lt;strong&gt;Token-2022&lt;/strong&gt; — giving the token &lt;br&gt;
a real name, symbol, and on-chain metadata. The difference is dramatic.&lt;/p&gt;




&lt;p&gt;🔗 Full code: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-29-spl-token" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Building daily → &lt;a href="https://twitter.com/GopichandAI" rel="noopener noreferrer"&gt;@GopichandAI&lt;/a&gt; | #100DaysOfSolana Day 29/100                                                                                                             &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built a Branded Token on Solana in 5 Minutes (No Smart Contract Needed)</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Thu, 21 May 2026 04:40:48 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/i-built-a-branded-token-on-solana-in-5-minutes-no-smart-contract-needed-32n</link>
      <guid>https://dev.to/gopichand_dev/i-built-a-branded-token-on-solana-in-5-minutes-no-smart-contract-needed-32n</guid>
      <description>&lt;p&gt;I spent years thinking "creating a token" meant writing complex smart contract &lt;br&gt;
code, deploying it, hoping nothing breaks. Today on Day 30 of my &lt;/p&gt;

&lt;h1&gt;
  
  
  100DaysOfSolana challenge, I created a fully branded token — with a name,
&lt;/h1&gt;

&lt;p&gt;symbol, and on-chain metadata — in under 5 minutes. No Solidity. No Rust. &lt;br&gt;
Just a CLI.&lt;/p&gt;

&lt;p&gt;Here's exactly what I did and what it means.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Yesterday's Token
&lt;/h2&gt;

&lt;p&gt;On Day 29, I created my first SPL token on Solana devnet. It worked — but &lt;br&gt;
when I looked it up on Solana Explorer, it showed up as "Unknown Token." Just &lt;br&gt;
a random address with no name, no symbol, no identity.&lt;/p&gt;

&lt;p&gt;That's like launching a rewards program for your app but forgetting to give &lt;br&gt;
it a name. Users would see a random ID and have no idea what it represents.&lt;/p&gt;

&lt;p&gt;Today I fixed that using &lt;strong&gt;Token-2022&lt;/strong&gt; — Solana's next-generation token program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token-2022 vs the Original SPL Token Program
&lt;/h2&gt;

&lt;p&gt;The original SPL Token Program is solid but basic. To add metadata (name, &lt;br&gt;
symbol, image), you'd traditionally need a separate Metaplex account — an &lt;br&gt;
extra transaction, extra cost, extra complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token Extensions Program (Token-2022)&lt;/strong&gt; changes this. It lets you embed &lt;br&gt;
metadata &lt;em&gt;directly inside the mint account itself&lt;/em&gt;. One account. Everything &lt;br&gt;
in one place. Fewer transactions, lower cost.&lt;/p&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Web2 Concept&lt;/th&gt;
&lt;th&gt;Solana Equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reward program definition&lt;/td&gt;
&lt;td&gt;Mint account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Display name / branding&lt;/td&gt;
&lt;td&gt;On-chain metadata (name, symbol, URI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User balance record&lt;/td&gt;
&lt;td&gt;Associated Token Account (ATA)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer API&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;spl-token transfer&lt;/code&gt; instruction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What I Built: 100DaysCoin (HUNDO)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Token name:&lt;/strong&gt; 100DaysCoin
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbol:&lt;/strong&gt; HUNDO
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decimals:&lt;/strong&gt; 6
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Program:&lt;/strong&gt; Token Extensions (Token-2022)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mint:&lt;/strong&gt; &lt;code&gt;3zX5oyL9skYKWo2ZoUHgBwLpG9BzLe1ViuXLgH8joqFt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step: How I Did It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Create the mint with metadata enabled
&lt;/h3&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; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--program-id&lt;/code&gt; flag tells the CLI to use Token-2022 instead of the &lt;br&gt;
original SPL Token Program. The &lt;code&gt;--enable-metadata&lt;/code&gt; flag activates the &lt;br&gt;
metadata extension on the mint.&lt;/p&gt;

&lt;p&gt;Output:                                                                                  Creating token 3zX5oyL9skYKWo2ZoUHgBwLpG9BzLe1ViuXLgH8joqFt&lt;br&gt;
Address: 3zX5oyL9skYKWo2ZoUHgBwLpG9BzLe1ViuXLgH8joqFt&lt;br&gt;
Decimals: 6                                                              &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Initialize on-chain metadata
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token initialize-metadata &lt;span class="se"&gt;\&lt;/span&gt;
  3zX5oyL9skYKWo2ZoUHgBwLpG9BzLe1ViuXLgH8joqFt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"100DaysCoin"&lt;/span&gt; &lt;span class="s2"&gt;"HUNDO"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This writes the token's name, symbol, and a URI directly onto the mint &lt;br&gt;
account. The URI points to a JSON file with extended details — description, &lt;br&gt;
image, attributes. This is now verifiable by anyone on-chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create a token account and mint supply
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Balance check:                                                                       1000                                                                                 &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Transfer tokens to a second wallet
&lt;/h3&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; ~/second-wallet.json &lt;span class="nt"&gt;--no-bip39-passphrase&lt;/span&gt;

spl-token transfer 3zX5oyL9skYKWo2ZoUHgBwLpG9BzLe1ViuXLgH8joqFt 250 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="si"&gt;$(&lt;/span&gt;solana-keygen pubkey ~/second-wallet.json&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--fund-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 &lt;code&gt;--fund-recipient&lt;/code&gt; flag is key — it automatically creates the recipient's &lt;br&gt;
Associated Token Account (ATA) and covers the rent cost from my wallet. In &lt;br&gt;
Web2 terms, it's like your API automatically creating a user's balance row &lt;br&gt;
before their first transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final balances
&lt;/h3&gt;

&lt;p&gt;My wallet: 750 HUNDO ✅&lt;br&gt;
Second wallet: 250 HUNDO ✅                                                            &lt;/p&gt;

&lt;h2&gt;
  
  
  The "Aha" Moment
&lt;/h2&gt;

&lt;p&gt;In Web2, building a token/rewards system means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database schema for the currency definition&lt;/li&gt;
&lt;li&gt;CRUD API endpoints for balance management
&lt;/li&gt;
&lt;li&gt;Custom transfer logic with double-spend protection&lt;/li&gt;
&lt;li&gt;A server running 24/7 to keep it all alive&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Solana with Token-2022:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create-token&lt;/code&gt; = currency definition&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;create-account&lt;/code&gt; = user balance row&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;transfer&lt;/code&gt; = transfer API&lt;/li&gt;
&lt;li&gt;Zero server. Zero maintenance. Publicly verifiable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And critically — the metadata lives &lt;strong&gt;on-chain&lt;/strong&gt;. Not in your database. Not &lt;br&gt;
on your server. On a public ledger that anyone can read, forever.&lt;/p&gt;

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

&lt;p&gt;Day 31 onward: deeper into token extensions — transfer fees, &lt;br&gt;
interest-bearing tokens, and eventually building tokens with real utility. &lt;br&gt;
The foundation is set.&lt;/p&gt;




&lt;p&gt;🔗 Full code + notes: &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana/tree/main/day-30-token2022-metadata" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;br&gt;&lt;br&gt;
🔗 Mint on Explorer: &lt;a href="https://explorer.solana.com/address/3zX5oyL9skYKWo2ZoUHgBwLpG9BzLe1ViuXLgH8joqFt?cluster=devnet" rel="noopener noreferrer"&gt;Solana Explorer&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;Building daily → &lt;a href="https://twitter.com/GopichandAI" rel="noopener noreferrer"&gt;@GopichandAI&lt;/a&gt; | #100DaysOfSolana Day 30/100                &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Solana's Account Model Explained for Web2 Developers (No Blockchain Experience Needed)</title>
      <dc:creator>Gopichand</dc:creator>
      <pubDate>Mon, 18 May 2026 06:57:15 +0000</pubDate>
      <link>https://dev.to/gopichand_dev/solanas-account-model-explained-for-web2-developers-no-blockchain-experience-needed-4acg</link>
      <guid>https://dev.to/gopichand_dev/solanas-account-model-explained-for-web2-developers-no-blockchain-experience-needed-4acg</guid>
      <description>&lt;p&gt;If you're a Web2 developer looking at Solana for the first time, the account &lt;br&gt;
model will either click immediately or confuse you completely. I was in the &lt;br&gt;
second camp — until I spent 26 days building on Solana daily as part of the &lt;br&gt;
100 Days of Solana challenge.&lt;/p&gt;

&lt;p&gt;Here's the explanation I wish I'd had on Day 1.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Filesystem Analogy
&lt;/h2&gt;

&lt;p&gt;Think of Solana like a filesystem.&lt;/p&gt;

&lt;p&gt;Each account is a &lt;strong&gt;file&lt;/strong&gt;. Every file has metadata (owner, permissions, size) &lt;br&gt;
and contents (data). Programs are &lt;strong&gt;executable files&lt;/strong&gt;. Data accounts are the &lt;br&gt;
&lt;strong&gt;documents&lt;/strong&gt; those programs read and write. The System Program is the &lt;br&gt;
&lt;strong&gt;operating system kernel&lt;/strong&gt; that manages file creation and ownership transfers.&lt;/p&gt;

&lt;p&gt;That's the whole model. Let's go deeper.&lt;/p&gt;
&lt;h2&gt;
  
  
  Everything Is an Account
&lt;/h2&gt;

&lt;p&gt;In Ethereum, there are two types of things: externally owned accounts (wallets) &lt;br&gt;
and contract accounts (smart contracts). They behave differently.&lt;/p&gt;

&lt;p&gt;Solana has one type: &lt;strong&gt;accounts&lt;/strong&gt;. Everything — your wallet, a smart contract, &lt;br&gt;
a token mint, a clock showing the current time — is an account. They all live &lt;br&gt;
in one flat key-value store where the key is a 32-byte address and the value &lt;br&gt;
is the account itself.&lt;/p&gt;

&lt;p&gt;This uniformity is powerful once it clicks.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Five Fields Every Account Has
&lt;/h2&gt;

&lt;p&gt;Every single Solana account — whether it's your wallet or the System Program &lt;br&gt;
itself — has exactly five fields. Here's what they look like when you inspect &lt;br&gt;
your own wallet with the Solana CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana account &lt;span class="si"&gt;$(&lt;/span&gt;solana address&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Public Key: AWKYsCGBcfGLSz6QpmXzRn7EJ9fRhiJsjYSLDV3c9L9y&lt;br&gt;
Balance: 6.137925 SOL&lt;br&gt;
Owner: 11111111111111111111111111111111&lt;br&gt;
Executable: false&lt;br&gt;
Rent Epoch: 18446744073709551615&lt;br&gt;&lt;br&gt;
And in JSON:&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;6137925000&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="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"base64"&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;"space"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;Let's break each field down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. lamports&lt;/strong&gt; — your SOL balance in the smallest unit. 1 SOL = 1,000,000,000 &lt;br&gt;
lamports. My wallet has 6,137,925,000 lamports = 6.137925 SOL. Think of &lt;br&gt;
lamports like wei in Ethereum, or paise in INR — the atomic unit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. data&lt;/strong&gt; — a raw byte array. For a basic wallet, this is empty (0 bytes). &lt;br&gt;
For a token mint, it's 82 bytes of structured data. For a program, it's the &lt;br&gt;
compiled bytecode. This field holds the entire state of the account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. owner&lt;/strong&gt; — the program that controls this account. My wallet is owned by &lt;br&gt;
&lt;code&gt;11111111111111111111111111111111&lt;/code&gt; — the System Program. Only the owner program &lt;br&gt;
can modify the account's data or debit its lamports. Anyone can &lt;em&gt;add&lt;/em&gt; lamports, &lt;br&gt;
but only the owner can &lt;em&gt;remove&lt;/em&gt; them. This one rule is most of Solana's &lt;br&gt;
security model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. executable&lt;/strong&gt; — a boolean. &lt;code&gt;false&lt;/code&gt; for wallets and data accounts. &lt;code&gt;true&lt;/code&gt; &lt;br&gt;
for programs. When you call a program, Solana checks this flag first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. rent_epoch&lt;/strong&gt; — deprecated. Ignore it. It's always set to the maximum u64 &lt;br&gt;
value (18446744073709551615) for all rent-exempt accounts, which is everything &lt;br&gt;
on mainnet today.&lt;/p&gt;
&lt;h2&gt;
  
  
  Programs Don't Store Their Own State
&lt;/h2&gt;

&lt;p&gt;This is the part that surprises every Web2 developer.&lt;/p&gt;

&lt;p&gt;In Web2, a server typically owns its own database. In Solana, &lt;strong&gt;programs are &lt;br&gt;
stateless&lt;/strong&gt;. A program's executable code lives in one account. Any data it &lt;br&gt;
needs to read or write lives in &lt;em&gt;separate&lt;/em&gt; data accounts that are passed in &lt;br&gt;
with each transaction.&lt;/p&gt;

&lt;p&gt;Here's the direct comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Web2&lt;/th&gt;
&lt;th&gt;Solana&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web server&lt;/td&gt;
&lt;td&gt;Program account (executable=true)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;Data account (executable=false)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database row&lt;/td&gt;
&lt;td&gt;Individual account's &lt;code&gt;data&lt;/code&gt; field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP request&lt;/td&gt;
&lt;td&gt;Transaction instruction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request body&lt;/td&gt;
&lt;td&gt;Instruction data + account list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why does this matter? Because it means Solana can process transactions in &lt;br&gt;
parallel. If two transactions touch different accounts, they can run at the &lt;br&gt;
same time. That's a big reason Solana achieves 3,000–4,000 TPS on mainnet.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Ownership Rules Are Simple But Powerful
&lt;/h2&gt;

&lt;p&gt;Three rules govern everything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only the owner program can modify an account's data&lt;/li&gt;
&lt;li&gt;Only the owner program can debit lamports from an account
&lt;/li&gt;
&lt;li&gt;Anyone can credit (send) lamports to any account&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No complex permission system. No role-based access control. Just: &lt;br&gt;
you own it, you control it.&lt;/p&gt;

&lt;p&gt;When you send SOL to someone, the System Program (owner of both wallets) &lt;br&gt;
executes the transfer. When a token program moves a token, it's the owner of &lt;br&gt;
the token account doing the write. The ownership chain is always clear.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rent Exemption
&lt;/h2&gt;

&lt;p&gt;Every account must hold a minimum lamport balance proportional to its data &lt;br&gt;
size to stay on-chain permanently. This is called being &lt;strong&gt;rent-exempt&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a basic wallet (0 bytes of data):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana rent 0
&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;For a token mint account (82 bytes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana rent 82
&lt;span class="c"&gt;# Rent-exempt minimum: 0.0015 SOL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of it as a deposit you make to reserve space on the network. You get it &lt;br&gt;
back if you ever close the account. The network needs this mechanism to prevent &lt;br&gt;
people from spamming millions of empty accounts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Seeing It All in Practice
&lt;/h2&gt;

&lt;p&gt;After 26 days of building on Solana, the account model stopped being abstract &lt;br&gt;
and became &lt;em&gt;obvious&lt;/em&gt;. When I inspected the System Program itself:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Owner: NativeLoader1111111111111111111111111111111&lt;br&gt;
Executable: true&lt;br&gt;
Data: system_program ← literally the program name in bytes&lt;br&gt;&lt;br&gt;
And the Clock sysvar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;solana account SysvarC1ock11111111111111111111111111111111
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
                                                                                        Owner: Sysvar1111111111111111111111111111111111111&lt;br&gt;
Executable: false&lt;br&gt;
Length: 40 bytes ← slot, epoch, unix timestamp packed as binary&lt;br&gt;&lt;br&gt;
The pattern is always the same: &lt;code&gt;executable=true&lt;/code&gt; means it's code, &lt;br&gt;
&lt;code&gt;executable=false&lt;/code&gt; means it's data. The owner tells you who controls it. The &lt;br&gt;
data tells you what it stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model That Clicked For Me
&lt;/h2&gt;

&lt;p&gt;After weeks of building, here's the one-sentence summary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Solana is a global key-value store. Keys are 32-byte addresses. Values are &lt;br&gt;
accounts with 5 fields. Programs are accounts that transform other accounts &lt;br&gt;
when you call them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything else — tokens, NFTs, DeFi protocols, oracles — is built on top of &lt;br&gt;
this foundation. Once you see it, you can't unsee it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building in public with the &lt;a href="https://mlh.link/solana-100" rel="noopener noreferrer"&gt;100 Days of Solana&lt;/a&gt; &lt;br&gt;
challenge by MLH. Follow along on &lt;a href="https://github.com/gopichandchalla16/100-days-of-solana" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;br&gt;
and &lt;a href="https://twitter.com/GopichandAI" rel="noopener noreferrer"&gt;X/Twitter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Day 27 of 100. #100DaysOfSolana&lt;/em&gt;                                                                        &lt;/p&gt;

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