<?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: Tobi Ayinmiro</title>
    <description>The latest articles on DEV Community by Tobi Ayinmiro (@tobi_ayinmiro).</description>
    <link>https://dev.to/tobi_ayinmiro</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%2F3897797%2F00dfedfc-bc0b-430e-89dc-69768541f4bb.png</url>
      <title>DEV Community: Tobi Ayinmiro</title>
      <link>https://dev.to/tobi_ayinmiro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tobi_ayinmiro"/>
    <language>en</language>
    <item>
      <title>The CPI Mental Model I Wish I Had on Day 71</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Mon, 06 Jul 2026 10:02:07 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/the-cpi-mental-model-i-wish-i-had-on-day-71-3h5d</link>
      <guid>https://dev.to/tobi_ayinmiro/the-cpi-mental-model-i-wish-i-had-on-day-71-3h5d</guid>
      <description>&lt;p&gt;&lt;em&gt;Cross-Program Invocation isn't magic. It's just a function call with a guest list.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I first came across Cross-Program Invocations (CPIs) in Solana, I thought they were some special feature that required a completely different way of thinking. Every example introduced &lt;code&gt;CpiContext&lt;/code&gt;, account structs, and signer seeds all at once, and I couldn't tell which pieces were essential and which were just Anchor boilerplate.&lt;/p&gt;

&lt;p&gt;After spending the last few days building programs that call the System Program, sign for PDAs, and even call another Anchor program, I realized that the whole idea is much simpler than I originally thought.&lt;/p&gt;

&lt;p&gt;My mental model is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A CPI is just a function call with three things: the program you're calling, the accounts that program expects, and who is authorized to sign.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once that clicked, every CPI example started looking almost identical.&lt;/p&gt;

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

&lt;p&gt;Every Cross-Program Invocation has three moving parts.&lt;/p&gt;

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

&lt;p&gt;Every CPI targets another on-chain program. That might be the System Program, the Token-2022 Program, or another program you wrote yourself. Anchor identifies that program by its program ID.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The accounts that program needs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Programs cannot magically access accounts. Your program has to pass every required account to the program you're calling.&lt;/p&gt;

&lt;p&gt;If the called program expects a &lt;code&gt;from&lt;/code&gt; account, a &lt;code&gt;to&lt;/code&gt; account, and a system program account, you must provide all three.&lt;/p&gt;

&lt;p&gt;This is why CPIs often feel like filling out a checklist rather than writing complicated logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The signer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the piece that confused me the most.&lt;/p&gt;

&lt;p&gt;Sometimes the signer is simply the wallet that signed the transaction.&lt;/p&gt;

&lt;p&gt;Other times, the signer is a Program Derived Address (PDA). Since PDAs don't have private keys, the runtime lets the program "sign" for them—but only if the signer seeds exactly match the PDA's seeds.&lt;/p&gt;

&lt;p&gt;That means the seeds used in &lt;code&gt;with_signer()&lt;/code&gt; must match the seeds declared in your &lt;code&gt;#[account(seeds = ..., bump)]&lt;/code&gt; constraint. If they don't match, the runtime rejects the CPI.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Small Example
&lt;/h2&gt;

&lt;p&gt;One of the first examples that made this pattern click was calling another Anchor 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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though this looked unfamiliar at first, it's really just following the same three-part pattern.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The program being called is &lt;code&gt;counter_program&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The account it needs is &lt;code&gt;tally&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;No PDA is signing here, so a regular &lt;code&gt;CpiContext::new()&lt;/code&gt; is enough.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Earlier in the week I used the exact same pattern to call the System Program to transfer SOL. The only difference was the destination program and the accounts passed into the context.&lt;/p&gt;

&lt;p&gt;The structure stayed exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tripped Me Up
&lt;/h2&gt;

&lt;p&gt;The biggest lesson from building a PDA-backed vault wasn't actually writing the transfer it was understanding &lt;strong&gt;why&lt;/strong&gt; &lt;code&gt;with_signer()&lt;/code&gt; exists.&lt;/p&gt;

&lt;p&gt;When withdrawing from my vault, the vault account itself had to authorize the transfer. But the vault is a PDA, and PDAs don't have private keys.&lt;/p&gt;

&lt;p&gt;The solution was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;.with_signer&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;signer_seeds&lt;/code&gt; matched the PDA declaration:&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That finally made the idea click.&lt;/p&gt;

&lt;p&gt;The program isn't inventing a signature. It's proving to the runtime that it controls the PDA by supplying the exact seeds that generated it.&lt;/p&gt;

&lt;p&gt;Once I understood that, PDA signing stopped feeling mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If I could go back to Day 71, I wouldn't start by memorizing &lt;code&gt;CpiContext&lt;/code&gt; or &lt;code&gt;invoke_signed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'd start with the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who am I calling, what accounts does that program need, and who is allowed to sign?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything else is just implementation details.&lt;/p&gt;

&lt;p&gt;Whether you're calling the System Program, Token-2022, or another Anchor program, that same mental model keeps showing up.&lt;/p&gt;

&lt;p&gt;This post draws on my work from Days 73-74 of &lt;strong&gt;#100DaysOfSolana&lt;/strong&gt;, covering PDA-signed transfers and Cross-Program Invocations between Anchor programs.&lt;/p&gt;

</description>
      <category>solana</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>Tobi Ayinmiro</dc:creator>
      <pubDate>Mon, 29 Jun 2026 14:40:45 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/what-i-learned-about-pdas-in-a-week-of-building-on-solana-1pio</link>
      <guid>https://dev.to/tobi_ayinmiro/what-i-learned-about-pdas-in-a-week-of-building-on-solana-1pio</guid>
      <description>&lt;h2&gt;
  
  
  What problem do PDAs solve?
&lt;/h2&gt;

&lt;p&gt;One of the first things that confused me when learning Solana was where programs actually store data.&lt;/p&gt;

&lt;p&gt;Unlike a traditional backend, Solana programs are stateless. They don't keep variables in memory between requests. Every piece of persistent data has to live inside an account.&lt;/p&gt;

&lt;p&gt;That immediately creates a problem.&lt;/p&gt;

&lt;p&gt;If my counter program needs to remember a counter for every user, how does it know where to find that user's data tomorrow?&lt;/p&gt;

&lt;p&gt;Program Derived Addresses (PDAs) solve exactly that problem. They let a program deterministically derive the same account address every time from known inputs without storing the address anywhere beforehand.&lt;/p&gt;

&lt;p&gt;Over the past week (Days 64–68 of the &lt;strong&gt;100 Days of Solana&lt;/strong&gt; challenge), I built a counter program that evolved from a simple PDA into a complete application with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one counter PDA per wallet&lt;/li&gt;
&lt;li&gt;a global config PDA&lt;/li&gt;
&lt;li&gt;account constraints&lt;/li&gt;
&lt;li&gt;pause functionality&lt;/li&gt;
&lt;li&gt;rent recovery through account closing&lt;/li&gt;
&lt;li&gt;experiments showing how seed choices affect security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the mental model that finally made PDAs click for me.&lt;/p&gt;

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

&lt;p&gt;Coming from Web2, the closest analogy is a deterministic database key.&lt;/p&gt;

&lt;p&gt;Imagine a users table where every counter row is identified by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;("counter", user_id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of generating a random UUID, you always compute the primary key from known values.&lt;/p&gt;

&lt;p&gt;A PDA works similarly.&lt;/p&gt;

&lt;p&gt;Rather than storing an address somewhere, Solana computes it from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one or more seeds&lt;/li&gt;
&lt;li&gt;the program ID&lt;/li&gt;
&lt;li&gt;a bump value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the program can always derive the same address later.&lt;/p&gt;

&lt;p&gt;Unlike a database row, though, a PDA is &lt;strong&gt;not automatically created&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can derive a PDA today that has no account living at that address.&lt;/p&gt;

&lt;p&gt;The account only exists after your program initializes it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Anatomy of a derivation
&lt;/h1&gt;

&lt;p&gt;This is the exact account 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;p&gt;Every line matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;init&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Create the account if it doesn't already exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;payer = user&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The wallet pays the rent-exempt storage deposit.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;space&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Anchor reserves enough bytes for the account.&lt;/p&gt;

&lt;p&gt;The extra 8 bytes are Anchor's discriminator.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;seeds&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"counter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&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;.as_ref&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the logical identity of the account.&lt;/p&gt;

&lt;p&gt;The first seed is static.&lt;/p&gt;

&lt;p&gt;The second seed is dynamic.&lt;/p&gt;

&lt;p&gt;Together they uniquely identify one counter for one wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;program ID&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The program ID is automatically included in the derivation.&lt;/p&gt;

&lt;p&gt;That means another Solana program using identical seeds produces an entirely different PDA.&lt;/p&gt;

&lt;p&gt;This was one of the biggest "aha" moments for me.&lt;/p&gt;

&lt;p&gt;The program ID is part of the address.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the bump?
&lt;/h3&gt;

&lt;p&gt;When Solana derives a PDA, it repeatedly hashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;seeds
+
program ID
+
candidate bump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;starting from 255 downward.&lt;/p&gt;

&lt;p&gt;It keeps trying until the resulting public key lands &lt;strong&gt;off the Ed25519 curve&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That final successful byte is called the &lt;strong&gt;canonical bump&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;During Day 64 I used:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which returned both:&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pda&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anchor stores that bump for me automatically:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Later instructions reuse it:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Why the seeds matter
&lt;/h1&gt;

&lt;p&gt;The seed design determines your entire data model.&lt;/p&gt;

&lt;p&gt;During Day 68 I experimented with different derivations.&lt;/p&gt;

&lt;p&gt;This script:&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="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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produced a completely different PDA than:&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="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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every wallet got its own counter.&lt;/p&gt;

&lt;p&gt;Exactly what I wanted.&lt;/p&gt;

&lt;p&gt;Then I removed the wallet from the seeds.&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both wallets derived the exact same PDA.&lt;/p&gt;

&lt;p&gt;That would create a single global counter.&lt;/p&gt;

&lt;p&gt;Sometimes that's perfect.&lt;/p&gt;

&lt;p&gt;For example, my config account intentionally uses:&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="n"&gt;seeds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;b"config"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because the whole program only needs one configuration.&lt;/p&gt;

&lt;p&gt;But if my counter used the same seed scheme, every user would be sharing one counter account.&lt;/p&gt;

&lt;p&gt;The first initialization would succeed.&lt;/p&gt;

&lt;p&gt;Everyone else would receive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account already in use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That experiment made the importance of seed selection much clearer than reading documentation alone.&lt;/p&gt;

&lt;h1&gt;
  
  
  What the bump buys you
&lt;/h1&gt;

&lt;p&gt;The bump is not random.&lt;/p&gt;

&lt;p&gt;It is the canonical value that successfully generated an off-curve PDA.&lt;/p&gt;

&lt;p&gt;Anchor automatically computes it when you write:&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="n"&gt;bump&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later validates it with:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the bump never changes, storing it inside the account is effectively free.&lt;/p&gt;

&lt;p&gt;Reusing the stored bump also avoids recalculating it every instruction.&lt;/p&gt;

&lt;p&gt;My counter stores it during initialization:&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and every later instruction validates the same PDA using that stored value.&lt;/p&gt;

&lt;h1&gt;
  
  
  The full lifecycle of a PDA
&lt;/h1&gt;

&lt;p&gt;One thing I appreciated about this week is that I saw the complete lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 64
&lt;/h2&gt;

&lt;p&gt;Learn how to derive a PDA.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;findProgramAddressSync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Day 65
&lt;/h2&gt;

&lt;p&gt;Actually create the account.&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="n"&gt;init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One wallet.&lt;/p&gt;

&lt;p&gt;One counter PDA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 66
&lt;/h2&gt;

&lt;p&gt;Introduce a second PDA.&lt;/p&gt;

&lt;p&gt;My program now had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Config PDA&lt;/li&gt;
&lt;li&gt;Counter PDA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also added constraints like:&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="n"&gt;has_one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&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="n"&gt;constraint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="py"&gt;.paused&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so invalid transactions were rejected before my handler even ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 67
&lt;/h2&gt;

&lt;p&gt;Delete the account.&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="n"&gt;close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The close instruction looked almost empty:&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;close_counter&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;CloseCounter&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="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;Anchor handled everything else.&lt;/p&gt;

&lt;p&gt;When the instruction completed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account data was cleared&lt;/li&gt;
&lt;li&gt;lamports were refunded&lt;/li&gt;
&lt;li&gt;the runtime removed the account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't the same as deleting a database row.&lt;/p&gt;

&lt;p&gt;The runtime drains the lamports, zeroes the data, and garbage-collects the account afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 68
&lt;/h2&gt;

&lt;p&gt;Break things on purpose.&lt;/p&gt;

&lt;p&gt;I wrote a script that tried to spoof another user's PDA.&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="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="na"&gt;user&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="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;Anchor rejected it immediately.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because the account constraint re-derived the PDA from Wallet A's public key and discovered that I had passed Wallet B's PDA instead.&lt;/p&gt;

&lt;p&gt;No manual ownership check.&lt;/p&gt;

&lt;p&gt;No custom security code.&lt;/p&gt;

&lt;p&gt;The constraint itself enforced the rule.&lt;/p&gt;

&lt;p&gt;That was probably the biggest takeaway of the week.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I would tell past me
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The program ID is part of every PDA derivation. The same seeds in another program produce a different address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A PDA is only an address. It does not become an account until your program initializes it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Seed design is your data model. Choose them carefully.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store the bump once and reuse it instead of deriving it every instruction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;init_if_needed&lt;/code&gt; is convenient, but I now understand why people call it a footgun. Use it intentionally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Account constraints are much more than syntax—they are a major part of your program's security.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;This week completely changed how I think about state on Solana.&lt;/p&gt;

&lt;p&gt;At first, PDAs felt like magic.&lt;/p&gt;

&lt;p&gt;Looking back, the biggest lesson wasn't how to derive a PDA it was learning that your seed design is your data model.&lt;/p&gt;

&lt;p&gt;In Web2, you spend time designing database schemas, primary keys, and relationships because they determine how your application behaves.&lt;/p&gt;

&lt;p&gt;On Solana, you spend that same effort designing your PDA seeds.&lt;/p&gt;

&lt;p&gt;The difference between:&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="s2"&gt;"counter"&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;and&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="s2"&gt;"counter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&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;is the difference between one shared counter for the entire application and one counter for every user.&lt;/p&gt;

&lt;p&gt;That isn't just an implementation detail it defines the structure of your application.&lt;/p&gt;

&lt;p&gt;Once I started thinking of PDA seeds the same way I think about database schema design, the rest of Solana's account model became much easier to understand.&lt;/p&gt;

&lt;p&gt;For Web2 developers, that mental model is what finally made everything click for me.&lt;/p&gt;

&lt;p&gt;If you're learning Solana, I highly recommend reading the official Solana PDA documentation, the Anchor PDA guide, and the Anchor crate documentation alongside building your own small project. The combination of reading and experimenting is what made these concepts stick for me.&lt;/p&gt;

&lt;p&gt;You can also find the full source code for my counter program on my GitHub repository.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>anchor</category>
      <category>solana</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I Built My First Anchor Counter Program and Learned Why Tests Matter</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Mon, 22 Jun 2026 10:29:02 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/how-i-built-my-first-anchor-counter-program-and-learned-why-tests-matter-3hi7</link>
      <guid>https://dev.to/tobi_ayinmiro/how-i-built-my-first-anchor-counter-program-and-learned-why-tests-matter-3hi7</guid>
      <description>&lt;p&gt;This week I built my first Solana program with Anchor: a simple counter that starts at 0 and can only be incremented by its owner.&lt;/p&gt;

&lt;p&gt;The first thing that helped me understand Anchor was the &lt;code&gt;Initialize&lt;/code&gt; 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;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;authority,&lt;/span&gt;
        &lt;span class="nd"&gt;space&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="nd"&gt;Counter::INIT_SPACE,&lt;/span&gt;
    &lt;span class="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;The &lt;code&gt;counter&lt;/code&gt; account is the on-chain account that stores the data. The &lt;code&gt;authority&lt;/code&gt; account is the wallet creating the counter and paying for the account creation. The &lt;code&gt;system_program&lt;/code&gt; is needed because Solana uses it to create new accounts.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;initialize&lt;/code&gt; handler is very short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Initialize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.authority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="py"&gt;.accounts.authority&lt;/span&gt;&lt;span class="nf"&gt;.key&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="py"&gt;.count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ctx.accounts&lt;/code&gt; gives me access to all the validated accounts from the &lt;code&gt;Initialize&lt;/code&gt; struct. Here I simply save the owner's wallet address and set the counter value to zero.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;increment&lt;/code&gt; instruction is also small:&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;ProgramError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArithmeticOverflow&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;The important security check is actually here:&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;The &lt;code&gt;has_one = authority&lt;/code&gt; constraint makes sure only the wallet that owns the counter can increment it. Anchor checks this before the instruction logic runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Happy Path
&lt;/h2&gt;

&lt;p&gt;My main test initializes a counter, increments it, and checks that everything was stored correctly.&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;// initialize counter&lt;/span&gt;
    &lt;span class="c1"&gt;// increment counter&lt;/span&gt;
    &lt;span class="c1"&gt;// verify count == 1&lt;/span&gt;
    &lt;span class="c1"&gt;// verify authority is correct&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test would fail if the counter was not initialized correctly or if incrementing did not update the value as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Failure Path
&lt;/h2&gt;

&lt;p&gt;I also wrote a test to make sure a different wallet cannot increment someone else's counter.&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;// create counter with authority_a&lt;/span&gt;
    &lt;span class="c1"&gt;// attempt increment with authority_b&lt;/span&gt;
    &lt;span class="c1"&gt;// expect transaction to fail&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test would fail if my ownership check stopped working and unauthorized users were allowed to modify the counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking the Program on Purpose
&lt;/h2&gt;

&lt;p&gt;For Day 61, I intentionally introduced bugs to make sure my tests were actually protecting me.&lt;/p&gt;

&lt;p&gt;One experiment changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;.checked_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;.checked_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction still succeeded, but the test failed because the counter became &lt;code&gt;2&lt;/code&gt; instead of &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The failing assertion looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assertion `left == right` failed
left: 2
right: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was a great reminder that a transaction can succeed while still producing the wrong result. Without the test, I might not have noticed the bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Build Next
&lt;/h2&gt;

&lt;p&gt;If I had another week, I'd expand this project by supporting multiple counters per user, and adding more edge-case tests.&lt;/p&gt;

&lt;p&gt;The biggest lesson from this project was that writing tests is useful, but intentionally breaking the program and watching the tests catch the bug is what really builds confidence in them.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>rust</category>
      <category>anchor</category>
    </item>
    <item>
      <title>Token-2022 Feels Like Middleware for Money</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:50:42 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/token-2022-feels-like-middleware-for-money-5cml</link>
      <guid>https://dev.to/tobi_ayinmiro/token-2022-feels-like-middleware-for-money-5cml</guid>
      <description>&lt;p&gt;As a Web2 developer, I naturally compare new technologies to tools I've already used.&lt;/p&gt;

&lt;p&gt;After spending a week exploring Solana's Token-2022 program, I realized the best analogy is middleware.&lt;/p&gt;

&lt;p&gt;Instead of wrapping requests, you're wrapping assets.&lt;/p&gt;

&lt;p&gt;With Token-2022 extensions, behaviors that would normally require custom smart contracts can be attached directly to a token mint. Over the past few days, I created three different Token-2022 mints on devnet and experimented with transfer fees, interest accrual, and non-transferability.&lt;/p&gt;




&lt;h1&gt;
  
  
  Day 50–51: Transfer Fee Token
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Mint Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2Ak24WKZZrvjE4KGWr8YMovGBsWB5bptcQekFycn5jpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Token
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Transfers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2Ak24WKZZrvjE4KGWr8YMovGBsWB5bptcQekFycn5jpd

spl-token mint &lt;span class="nv"&gt;$MINT&lt;/span&gt; 1000000

spl-token transfer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expected-fee&lt;/span&gt; 10 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;$MINT&lt;/span&gt; 1000 &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allow-unfunded-recipient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Harvest Collected Fees
&lt;/h2&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;
  &lt;span class="nv"&gt;$MY_TA&lt;/span&gt; &lt;span class="se"&gt;\&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 Transfer Fee extension automatically withholds a percentage of every transfer. In a traditional Web2 application, implementing this would typically require backend logic, payment processing, and accounting systems. With Token-2022, the rule is enforced directly by the token itself.&lt;/p&gt;

&lt;p&gt;Potential use cases include protocol fees, creator royalties, treasury funding, and community currencies.&lt;/p&gt;




&lt;h1&gt;
  
  
  Day 52: Interest-Bearing Token
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Mint Address
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;86D7eecNMkJMofFabFq9GMFPPUVYrwLbLx3KA6vnAyee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Token
&lt;/h2&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;h2&gt;
  
  
  Observe Interest Accrual
&lt;/h2&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This extension surprised me the most.&lt;/p&gt;

&lt;p&gt;Unlike traditional interest-bearing assets, the underlying token balance does not increase over time. Instead, Token-2022 adjusts the displayed UI amount to reflect accrued interest.&lt;/p&gt;

&lt;p&gt;The result is a token that appears to grow while maintaining the same underlying balance. This allows applications to represent yield without continuously minting new tokens.&lt;/p&gt;




&lt;h1&gt;
  
  
  Day 54: Non-Transferable Token
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Mint Address
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create Token
&lt;/h2&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-2022&lt;/span&gt; &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;h2&gt;
  
  
  Attempt a Transfer
&lt;/h2&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; 1 &lt;span class="nv"&gt;$RECIPIENT&lt;/span&gt; &lt;span class="nt"&gt;--allow-unfunded-recipient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Actual Result
&lt;/h2&gt;

&lt;p&gt;The transfer failed exactly as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transfer 1 tokens
  Sender: AUVDPscnF5YwhSd5oKKVFiASVLJvzix9v9rT6SFiUaTk
  Recipient: A8MAafu4XrCxetfZhMWtSoSMXarzUBeXX5yUrcHqdCxq
  Recipient associated token account: 4BkZZy8r1uaraL4PRu6CgNAvA8HhBEhcBMcmPgexB46C

Error: Client(Error {
  request: Some(SendTransaction),
  kind: RpcError(RpcResponseError {
    code: -32002,
    message: "Transaction simulation failed:
    Error processing Instruction 0:
    custom program error: 0x25",
    data: SendTransactionPreflightFailure(
      RpcSimulateTransactionResult {
        err: Some(
          UiTransactionError(
            InstructionError(0, Custom(37))
          )
        ),
        logs: [
          "Program log: Instruction: TransferChecked",
          "Program log: Transfer is disabled for this mint"
        ]
      }
    )
  })
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part of the error is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Program log: Transfer is disabled for this mint&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This confirms that the Non-Transferable extension was functioning exactly as designed. The Token-2022 program prevented the asset from being transferred between wallets, enforcing the restriction at the protocol level rather than relying on application logic.&lt;/p&gt;

&lt;p&gt;Unlike standard SPL tokens, these assets are intended to remain attached to the wallet that receives them.&lt;/p&gt;

&lt;p&gt;Potential use cases include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Educational certificates&lt;/li&gt;
&lt;li&gt;Membership badges&lt;/li&gt;
&lt;li&gt;Event attendance proofs&lt;/li&gt;
&lt;li&gt;Reputation systems&lt;/li&gt;
&lt;li&gt;Identity credentials&lt;/li&gt;
&lt;li&gt;Soul-bound tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than functioning as currency, these tokens act as verifiable digital records tied to a specific wallet.&lt;/p&gt;




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

&lt;p&gt;What stood out to me throughout this exercise was how much functionality can be configured without writing a custom Solana program.&lt;/p&gt;

&lt;p&gt;In a typical Web2 architecture, implementing these features would likely require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;Background jobs&lt;/li&gt;
&lt;li&gt;Access-control logic&lt;/li&gt;
&lt;li&gt;Payment processing systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Token-2022, many of these behaviors can be embedded directly into the asset itself.&lt;/p&gt;

&lt;p&gt;A transfer fee becomes part of the token.&lt;/p&gt;

&lt;p&gt;Interest accrual becomes part of the token.&lt;/p&gt;

&lt;p&gt;Transfer restrictions become part of the token.&lt;/p&gt;

&lt;p&gt;The asset carries the logic.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Token-2022 has changed how I think about digital assets on Solana.&lt;/p&gt;

&lt;p&gt;Instead of building applications around tokens, developers can increasingly build behavior into the tokens themselves.&lt;/p&gt;

&lt;p&gt;Over the course of these experiments, I created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A token that collects fees&lt;/li&gt;
&lt;li&gt;A token that accrues interest&lt;/li&gt;
&lt;li&gt;A token that cannot be transferred&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All without deploying a custom smart contract.&lt;/p&gt;

&lt;p&gt;For a Web2 developer, the closest analogy remains middleware—but middleware for money.&lt;/p&gt;

&lt;p&gt;The most interesting lesson from this week was seeing how different token behaviors can be achieved through configuration alone. Features that would traditionally require custom contracts, backend services, or application-level enforcement can be attached directly to the asset at mint creation.&lt;/p&gt;

&lt;p&gt;That's a powerful shift in how we think about designing digital assets.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>web3</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Creating My First Token on Solana Devnet as a Web2 Developer</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Mon, 25 May 2026 10:45:56 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/creating-my-first-token-on-solana-devnet-as-a-web2-developer-2bln</link>
      <guid>https://dev.to/tobi_ayinmiro/creating-my-first-token-on-solana-devnet-as-a-web2-developer-2bln</guid>
      <description>&lt;p&gt;As a web developer, I’m used to building systems where currencies, rewards, and balances live inside databases controlled by the backend.&lt;/p&gt;

&lt;p&gt;This week was my first time creating an actual on-chain token on Solana, and honestly, it changed how I think about digital assets.&lt;/p&gt;

&lt;p&gt;Instead of storing balances in a database table, the blockchain itself handled everything — minting, transfers, metadata, and verification.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk through how I created my first token on Solana devnet using Token-2022, added metadata to it, minted supply, and transferred tokens between wallets.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Needed Before Starting
&lt;/h1&gt;

&lt;p&gt;Before creating the token, I needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solana CLI installed&lt;/li&gt;
&lt;li&gt;SPL Token CLI installed&lt;/li&gt;
&lt;li&gt;A funded devnet wallet&lt;/li&gt;
&lt;li&gt;Solana configured to devnet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, I configured Solana to use devnet:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Then I requested free devnet SOL:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  Creating My First Token Mint
&lt;/h1&gt;

&lt;p&gt;Unlike the older SPL Token Program, I used the newer Token-2022 Program because it supports extensions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metadata&lt;/li&gt;
&lt;li&gt;Transfer fees&lt;/li&gt;
&lt;li&gt;Interest-bearing tokens&lt;/li&gt;
&lt;li&gt;Non-transferable (soulbound) tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the command I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token 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;After running it, Solana generated a token mint address for me.&lt;/p&gt;

&lt;p&gt;At this point, the token technically existed on-chain, but it still had no identity.&lt;/p&gt;

&lt;p&gt;It was basically just a random string of characters.&lt;/p&gt;




&lt;h1&gt;
  
  
  Adding Metadata to the Token
&lt;/h1&gt;

&lt;p&gt;This was one of the coolest parts for me.&lt;/p&gt;

&lt;p&gt;I learned that Solana’s Token-2022 Program allows metadata to live directly on-chain instead of depending entirely on separate programs.&lt;/p&gt;

&lt;p&gt;I initialized the token metadata like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spl-token initialize-metadata &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;TOKEN_MINT_ADDRESS&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&amp;lt;TOKEN_NAME&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&amp;lt;TOKEN_SYMBOL&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&amp;lt;TOKEN_METADATA_URI&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example placeholders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;TOKEN_MINT_ADDRESS&amp;gt;&lt;/code&gt; → your token mint address&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;TOKEN_NAME&amp;gt;&lt;/code&gt; → e.g. MyCoin&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;TOKEN_SYMBOL&amp;gt;&lt;/code&gt; → e.g. MYC&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;TOKEN_METADATA_URI&amp;gt;&lt;/code&gt; → public JSON metadata link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, my token finally had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a name&lt;/li&gt;
&lt;li&gt;a symbol&lt;/li&gt;
&lt;li&gt;metadata attached to it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made it feel like a real digital asset instead of just raw blockchain data.&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating a Token Account
&lt;/h1&gt;

&lt;p&gt;One concept that confused me initially was token accounts.&lt;/p&gt;

&lt;p&gt;I assumed my wallet could directly hold tokens.&lt;/p&gt;

&lt;p&gt;That’s not exactly how Solana works.&lt;/p&gt;

&lt;p&gt;On Solana:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the mint defines the token&lt;/li&gt;
&lt;li&gt;token accounts hold balances for that token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So before I could receive my token, I had to create 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 &amp;lt;TOKEN_MINT_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generated an associated token account tied to my wallet and token mint.&lt;/p&gt;




&lt;h1&gt;
  
  
  Minting My First Supply
&lt;/h1&gt;

&lt;p&gt;Now came the exciting part.&lt;/p&gt;

&lt;p&gt;I minted 1000 tokens into my 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 mint &amp;lt;TOKEN_MINT_ADDRESS&amp;gt; 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I checked the balance:&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 balance &amp;lt;TOKEN_MINT_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And seeing this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it felt surprisingly rewarding to be honest&lt;/p&gt;




&lt;h1&gt;
  
  
  Creating a Second Wallet
&lt;/h1&gt;

&lt;p&gt;To test transfers properly, I created 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;solana-keygen new &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--outfile&lt;/span&gt; ~/second-wallet.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-bip39-passphrase&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simulated sending tokens to another user.&lt;/p&gt;




&lt;h1&gt;
  
  
  Transferring Tokens
&lt;/h1&gt;

&lt;p&gt;I transferred 250 tokens to the 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 &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt;TOKEN_MINT_ADDRESS&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  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="se"&gt;\&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 automatically created the recipient’s token account if it didn’t already exist.&lt;/p&gt;

&lt;p&gt;That was another thing that clicked for me:&lt;br&gt;
even receiving tokens on Solana involves token accounts behind the scenes.&lt;/p&gt;


&lt;h1&gt;
  
  
  Verifying the Transfer
&lt;/h1&gt;

&lt;p&gt;I checked both balances.&lt;/p&gt;

&lt;p&gt;My wallet balance:&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 balance &amp;lt;TOKEN_MINT_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recipient wallet balance:&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 balance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--owner&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;
  &amp;lt;TOKEN_MINT_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The balances showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;750&lt;/code&gt; in my wallet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;250&lt;/code&gt; in the second wallet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The transfer worked successfully.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Confused Me Initially
&lt;/h1&gt;

&lt;p&gt;One of the biggest things that confused me was the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wallet addresses&lt;/li&gt;
&lt;li&gt;token accounts&lt;/li&gt;
&lt;li&gt;token mints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, I thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Why can’t I just send directly to the wallet?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But Solana separates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the wallet owner&lt;/li&gt;
&lt;li&gt;the token account holding balances&lt;/li&gt;
&lt;li&gt;the token definition itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that clicked, the entire token system started making much more sense.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Surprised Me Most
&lt;/h1&gt;

&lt;p&gt;The biggest surprise was realizing how much logic the blockchain itself handles.&lt;/p&gt;

&lt;p&gt;In Web2, building something similar would require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;database tables&lt;/li&gt;
&lt;li&gt;backend APIs&lt;/li&gt;
&lt;li&gt;transfer validation&lt;/li&gt;
&lt;li&gt;balance management&lt;/li&gt;
&lt;li&gt;permission handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Solana:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the protocol handles balances&lt;/li&gt;
&lt;li&gt;token programs enforce rules&lt;/li&gt;
&lt;li&gt;transactions are verifiable on-chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It felt like moving backend business logic directly into infrastructure.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Creating my first token on Solana made Web3 feel much less abstract.&lt;/p&gt;

&lt;p&gt;Before this, tokens felt like mysterious blockchain concepts.&lt;/p&gt;

&lt;p&gt;Now I understand that they’re actually programmable digital assets with rules enforced directly by the protocol.&lt;/p&gt;

&lt;p&gt;This was my first step into Solana development, and it definitely won’t be the last.&lt;/p&gt;

&lt;p&gt;Next, I want to explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transfer fee extensions&lt;/li&gt;
&lt;li&gt;soulbound tokens&lt;/li&gt;
&lt;li&gt;Solana programs with Rust&lt;/li&gt;
&lt;li&gt;building full-stack dApps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re coming from Web2 development, my advice is simple:&lt;/p&gt;

&lt;h1&gt;
  
  
  Start building.
&lt;/h1&gt;

&lt;p&gt;Things make much more sense once you actually create something yourself.&lt;/p&gt;

&lt;p&gt;link to the token: &lt;a href="https://explorer.solana.com/address/vXRh8HHmjeFvpQgt19EEqf6iaAQxqM8NtCdTwCAqozu?cluster=devnet" rel="noopener noreferrer"&gt;https://explorer.solana.com/address/vXRh8HHmjeFvpQgt19EEqf6iaAQxqM8NtCdTwCAqozu?cluster=devnet&lt;/a&gt;&lt;/p&gt;

</description>
      <category>solana</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Understanding Solana’s Account Model as a Web2 Developer</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Mon, 18 May 2026 08:56:42 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/understanding-solanas-account-model-as-a-web2-developer-4k0p</link>
      <guid>https://dev.to/tobi_ayinmiro/understanding-solanas-account-model-as-a-web2-developer-4k0p</guid>
      <description>&lt;h1&gt;
  
  
  Understanding Solana’s Account Model as a Web2 Developer
&lt;/h1&gt;

&lt;p&gt;When I started learning Solana, I expected wallets and smart contracts to work like Ethereum. Instead, I discovered something very different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Solana, everything is an account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first, that sounded simple. But it completely changed how I thought about blockchain architecture.&lt;/p&gt;

&lt;p&gt;As a Web2 developer, I’m used to systems that look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend → Backend API → Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The backend stores logic&lt;/li&gt;
&lt;li&gt;The database stores state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solana works differently — but underneath it all is a surprisingly familiar mental model.&lt;/p&gt;

&lt;p&gt;The easiest way to understand Solana’s account model is to think of it like a filesystem or operating system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accounts = files&lt;/li&gt;
&lt;li&gt;Programs = executable applications&lt;/li&gt;
&lt;li&gt;Data accounts = databases/documents&lt;/li&gt;
&lt;li&gt;The System Program = operating system kernel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this clicks, the rest of Solana becomes much easier to understand.&lt;/p&gt;




&lt;h1&gt;
  
  
  Everything on Solana Is an Account
&lt;/h1&gt;

&lt;p&gt;Unlike Ethereum, which separates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;externally owned accounts (wallets)&lt;/li&gt;
&lt;li&gt;contract accounts (smart contracts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solana uses a &lt;strong&gt;unified account model&lt;/strong&gt; for everything.&lt;/p&gt;

&lt;p&gt;On Solana:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wallets are accounts&lt;/li&gt;
&lt;li&gt;programs are accounts&lt;/li&gt;
&lt;li&gt;tokens are accounts&lt;/li&gt;
&lt;li&gt;NFT metadata lives in accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything exists inside one giant flat key-value store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key   = public address
value = account data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you’re thinking like a backend developer:&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="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PublicKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That simplicity is one of the reasons Solana can process transactions efficiently.&lt;/p&gt;




&lt;h1&gt;
  
  
  Every Solana Account Has the Same Structure
&lt;/h1&gt;

&lt;p&gt;Every account contains the same core fields:&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;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lamports&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SOL balance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raw bytes storing state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;owner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Program allowed to control the account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;executable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether the account contains executable code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rent_epoch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deprecated rent-related field&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let’s break them down.&lt;/p&gt;




&lt;h1&gt;
  
  
  1. Lamports
&lt;/h1&gt;

&lt;p&gt;Lamports are the smallest unit of SOL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 SOL = 1,000,000,000 lamports
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dollars → cents&lt;/li&gt;
&lt;li&gt;bitcoin → satoshis&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;500000000 lamports = 0.5 SOL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lamports are used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transaction fees&lt;/li&gt;
&lt;li&gt;storing accounts&lt;/li&gt;
&lt;li&gt;transferring SOL&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  2. Data
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;data&lt;/code&gt; field stores arbitrary bytes.&lt;/p&gt;

&lt;p&gt;This is where programs save state, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;token balances&lt;/li&gt;
&lt;li&gt;NFT metadata&lt;/li&gt;
&lt;li&gt;usernames&lt;/li&gt;
&lt;li&gt;protocol configuration&lt;/li&gt;
&lt;li&gt;DeFi positions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike traditional databases, Solana does not store JSON directly.&lt;/p&gt;

&lt;p&gt;Everything is serialized into bytes.&lt;/p&gt;

&lt;p&gt;For Web2 developers, this feels more like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;binary storage&lt;/li&gt;
&lt;li&gt;buffers&lt;/li&gt;
&lt;li&gt;low-level database records&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  3. Owner
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;owner&lt;/code&gt; field is one of the most important concepts in Solana.&lt;/p&gt;

&lt;p&gt;It defines &lt;strong&gt;which program controls an account&lt;/strong&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Owner: Token Program
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means only the Token Program can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modify the account’s data&lt;/li&gt;
&lt;li&gt;debit lamports from the account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the foundation of Solana’s security model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ownership Rules
&lt;/h2&gt;

&lt;p&gt;Solana follows a surprisingly simple set of rules:&lt;/p&gt;

&lt;h3&gt;
  
  
  Only the owner program can:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;modify account data&lt;/li&gt;
&lt;li&gt;remove lamports from the account&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  But anyone can:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;send lamports to a writable account&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without ownership rules, any program could edit any account’s data.&lt;/p&gt;

&lt;p&gt;Imagine random APIs directly modifying your production database tables.&lt;/p&gt;

&lt;p&gt;That would be chaos.&lt;/p&gt;

&lt;p&gt;Solana prevents this through program ownership.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Executable
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;executable&lt;/code&gt; field is simply a boolean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true  = account contains program code
false = account stores data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;executable = true&lt;/code&gt;, the account contains compiled program code.&lt;/p&gt;

&lt;p&gt;Otherwise, it’s just a data account.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Rent Epoch
&lt;/h1&gt;

&lt;p&gt;This field is mostly deprecated today.&lt;/p&gt;

&lt;p&gt;Historically, it related to Solana’s rent system.&lt;/p&gt;

&lt;p&gt;Now it’s typically set to a maximum value and ignored in most cases.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Biggest Mindset Shift: Programs Don’t Store Their Own State
&lt;/h1&gt;

&lt;p&gt;This was the concept that confused me the most.&lt;/p&gt;

&lt;p&gt;On Solana:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Programs are stateless.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Smart contracts do not permanently store their own internal state.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;program accounts store executable code&lt;/li&gt;
&lt;li&gt;separate accounts store persistent data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is very different from Ethereum.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Web2 Analogy That Made It Click
&lt;/h1&gt;

&lt;p&gt;Think of a Solana program like a Node.js backend service.&lt;/p&gt;

&lt;p&gt;The backend server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contains logic&lt;/li&gt;
&lt;li&gt;processes requests&lt;/li&gt;
&lt;li&gt;validates data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it does not permanently store all user data in memory.&lt;/p&gt;

&lt;p&gt;Instead, it reads from and writes to a database.&lt;/p&gt;

&lt;p&gt;Solana works the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program Account = Backend Server
Data Accounts   = Database Records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Programs execute logic.&lt;/p&gt;

&lt;p&gt;Accounts store persistent state.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example: A Todo App on Solana
&lt;/h1&gt;

&lt;p&gt;Imagine building a Todo app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Program Account
&lt;/h3&gt;

&lt;p&gt;Contains logic like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createTodo()
updateTodo()
deleteTodo()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Data Accounts
&lt;/h3&gt;

&lt;p&gt;Store actual user data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- todo text
- completed status
- owner wallet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program modifies data accounts whenever users interact with the app.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Solana Separates Code From State
&lt;/h1&gt;

&lt;p&gt;At first, this architecture feels inconvenient.&lt;/p&gt;

&lt;p&gt;But it enables several powerful features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parallel transaction execution&lt;/li&gt;
&lt;li&gt;better scalability&lt;/li&gt;
&lt;li&gt;clearer ownership rules&lt;/li&gt;
&lt;li&gt;flexible storage patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because accounts are separate from programs, Solana can determine which transactions touch which accounts.&lt;/p&gt;

&lt;p&gt;That allows many transactions to execute simultaneously.&lt;/p&gt;

&lt;p&gt;This is one of the major reasons Solana is fast.&lt;/p&gt;




&lt;h1&gt;
  
  
  Rent Exemption Explained
&lt;/h1&gt;

&lt;p&gt;Every account on Solana must maintain a minimum balance to stay on-chain.&lt;/p&gt;

&lt;p&gt;This is called:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;rent exemption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The required amount depends on the account’s data size.&lt;/p&gt;

&lt;p&gt;More data requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more storage&lt;/li&gt;
&lt;li&gt;more lamports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a tiny account with no additional data, the requirement is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~0.00089 SOL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check rent requirements with:&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;getMinimumBalanceForRentExemption&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Why Rent Exists
&lt;/h1&gt;

&lt;p&gt;In Web2 systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;servers have storage limits&lt;/li&gt;
&lt;li&gt;databases cost money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Solana validators also store account data permanently.&lt;/p&gt;

&lt;p&gt;Without rent requirements, attackers could spam the network with millions of useless accounts.&lt;/p&gt;

&lt;p&gt;Rent helps prevent storage abuse.&lt;/p&gt;




&lt;h1&gt;
  
  
  Reading a Real Solana Account
&lt;/h1&gt;

&lt;p&gt;Using the 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 &amp;lt;ACCOUNT_ADDRESS&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public Key: 7xKXtg2CW...
Balance: 0.002 SOL
Owner: System Program
Executable: false
Rent Epoch: 18446744073709551615
Data Length: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s how to interpret it:&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;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Public Key&lt;/td&gt;
&lt;td&gt;Account address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Balance&lt;/td&gt;
&lt;td&gt;SOL stored in the account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Owner&lt;/td&gt;
&lt;td&gt;Program controlling the account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executable&lt;/td&gt;
&lt;td&gt;Whether it stores code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Length&lt;/td&gt;
&lt;td&gt;Amount of stored data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rent Epoch&lt;/td&gt;
&lt;td&gt;Deprecated field&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you understand these fields, Solana Explorer becomes much easier to read.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example of an account detail
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbstol784wh1gpaqp3kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbstol784wh1gpaqp3kx.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The System Program
&lt;/h1&gt;

&lt;p&gt;One program you’ll constantly encounter is the System Program.&lt;/p&gt;

&lt;p&gt;Think of it like the operating system kernel.&lt;/p&gt;

&lt;p&gt;It handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account creation&lt;/li&gt;
&lt;li&gt;SOL transfers&lt;/li&gt;
&lt;li&gt;ownership assignment&lt;/li&gt;
&lt;li&gt;storage allocation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost every Solana application interacts with it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Comparing Solana to Ethereum
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Ethereum
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;contracts store their own state&lt;/li&gt;
&lt;li&gt;different account types exist&lt;/li&gt;
&lt;li&gt;code and storage are tightly coupled&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solana
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;everything is an account&lt;/li&gt;
&lt;li&gt;programs are stateless&lt;/li&gt;
&lt;li&gt;state lives in separate accounts&lt;/li&gt;
&lt;li&gt;unified account architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one of the biggest conceptual shifts for Web2 developers entering Solana.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Mental Model
&lt;/h1&gt;

&lt;p&gt;Here’s the simplest way I now think about Solana:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accounts       = Files
Programs       = Executable apps
Data Accounts  = Databases/documents
System Program = Operating system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the account model clicks, many other Solana concepts become easier to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tokens&lt;/li&gt;
&lt;li&gt;NFTs&lt;/li&gt;
&lt;li&gt;staking&lt;/li&gt;
&lt;li&gt;DeFi protocols&lt;/li&gt;
&lt;li&gt;DAOs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They all build on the same foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accounts&lt;/li&gt;
&lt;li&gt;ownership&lt;/li&gt;
&lt;li&gt;programs&lt;/li&gt;
&lt;li&gt;data storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly, that unified design is one of the things that makes Solana so interesting to learn as a developer.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Solana Transactions Explained for Backend Developers</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Sat, 09 May 2026 15:17:33 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/solana-transactions-explained-for-backend-developers-47hk</link>
      <guid>https://dev.to/tobi_ayinmiro/solana-transactions-explained-for-backend-developers-47hk</guid>
      <description>&lt;p&gt;Learning Solana transactions completely changed how I think about backend systems.&lt;/p&gt;

&lt;p&gt;At first, I treated transactions like normal API requests:&lt;/p&gt;

&lt;p&gt;client → server → database → response.&lt;/p&gt;

&lt;p&gt;But Solana transactions are different.&lt;/p&gt;

&lt;p&gt;They’re signed, temporary, atomic state changes sent to a decentralized network. Instead of “calling a server,” you prepare instructions that validators execute on-chain.&lt;/p&gt;

&lt;p&gt;The biggest shift for me was understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recent blockhashes&lt;/li&gt;
&lt;li&gt;signatures&lt;/li&gt;
&lt;li&gt;atomic execution&lt;/li&gt;
&lt;li&gt;compute limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing that made it click for me was actually building and sending transactions with "@solana/kit".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createSolanaRpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;devnet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;generateKeyPairSigner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;airdropFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@solana/kit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateKeyPairSigner&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;airdrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;airdropFactory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;rpc&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;airdrop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;recipientAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lamports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Airdrop complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oddly enough, failed transactions taught me the most.&lt;/p&gt;

&lt;p&gt;Breaking transactions on devnet helped me understand how Solana programs actually execute internally.&lt;/p&gt;

&lt;p&gt;Web3 development feels very different once you stop thinking in request/response patterns.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Solana transfer</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Tue, 05 May 2026 10:36:40 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/solana-transfer-2k6h</link>
      <guid>https://dev.to/tobi_ayinmiro/solana-transfer-2k6h</guid>
      <description>&lt;p&gt;Hey guys i just sent my first solana and we can all see it. blockchain = transparency&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0lmn5ttn03dyhleafhl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0lmn5ttn03dyhleafhl.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
    </item>
    <item>
      <title>Understand transaction anatomy</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Tue, 05 May 2026 09:48:17 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/understand-transaction-anatomy-2kaj</link>
      <guid>https://dev.to/tobi_ayinmiro/understand-transaction-anatomy-2kaj</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmm6h0vtp3bcz12xn8wx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmm6h0vtp3bcz12xn8wx8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4bk61b3vov5eubxisvc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4bk61b3vov5eubxisvc.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;Ever wondered what’s inside a Solana transaction? 👀&lt;br&gt;
Just drop the signature into the explorer:&lt;a href="https://explorer.solana.com/" rel="noopener noreferrer"&gt;https://explorer.solana.com/&lt;/a&gt; and unlock everything status, wallets, tokens, and logs. Blockchain = open books.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
    </item>
    <item>
      <title>From Confusion to Clarity: My Second Week with Solana</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Sat, 02 May 2026 16:41:29 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/from-confusion-to-clarity-my-second-week-with-solana-2lk0</link>
      <guid>https://dev.to/tobi_ayinmiro/from-confusion-to-clarity-my-second-week-with-solana-2lk0</guid>
      <description>&lt;p&gt;Week one felt like learning new vocabulary. Week two with Solana felt like trying to form actual sentences.&lt;br&gt;
At first, nothing made sense accounts, lamports, RPC calls. But then something clicked: blockchain isn’t magic. It’s just a different way of organizing and accessing data.&lt;br&gt;
The turning point for me was building a simple dashboard that reads on-chain data. Seeing real values update made everything feel tangible.&lt;br&gt;
Compared to traditional APIs, working with RPC feels more raw and direct. There’s no middle layer simplifying things you’re responsible for understanding what the data means.&lt;br&gt;
What surprised me most is how much responsibility is pushed to the developer. There’s power in that, but also complexity.&lt;br&gt;
I’m still figuring out how to design systems properly in this environment, but I’m starting to see the bigger picture and I'm excited for what's to come.&lt;/p&gt;

&lt;h1&gt;
  
  
  100DaysOfSolana
&lt;/h1&gt;

</description>
      <category>100daysofsolana</category>
    </item>
    <item>
      <title>Traditional Database vs Solana Accounts</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Thu, 30 Apr 2026 13:33:47 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/traditional-database-vs-solana-accounts-4om6</link>
      <guid>https://dev.to/tobi_ayinmiro/traditional-database-vs-solana-accounts-4om6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh33ly3hg4kf9didwlten.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh33ly3hg4kf9didwlten.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey guys, i did a comparison of traditional database and solana account &lt;/p&gt;

</description>
      <category>100daysofsolana</category>
    </item>
    <item>
      <title>Exploring web3</title>
      <dc:creator>Tobi Ayinmiro</dc:creator>
      <pubDate>Sun, 26 Apr 2026 15:58:21 +0000</pubDate>
      <link>https://dev.to/tobi_ayinmiro/exploring-web3-1lad</link>
      <guid>https://dev.to/tobi_ayinmiro/exploring-web3-1lad</guid>
      <description>&lt;p&gt;Day 7 of #100DaysOfSolana&lt;/p&gt;

&lt;p&gt;I created my  Solana devnet wallet and checked my balance for the first time. Seeing the transaction appear on-chain made Web3 feel real everything is verified publicly.&lt;/p&gt;

&lt;p&gt;Next, I’m excited to build something that interacts with the blockchain using my wallet.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
    </item>
  </channel>
</rss>
