<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Master blockchain internals through real examples]]></title><description><![CDATA[I’m Andrey,  a Go & Solidity engineer who’s spent the past years in the trenches of the blockchain quirks, gas optimization, encoding schemes, and different protocol internals. Every week I share battle-tested code examples and real-world RPC deep dives. ]]></description><link>https://andreyobruchkov1996.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png</url><title>Master blockchain internals through real examples</title><link>https://andreyobruchkov1996.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 19 Jul 2026 04:19:26 GMT</lastBuildDate><atom:link href="https://andreyobruchkov1996.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Andrey]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[andreyobruchkov1996@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[andreyobruchkov1996@substack.com]]></itunes:email><itunes:name><![CDATA[0xByteBeetle]]></itunes:name></itunes:owner><itunes:author><![CDATA[0xByteBeetle]]></itunes:author><googleplay:owner><![CDATA[andreyobruchkov1996@substack.com]]></googleplay:owner><googleplay:email><![CDATA[andreyobruchkov1996@substack.com]]></googleplay:email><googleplay:author><![CDATA[0xByteBeetle]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Utility Extensions: Completing the Token-2022 Architecture]]></title><description><![CDATA[Historically, the SPL token standard was incredibly simple.]]></description><link>https://andreyobruchkov1996.substack.com/p/the-utility-extensions-completing</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/the-utility-extensions-completing</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Tue, 14 Jul 2026 12:14:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!e9MB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Historically, the SPL token standard was incredibly simple. It did exactly one thing: manage a generic <code>u64</code> integer. If a protocol needed anything more complex like royalties, KYC whitelists, vesting schedules, or privacy, developers had to write custom wrapper smart contracts. They had to spin up external PDAs, force users to route transactions through proprietary programs, and execute Cross-Program Invocations (CPIs) just to mutate basic state.</p><p>This fragmented liquidity, broke composability across DeFi, and multiplied smart contract attack vectors.</p><p>Token-2022 flips this architecture on its head. By pushing custom logic directly into the Mint and the Token Account via Type-Length-Value (TLV) extensions, the asset itself becomes smart. The L1 runtime handles the complexity natively, allowing decentralized exchanges, lending markets, and automated market makers to interact with heavily customized tokens without needing to trust or audit a third-party wrapper contract.</p><p>But Token-2022 is not just about massive, complex upgrades like Zero-Knowledge proofs. The standard also ships with a suite of lightweight, &#8220;utility&#8221; primitives designed to solve everyday enterprise, compliance, and UX issues.</p><p>In this final installment of our Token-2022 series, we are going to close out the toolkit. We will break down the smaller, highly effective extensions that every Solana developer needs in their arsenal: <strong>Permanent Delegates, Non-Transferable tokens</strong>, <strong>Default Account States</strong>, and <strong>Required Memos</strong>.</p></blockquote><div><hr></div><h2>Permanent Delegate Extension</h2><p>In the legacy SPL standard, there is a strict cryptographic boundary: <strong>Only the Token Account Owner can transfer or burn the tokens inside their wallet.</strong> The Mint Authority only has the power to <em>create</em> new tokens. Once those tokens land in a user&#8217;s wallet, the protocol loses all control over them. If you are building a stablecoin and need to comply with OFAC sanctions by clawing back funds from a hacked wallet, you are out of luck. If you are building an automated subscription service and need to deduct tokens every month, you have to force the user to sign a pre-approved delegation transaction which is a massive UX friction point.</p><p>Token-2022 solves this by introducing the <strong>Permanent Delegate</strong> extension.</p><p>This extension allows the token creator to designate a specific public key at the Mint level that has unrestricted, permanent <code>transfer</code> and <code>burn</code> rights over <em>every single token account</em> holding that asset.</p><h3>Hands-on overview</h3><p>To truly understand how powerful this is, let&#8217;s test it live on Devnet. We are going to create a token with a Permanent Delegate, mint it to a user, and then watch the delegate reach in and burn the user&#8217;s tokens without their signature.</p><h4>Create Token</h4><p>You enable this extension and specify the delegate address during the initial Mint creation. Once set, the permanent delegate has &#8220;God Mode&#8221; over the token supply.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;5b8efa46-e84a-4efa-8964-e332e518abc1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-permanent-delegate</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!e9MB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!e9MB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 424w, https://substackcdn.com/image/fetch/$s_!e9MB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 848w, https://substackcdn.com/image/fetch/$s_!e9MB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 1272w, https://substackcdn.com/image/fetch/$s_!e9MB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!e9MB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png" width="1456" height="651" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:651,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:252889,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!e9MB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 424w, https://substackcdn.com/image/fetch/$s_!e9MB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 848w, https://substackcdn.com/image/fetch/$s_!e9MB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 1272w, https://substackcdn.com/image/fetch/$s_!e9MB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8a30803-973d-4db7-83fc-56b5769d5c37_2952x1320.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/token/33iQokS7SC7XQ8NgoayfVo3KqU19mYjpW8dHJbgHUfj2?cluster=devnet#extensions</figcaption></figure></div><p>We can see the created <strong>PermanentDelegate</strong> extension for our token.</p><h4>Mint Token To Someone</h4><p>Create token account for someone&#8217;s wallet and mint 100 tokens to him.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;a427e650-0b58-4ded-8b81-724159fba107&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># create token account for someone
spl-token create-account &lt;MINT_ADDRESS&gt; --owner &lt;SOME_WALLET&gt; --fee-payer ~/.config/solana/id.json

# mint 100 tokens to someone's Token Account
spl-token mint &lt;MINT_ADDRESS&gt; 100 &lt;SOME_WALLET_TOKEN_ACCOUNT&gt; 
</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jfu8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jfu8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 424w, https://substackcdn.com/image/fetch/$s_!Jfu8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 848w, https://substackcdn.com/image/fetch/$s_!Jfu8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 1272w, https://substackcdn.com/image/fetch/$s_!Jfu8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jfu8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png" width="1456" height="336" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:336,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:199709,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Jfu8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 424w, https://substackcdn.com/image/fetch/$s_!Jfu8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 848w, https://substackcdn.com/image/fetch/$s_!Jfu8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 1272w, https://substackcdn.com/image/fetch/$s_!Jfu8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd49c50c2-59d0-48df-bdc2-d07793be3122_2952x682.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">https://solscan.io/tx/QNNz9XJk1ccKNTtS96mJsxtbo8766tLsPnX1GWYa5mfx6iXbn2ffBsumcXWVwoztfrWAKjZrSWCMTgSSq9WnMSw?cluster=devnet</figcaption></figure></div><h4>Burning Tokens With the delegate address</h4><p>Right now, someone holds 100 tokens. In legacy SPL, only this address could touch them. But because we established a <strong>Permanent Delegate </strong>on our address, we can reach directly into this wallet&#8217;s account and burn 50 tokens.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;c588dd66-20d4-4a64-98c5-3c5cd8e791ad&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token burn &lt;SOMEONE_TOKEN_ACCOUNT&gt; 50</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2V0E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2V0E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 424w, https://substackcdn.com/image/fetch/$s_!2V0E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 848w, https://substackcdn.com/image/fetch/$s_!2V0E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 1272w, https://substackcdn.com/image/fetch/$s_!2V0E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2V0E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png" width="1456" height="433" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:433,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:241101,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2V0E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 424w, https://substackcdn.com/image/fetch/$s_!2V0E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 848w, https://substackcdn.com/image/fetch/$s_!2V0E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 1272w, https://substackcdn.com/image/fetch/$s_!2V0E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe83fd99-ec7f-469e-b8f8-7023f9d1722c_2952x878.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/tx/6r2iYDVTGdbMGNnCm3jNexeN22nkSUxRXY6vMnRfPC1re7NX11X7KUxM3mhRX18kZRrc8ChLrJ6cdhXtGNrYue8?cluster=devnet</figcaption></figure></div><p>The transaction succeeds. The Token-2022 program checks the Mint, sees that We are the Permanent Delegate, bypasses Someone&#8217;s wallet ownership authority entirely, and burns the tokens.</p><p>This primitive is incredibly powerful on its own, but it unlocks a massive architectural design pattern when paired with next extension.</p><h2>Non-Transferable Tokens Extension</h2><p>Sometimes, you need a guarantee that a user cannot sell, trade, or transfer an asset once it lands in their wallet.</p><p>Historically, developers achieved this using a standard NFT mint to a user and immediately issue a <code>Freeze</code> instruction to lock the token account. This worked, but it was fragile. It required the issuer to maintain freeze authority, pay external protocol fees, and if they ever accidentally unfroze the account, the user could immediately dump the &#8220;identity&#8221; token on a secondary marketplace.</p><p>Token-2022 completely replaces this legacy NFT use-case with the <strong>Non-Transferable</strong> extension. It makes immobility a fundamental law of the Mint itself. The L1 base layer will permanently reject any <code>Transfer</code> instruction involving this token.</p><h3>Hands-on Overview</h3><p>To create a Non-Transferable token, you must apply the flag during the initial Mint creation. Once the Mint is initialized, this extension is permanently baked into the asset and cannot be disabled.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;9349cb6b-1ab8-4b45-8b06-b6d7108fd7a2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-non-transferable</code></pre></div><blockquote><p><strong>Note:</strong><em><strong> </strong>You can mint this token to your users exactly like a normal SPL token. The restriction only applies when they try to send it somewhere else</em></p></blockquote><p>Let&#8217;s say some address holds 100 of these tokens and tries to transfer it to another wallet.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;8854277e-4400-4fd4-9eea-8ef05f5a176c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token transfer 7b7FYYVpa5xgTS3PCHAU6ZBDjD1NbwZcKCqYcMmArj4n 50 L6h2ugreR3oNmKtBCpcsMPSLg3BwLR31fSPB1iU76mp</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vxwv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vxwv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 424w, https://substackcdn.com/image/fetch/$s_!vxwv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 848w, https://substackcdn.com/image/fetch/$s_!vxwv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 1272w, https://substackcdn.com/image/fetch/$s_!vxwv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vxwv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png" width="1386" height="424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:424,&quot;width&quot;:1386,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:476032,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vxwv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 424w, https://substackcdn.com/image/fetch/$s_!vxwv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 848w, https://substackcdn.com/image/fetch/$s_!vxwv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 1272w, https://substackcdn.com/image/fetch/$s_!vxwv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8efa0f13-5271-4737-8c7e-8d3941478dcd_1386x424.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The Result: Hard Fail.</strong> The transaction will instantly revert at the protocol level with a <code>Transfer is disabled for this mint</code> custom program error.</p><h4>Architecture 1: Native L1 Soulbound Badges</h4><p>When developers hear &#8220;Non-Transferable,&#8221; they almost exclusively think of 1-of-1 NFTs. For example, issuing an on-chain graduation certificate for a developer bootcamp or a KYC verification badge.</p><p>By combining the <strong>Metadata</strong> extension (which we covered<strong> <a href="https://andreyobruchkov1996.substack.com/p/where-token-metadata-lives-on-solana">here</a></strong>) with <strong>0 Decimals</strong> and the <strong>Non-Transferable</strong> extension, you can mint native, beautifully rendered L1 &#8220;Soulbound Badges&#8221; without ever touching a third-party standard.</p><h4>Architecture 2: On-Chain Consumables</h4><p>But the real magic happens when you apply the Non-Transferable extension to a <strong>Fungible</strong> token (for example with 9 decimals) and combine it with the <strong>Permanent Delegate</strong> we just built previously.</p><p>This creates the perfect architecture for <strong>On-Chain Consumables or Prepaid Credits</strong>. Imagine an RPC node provider, a decentralized AI compute network, or an in-game economy. A user buys 1,000 API credits.</p><ol><li><p>Because the tokens are <strong>Non-Transferable</strong>, you guarantee the user cannot create an AMM liquidity pool to dump their unused credits and crash your internal economy. They can only hold them.</p></li><li><p>Because your protocol backend holds the <strong>Permanent Delegate</strong> key, your server can automatically execute a <code>Burn</code> instruction to destroy tokens directly from the user&#8217;s wallet every time they make an API call.</p></li></ol><p>The user gets transparent, on-chain accounting of their prepaid credits, and the protocol gets absolute economic security without sacrificing UX.</p><h4>Example</h4><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;4162e4b0-391c-49df-afc0-d4a43f0c6064&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-permanent-delegate --enable-non-transferable</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CYgj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CYgj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 424w, https://substackcdn.com/image/fetch/$s_!CYgj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 848w, https://substackcdn.com/image/fetch/$s_!CYgj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 1272w, https://substackcdn.com/image/fetch/$s_!CYgj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CYgj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png" width="1456" height="741" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:741,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:275718,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CYgj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 424w, https://substackcdn.com/image/fetch/$s_!CYgj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 848w, https://substackcdn.com/image/fetch/$s_!CYgj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 1272w, https://substackcdn.com/image/fetch/$s_!CYgj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa180a35e-4689-4bd6-8b70-aeb4058ae9ae_2938x1496.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/token/6k4XgpoJx4WuvPTpfpn4VrHrmtHMdqYSsDY2WZA3QZrf?cluster=devnet#extensions</figcaption></figure></div><h4>What about manual burning?</h4><p>A common developer assumption is that &#8220;non-transferable&#8221; means the token is permanently stuck in the user&#8217;s wallet forever. This is a massive UX risk what if a user receives a malicious or spam soulbound token and wants to clean up their wallet?</p><p>Non-transferable does <em>not</em> mean non-burnable. Even though the base layer blocks the <code>Transfer</code> instruction, the Token-2022 program still allows the account owner to execute a standard <code>Burn</code> instruction. Users always retain the ultimate authority to destroy assets in their own wallets to prevent permanent spam.</p><h2>Default Account State Extension</h2><p>If you are building a regulated protocol like an enterprise stablecoin, a real estate RWA (Real World Asset), or a strict KYC ecosystem, you need absolute control over who can hold and move your token.</p><p>By configuring this extension at the Mint level, you can force the L1 runtime to automatically initialize every single new Token Account in a <code>Frozen</code> state. Users can create their accounts to receive the asset, but they cannot send or receive tokens until your backend server verifies their KYC status and explicitly unfreezes their specific account.</p><h3>Hands-on Overview</h3><p>To use this, you must apply the <code>--default-account-state frozen</code> flag during Mint creation. Because you will eventually need to unfreeze compliant accounts, you <em>must</em> also stack the <code>--enable-freeze</code> flag so your protocol retains the Freeze Authority.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;43180d0c-3194-44c0-a5f0-fa365729e6b8&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># Create a compliance-gated Mint
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --default-account-state frozen --enable-freeze</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kai-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kai-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 424w, https://substackcdn.com/image/fetch/$s_!Kai-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 848w, https://substackcdn.com/image/fetch/$s_!Kai-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 1272w, https://substackcdn.com/image/fetch/$s_!Kai-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kai-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png" width="1456" height="643" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:643,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:231942,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Kai-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 424w, https://substackcdn.com/image/fetch/$s_!Kai-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 848w, https://substackcdn.com/image/fetch/$s_!Kai-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 1272w, https://substackcdn.com/image/fetch/$s_!Kai-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7616e59-86f9-416c-9634-12fbc84bd6f6_2940x1298.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/token/CgAAhZTKoeaAPBtXpJ5cfe3XnmgGGVH78Lq8JhFV627F?cluster=devnet#extensions</figcaption></figure></div><p>If someone creates an account for this token, it is frozen the millisecond it is initialized. Any incoming or outgoing transfers will revert until your protocol explicitly thaws their account.</p><p>Lets say that now someone creates his own token account and did not submit all the required data to our backend server, and our automated system accidentally tries to mint to this token account</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;c97c0b9f-37c4-40d6-b885-656286d03048&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token mint &lt;MINT_ADDRESS&gt; 100 &lt;SOME_TOKEN_ACCOUNT&gt;</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xhTl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xhTl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 424w, https://substackcdn.com/image/fetch/$s_!xhTl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 848w, https://substackcdn.com/image/fetch/$s_!xhTl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 1272w, https://substackcdn.com/image/fetch/$s_!xhTl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xhTl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png" width="1384" height="396" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:396,&quot;width&quot;:1384,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:433628,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xhTl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 424w, https://substackcdn.com/image/fetch/$s_!xhTl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 848w, https://substackcdn.com/image/fetch/$s_!xhTl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 1272w, https://substackcdn.com/image/fetch/$s_!xhTl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bf16031-a0ff-4aab-9aee-e16b275315f6_1384x396.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It will fail with &#8220;account is frozen&#8220; error. The transaction reverts perfectly at the runtime level. The Token-2022 program recognizes that this account is completely locked. It cannot receive incoming mints or transfers, and it cannot send tokens out.</p><p>Now lets say that this user submitted all the required data. Our backend verifies the identity and executes a <code>Thaw</code> instruction to unlock her specific Token Account.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;cd579df0-38ff-4a7d-a659-7be6847bb006&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token thaw &lt;SOME_TOKEN_ACCOUNT&gt;</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Omx7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Omx7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 424w, https://substackcdn.com/image/fetch/$s_!Omx7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 848w, https://substackcdn.com/image/fetch/$s_!Omx7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 1272w, https://substackcdn.com/image/fetch/$s_!Omx7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Omx7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png" width="1456" height="128" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/caebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:128,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50679,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Omx7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 424w, https://substackcdn.com/image/fetch/$s_!Omx7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 848w, https://substackcdn.com/image/fetch/$s_!Omx7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 1272w, https://substackcdn.com/image/fetch/$s_!Omx7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcaebb37a-0a99-475e-8b1d-ac7c73a14b71_2940x258.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">https://solscan.io/tx/WpW47gmduBEUkHK78To57NLds7vMzmtCHZqRAFU1KuUYzKAN4SZXkH9bBcqqeXWPU4vYh5w8b2oHjQypRjnWGkN?cluster=devnet</figcaption></figure></div><p>Now that this user is legally verified and the account is thawed, Our backend tries the exact same mint command again.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;60514c8e-2da9-4970-9c88-529b6bc618de&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token mint &lt;MINT_ADDRESS&gt; 100 &lt;SOME_TOKEN_ACCOUNT&gt;</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Oyzy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Oyzy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 424w, https://substackcdn.com/image/fetch/$s_!Oyzy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 848w, https://substackcdn.com/image/fetch/$s_!Oyzy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 1272w, https://substackcdn.com/image/fetch/$s_!Oyzy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Oyzy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png" width="1456" height="559" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:559,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:259095,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Oyzy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 424w, https://substackcdn.com/image/fetch/$s_!Oyzy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 848w, https://substackcdn.com/image/fetch/$s_!Oyzy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 1272w, https://substackcdn.com/image/fetch/$s_!Oyzy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6ace4043-3cc9-472a-83dc-36869bd82f01_2940x1128.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The Result: Success.</strong> This user receives the 100 tokens, fully compliant with the protocol&#8217;s L1 rules.</p><h2>Required Memo on Transfer</h2><p>If you have ever deposited tokens into a centralized exchange (CEX - centralized exchange), you know the panic of forgetting the &#8220;Deposit Memo.&#8221;</p><p>Because exchanges use massive, shared omnibus wallets to collect user deposits, they rely on attached text memos to map an incoming L1 transfer to a specific user&#8217;s database ID. The <strong>Required Memo on Transfer</strong> extension solves this at the protocol level.</p><p>Unlike the previous extensions we discussed, this is an <strong>Account Extension</strong>, not a Mint extension. It is applied by the <em>owner of the Token Account</em>, not the creator of the token.</p><h3>Hands-on Overview</h3><p>Let&#8217;s say your protocol&#8217;s treasury wallet (Alice) wants to ensure that no one can blindly dump tokens into her account without explaining what the transfer is for. Alice simply enables the extension on her specific Token Account.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;43ffb35a-93b5-4974-ac93-0a61d05294da&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># Alice enforces memos on her own Token Account
spl-token enable-required-transfer-memos &lt;ALICE_TOKEN_ACCOUNT_ADDRESS&gt;
</code></pre></div><p><em>Output On Explorer</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s488!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s488!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 424w, https://substackcdn.com/image/fetch/$s_!s488!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 848w, https://substackcdn.com/image/fetch/$s_!s488!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 1272w, https://substackcdn.com/image/fetch/$s_!s488!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s488!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png" width="1456" height="376" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:376,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:166093,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s488!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 424w, https://substackcdn.com/image/fetch/$s_!s488!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 848w, https://substackcdn.com/image/fetch/$s_!s488!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 1272w, https://substackcdn.com/image/fetch/$s_!s488!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F973c1992-2adf-4d95-b27c-c9e4e83ff97e_2980x770.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/account/EpBnH3MaJsSRVi7WBGHw9mXCtYTvwWY1NL789fBQXaKK?cluster=devnet</figcaption></figure></div><p>Now we see that the <strong>Memo is Required</strong>, if someone tries to run a standard transfer command to Alice&#8217;s account:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;2225a643-8aa1-4d1d-8feb-c6aa14accb41&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token transfer &lt;MINT_ADDRESS&gt; 50 &lt;ALICE_TOKEN_ACCOUNT_ADDRESS&gt;</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BoO5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BoO5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 424w, https://substackcdn.com/image/fetch/$s_!BoO5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 848w, https://substackcdn.com/image/fetch/$s_!BoO5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 1272w, https://substackcdn.com/image/fetch/$s_!BoO5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BoO5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png" width="1382" height="144" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:144,&quot;width&quot;:1382,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:114269,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BoO5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 424w, https://substackcdn.com/image/fetch/$s_!BoO5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 848w, https://substackcdn.com/image/fetch/$s_!BoO5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 1272w, https://substackcdn.com/image/fetch/$s_!BoO5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2444167e-60b4-4fd7-8dc8-4f03b5333045_1382x144.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong>The Result: Hard Fail.</strong> The transaction reverts at the runtime level because the destination account requires a memo, and none was provided.</p><p>To successfully send tokens to Alice, the sender is now mathematically forced to attach a memo string to the transaction instruction:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;5a2ae204-8421-409b-9e8e-d5da50c61a1a&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># The sender must explicitly attach the memo for the transaction to succeed
spl-token transfer &lt;MINT_ADDRESS&gt; 50 &lt;ALICE_TOKEN_ACCOUNT_ADDRESS&gt; --with-memo "Invoice payment for Q3 RPC node hosting"</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bLLh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bLLh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 424w, https://substackcdn.com/image/fetch/$s_!bLLh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 848w, https://substackcdn.com/image/fetch/$s_!bLLh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 1272w, https://substackcdn.com/image/fetch/$s_!bLLh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bLLh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png" width="1456" height="281" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:281,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:126838,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/191251836?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bLLh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 424w, https://substackcdn.com/image/fetch/$s_!bLLh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 848w, https://substackcdn.com/image/fetch/$s_!bLLh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 1272w, https://substackcdn.com/image/fetch/$s_!bLLh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71e44636-decc-4413-9a1d-25d1fd7b0463_2940x568.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">https://solscan.io/tx/3VGqCo88izrsDm1mYQFTbvi4c5V5CSgSgAhQ5YnDD4RysVBF3yXiFp1qm7kax8Fs46hKhL6LbrMMRAZtyLmbXNP8?cluster=devnet</figcaption></figure></div><h2>Summary</h2><p>Over the course of this series, we have covered Metadata, Transfer Hooks, Interest-Bearing Mints, Confidential Transfers, and finally, the core Utility Primitives.</p><p>If there is one major takeaway for developers, it is that the this blog&#8217;s architecture is no longer just a theory it is the standard. Token-2022 shifts complex, vulnerable logic out of custom smart contracts and pushes it natively into the L1 asset itself.</p><p>By stacking these extensions, you can build a stablecoin that natively yields interest, shields transfer amounts with Zero-Knowledge proofs, automatically freezes non-compliant accounts, and blocks unknown deposits, all without writing or deploying a single line of Rust.</p><p>The standard is here. It is time to start building.</p>]]></content:encoded></item><item><title><![CDATA[Native ZK on Solana: The Architecture of Confidential Transfers]]></title><description><![CDATA[Historically, solving the privacy problem has meant abandoning the Layer-1 execution environment.]]></description><link>https://andreyobruchkov1996.substack.com/p/native-zk-on-solana-the-architecture</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/native-zk-on-solana-the-architecture</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Wed, 15 Apr 2026 15:28:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!8Bdu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Historically, solving the privacy problem has meant abandoning the Layer-1 execution environment. Developers have been forced to spin up complex Layer-2 Zero-Knowledge (ZK) rollups, manage off-chain provers, or fragment their liquidity into isolated privacy pools.</p><p>Solana&#8217;s Token-2022 standard changes this by bringing the cryptography directly to the base layer.</p><p>With the introduction of the <strong>Confidential Transfers</strong> extension, Token-2022 embeds native Zero-Knowledge proofs and Twisted <strong>ElGamal encryption</strong> directly into the SPL token standard. Instead of moving assets to a separate network to obfuscate them, the tokens themselves carry encrypted state. The runtime mathematically proves to the network that a transfer is valid, ensuring no tokens were secretly minted or burned without ever revealing the actual transfer amount or the underlying account balances.</p><p>Just like the Interest-Bearing extension shifted yield from state mutation to pure computation, the Confidential Transfer extension shifts privacy from off-chain infrastructure to native, on-chain cryptography.</p><p>However, encrypting and decrypting state natively on the BPF runtime requires a radical shift in how we handle token routing. A standard transfer no longer just subtracts from Account A and adds to Account B. Instead, it introduces complex cryptographic states like &#8220;pending balances&#8221; and requires explicit ZK-proof verification instructions.</p><p>In this deep dive, we are going to unpack the architecture of the <code>ConfidentialTransfer</code> extension. We will break down the encrypted state layout and inspect the raw ciphertexts using the Solana CLI.</p></blockquote><div><hr></div><h2><strong>What are Confidential Transfers</strong></h2><p>When developers first hear &#8220;Confidential Transfers,&#8221; they often assume it is a privacy coin mixer like Tornado Cash. It is not. Token-2022 Confidential Transfers do not obfuscate the transaction graph.</p><p>If Alice sends a confidential transfer to Bob, the network (and anyone looking at a block explorer) can still see:</p><ul><li><p><strong>The Sender:</strong> Alice&#8217;s wallet address.</p></li><li><p><strong>The Receiver:</strong> Bob&#8217;s wallet address.</p></li><li><p><strong>The Token:</strong> The specific Mint address being transferred.</p></li></ul><p>What the network <em>cannot</em> see is <strong>the amount of tokens transferred</strong> and <strong>the underlying balances</strong> of Alice and Bob&#8217;s accounts.</p><p>To achieve this, Token-2022 introduces a <strong>dual-state architecture</strong>. A single token account now holds two entirely separate balances:</p><ol><li><p><strong>The Public Balance:</strong> The standard <code>u64</code> integer representing public tokens (fully transparent).</p></li><li><p><strong>The Confidential Balance:</strong> Cryptographic ciphertexts representing encrypted tokens.</p></li></ol><p>These two balances exist side-by-side. Tokens enter the encrypted ecosystem via a <code>Deposit</code> instruction, where a user converts their public tokens into an encrypted ciphertext. However, once those tokens are shielded, they can circulate privately forever. If you receive a confidential transfer from a friend or an employer, you never have to touch the public balance, you can simply hold, transfer, or spend those encrypted tokens using Zero-Knowledge proofs (<strong>will be explained shortly</strong>). If you ever want to exit the shielded ecosystem, you execute a <code>Withdraw</code> instruction, decrypting the ciphertext and moving the funds back to your public balance.</p><h3><strong>Initializing the Cryptographic State via CLI</strong></h3><p>To truly understand how this dual-state architecture works, we need to look at it on-chain. Let&#8217;s use the Solana CLI to create a confidential token, fund a user&#8217;s account, and watch the state transition from public to encrypted.</p><h4><strong>Creating the Mint</strong> </h4><p>First, we initialize a Token-2022 mint and explicitly tell the program to allocate the <code>ConfidentialTransfer</code> extension.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;4b16762a-7af8-4b88-801d-abab87aa9733&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --enable-confidential-transfers auto</code></pre></div><blockquote><p><em><strong>Note:</strong> </em>The <code>auto</code> keyword means that any token user can permissionlessly configure their account to perform confidential transfers.</p></blockquote><h4><strong>Configuring the User&#8217;s Token Account</strong> </h4><p>Next, we create a standard token account for our wallet. However, a standard token account can only hold a public <code>u64</code> balance. We must explicitly configure the account to allocate the extra bytes required to hold the ElGamal ciphertexts.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;5c4b5076-2565-438c-bae1-b8927d1c37d4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">
# Create the base token account
spl-token create-account &lt;YOUR_MINT_ADDRESS&gt;

# Allocate the cryptographic extension and generate the ElGamal keypair
spl-token configure-confidential-transfer-account &lt;YOUR_MINT_ADDRESS&gt;</code></pre></div><p>Under the hood, this second command does something important, <strong>it generates a Twisted ElGamal keypair</strong> (<strong>will be explained shortly</strong>) locally on your machine and stores the public key directly on the token account. This is the key the Token-2022 program will use to encrypt your incoming transfers.</p><h4><strong>The Public-to-Confidential Deposit</strong> </h4><p>Now that our account is configured for cryptography, let&#8217;s give ourselves <strong>100 public tokens</strong>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;7ad3d60b-e8e9-4c77-b841-6cbde95f6e54&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token mint &lt;YOUR_MINT_ADDRESS&gt; 100
</code></pre></div><p>lets take a look at the token account state</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;09e3a3ef-cf6d-4e8c-8b0e-d4b5bf131c5b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token display &lt;TOKEN_ACCOUNT_ACCRESS&gt;</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Bdu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Bdu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 424w, https://substackcdn.com/image/fetch/$s_!8Bdu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 848w, https://substackcdn.com/image/fetch/$s_!8Bdu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 1272w, https://substackcdn.com/image/fetch/$s_!8Bdu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Bdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png" width="1384" height="810" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:810,&quot;width&quot;:1384,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:483145,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190953373?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8Bdu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 424w, https://substackcdn.com/image/fetch/$s_!8Bdu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 848w, https://substackcdn.com/image/fetch/$s_!8Bdu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 1272w, https://substackcdn.com/image/fetch/$s_!8Bdu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F534d385a-ec4a-4c43-8e35-23c6f9a58cba_1384x810.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can see that the public balance is 100 and still not encrypted.</p><p>To enter the shielded state, we must deposit these public tokens into the encrypted balance.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;b07f7ad6-2ae8-46c6-93f1-d0ce50ea272e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">
spl-token deposit-confidential-tokens &lt;YOUR_MINT_ADDRESS&gt; 100</code></pre></div><p>and run the display again</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;64c68e5c-b8b3-47ce-8304-093d85701f0f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">spl-token display &lt;TOKEN_ACCOUNT_ACCRESS&gt;</code></pre></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ElWJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ElWJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 424w, https://substackcdn.com/image/fetch/$s_!ElWJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 848w, https://substackcdn.com/image/fetch/$s_!ElWJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 1272w, https://substackcdn.com/image/fetch/$s_!ElWJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ElWJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png" width="1384" height="810" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:810,&quot;width&quot;:1384,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:519955,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190953373?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ElWJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 424w, https://substackcdn.com/image/fetch/$s_!ElWJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 848w, https://substackcdn.com/image/fetch/$s_!ElWJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 1272w, https://substackcdn.com/image/fetch/$s_!ElWJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3516f40-4f53-41dd-8a4a-fb36a29cdd4d_1384x810.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is where the magic happens. The Token-2022 program deducts the 100 from your public <code>amount</code>, encrypts the number <code>100</code> using your account&#8217;s <strong>ElGamal public key</strong>, and writes the resulting ciphertext to the <code>ConfidentialTransferAccount</code> TLV extension.</p><p>right now, your public balance will show <code>0</code>, but your funds are not gone, they are locked inside the cryptographic state, ready to be transferred with Zero-Knowledge proofs.</p><blockquote><p><strong>Note:</strong> Even though your CLI wallet holds the <strong>ElGamal private key</strong> locally, the standard <code>spl-token</code> CLI currently lacks a <code>--decrypt</code> flag. Instead of a clean integer, you will be staring at raw, encrypted bytes, the actual Pedersen commitments and ciphertexts living on the BPF runtime. To see the human-readable number <code>100</code>, you must write a custom client-side script to manually fetch the state and decrypt the ciphertext locally. </p><p><strong>open github issue:</strong> https://github.com/solana-program/token-2022/issues/145</p></blockquote><h2><strong>A High-Level Overview of the Cryptography of Encrypted State </strong></h2><p>To understand the architecture, lets understand the core problem.</p><p>Imagine Alice wants to send Bob 10 tokens. On a standard SPL token, the runtime simply checks if Alice has &gt;= 10 tokens and if so subtracts 10 from her balance, and adds 10 to Bob&#8217;s balance.</p><p>If we want to make this confidential, the most obvious solution is to encrypt the token balances before writing them to the account data. But this introduces an immediate architectural roadblock: if the Token-2022 program cannot read the raw numbers because they are encrypted, how can it execute the transfer? It cannot natively subtract 10 from Alice&#8217;s account if it doesn&#8217;t know what her balance is.</p><h4><strong>Linear Homomorphism</strong></h4><p>To solve this, Token-2022 relies on a class of encryption called <strong>Linear Homomorphism</strong>.</p><p>A linearly homomorphic encryption scheme allows mathematical operations, like addition and subtraction to be performed directly on <em>encrypted ciphertexts</em>, yielding a result that perfectly matches the encrypted sum or difference of the underlying plaintexts.</p><p>When Alice initiates a confidential transfer, she doesn&#8217;t pass the raw number <code>10</code> to the program. Instead, she generates an encrypted ciphertext of the transfer amount. The Token-2022 program simply takes Alice&#8217;s encrypted account balance, subtracts the encrypted transfer amount, and saves the new ciphertext back to her state. It then takes Bob&#8217;s encrypted balance, adds the encrypted transfer amount, and saves it.</p><p><strong>Encrypted(50) - Encrypted(10) = Encrypted(40)</strong></p><p>Because the math is homomorphic, the Solana runtime successfully processes the transfer without ever knowing that the underlying numbers were 50, 10, and 40.</p><h4><strong>Zero-Knowledge Proofs - Trust but Verify</strong></h4><p>If the program never decrypts the underlying numbers, what stops a malicious user with an encrypted balance of 50 from sending an encrypted transfer of 70? Or what stops them from sending a negative amount to mathematically inflate their own balance?</p><p>Because the token program cannot natively see the hidden inputs, every confidential transfer must be accompanied by <strong>Zero-Knowledge (ZK) Proofs</strong> generated locally by the sender&#8217;s wallet:</p><ol><li><p><strong>Range Proofs:</strong> The sender must prove that the transfer amount is a positive 64-bit integer, and that subtracting this amount from their current balance will leave a value &gt;= 0.</p></li><li><p><strong>Equality Proofs:</strong> The transfer amount is encrypted twice, once under the sender&#8217;s public key to deduct it and once under the receiver&#8217;s public key to add it. The sender must mathematically prove that both ciphertexts encrypt the exact same underlying value.</p></li></ol><p>Token-2022 passes these proofs to Solana&#8217;s<strong> native ZK Token Proof program</strong>. Once the ZK program verifies the math is sound, Token-2022 executes the homomorphic addition and subtraction.</p><p>More detailed reference can be found <a href="https://www.solana-program.com/docs/confidential-balances/overview#zero-knowledge-proofs">here</a>.  </p><h2><strong>Preventing Cryptographic Griefing with the Split State Architecture</strong></h2><p>Encrypting state and using ZK proofs creates a unique new attack vector that doesn&#8217;t exist in standard transparent tokens: <strong>Cryptographic Front-Running</strong>.</p><p>To submit a valid transfer, Alice must generate her ZK proofs locally on her machine. This proof is mathematically tied to her <em>current</em> encrypted account balance on-chain. Generating this proof takes computational time (often a few seconds on a standard client).</p><p>Imagine Alice generates a proof based on her encrypted balance of 50 tokens and submits the transaction to the network. However, milliseconds before her transaction lands, a malicious actor (or just a busy protocol) transfers 1 token to Alice.</p><p>Because of the homomorphic math, that incoming 1 token mutates Alice&#8217;s encrypted state on-chain. When Alice&#8217;s transaction finally hits the Token-2022 program a fraction of a second later, the ZK proof she generated is instantly rejected because it was built against the old ciphertext.</p><p>If an attacker continuously flooded Alice&#8217;s account with micro-transfers (dusting), they could constantly mutate her state, making it mathematically impossible for her to ever generate a valid ZK proof fast enough to send an outgoing transfer. Her account would be permanently bricked.</p><h4><strong>The Solution: Splitting the Balance</strong></h4><p>To solve this, Token-2022 fundamentally alters the Token Account layout. The <code>ConfidentialTransferAccount</code> TLV extension splits the encrypted balance into two entirely separate ciphertexts:</p><ol><li><p><code>available_balance</code></p></li><li><p><code>pending_balance</code></p></li></ol><p>When Alice sends tokens <em>out</em>, the Token-2022 program subtracts the ciphertext strictly from her <code>available_balance</code>. When someone sends tokens <em>to</em> Alice, the program adds the ciphertext strictly to her <code>pending_balance</code>.</p><p>This strict separation guarantees that nobody but Alice can mutate her <code>available_balance</code>. Because her ZK proofs are generated exclusively against her <code>available_balance</code>, incoming transfers can no longer invalidate her outgoing proofs. The attack vector is completely neutralized.</p><p><strong>However, this architecture introduces an integration trap.</strong></p><h3><strong>The Mechanics of the Pending Balance</strong></h3><p>The split-state architecture solves the cryptographic front-running problem. Because incoming transfers only hit your <code>pending_balance</code>, your <code>available_balance</code> remains completely static. This ensures that the ZK proofs your client is busy generating locally will never be invalidated by unexpected network traffic.</p><p>However, this architectural safeguard introduces a necessary friction point in the token lifecycle: <strong>Incoming funds are not immediately spendable.</strong></p><p>If Alice sends you 50 encrypted tokens, the Token-2022 program homomorphically adds the transfer ciphertext to your <code>pending_balance</code> ciphertext. Your <code>available_balance</code> remains untouched. Because outgoing transfers can <em>only</em> deduct from the <code>available_balance</code>, those 50 tokens are effectively parked in a cryptographic waiting room.</p><h4><strong>The State Sweep - Apply Pending Balance</strong></h4><p>To actually spend received funds, the account owner must explicitly instruct the Token-2022 program to merge the two ciphertexts. This is done via the <code>ApplyPendingBalance</code> instruction.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;5bf92bba-29b6-4893-bf16-a915448a2274&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token apply-pending-balance &lt;MINT_ACCOUNT&gt;
</code></pre></div><p>When this instruction is fired, it triggers a deterministic state transition on-chain. The Token-2022 processor:</p><ol><li><p>Takes the current <code>pending_balance</code> ciphertext.</p></li><li><p>Homomorphically adds it to the <code>available_balance</code> ciphertext.</p></li><li><p>Overwrites the <code>available_balance</code> with the new combined ciphertext.</p></li><li><p>Resets the <code>pending_balance</code> ciphertext to a zero-value encryption.</p></li></ol><blockquote><p><em><strong>Note:</strong></em> This is done manually because by forcing the account owner to explicitly call <code>ApplyPendingBalance</code>, the protocol guarantees that the <code>available_balance</code> only ever mutates exactly when the user wants it to. You control your own state updates, ensuring your local ZK proof generation environment remains perfectly stable.</p></blockquote><h4>The Transfer Trap: Failing vs Succeeding hands on</h4><p>Let&#8217;s prove that the 100 tokens we just deposited are unspendable in their current state. We will try to send 50 confidential tokens to a friend.</p><p><strong>Prerequisite:</strong> The Receiver Opt-In Handshake</p><p>If you try to send confidential tokens to a standard public wallet, the transfer will instantly fail because to send an encrypted transfer, your local client must encrypt the transfer amount using the <em>receiver&#8217;s</em> ElGamal public key.</p><p>If Bob hasn&#8217;t explicitly opted into the shielded ecosystem, he doesn&#8217;t have an ElGamal keypair attached to his account. You cannot force-send privacy tokens, it is a two-way handshake. Before we can route tokens to Bob, he must create an account and configure his cryptography:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;ae2c7cb9-c46c-4abb-b882-6fce25235ba5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash"># Bob creates his token account
spl-token create-account &lt;YOUR_MINT_ADDRESS&gt; --owner bob.json

# Bob generates his ElGamal keypair and appends it to his account
spl-token configure-confidential-transfer-account &lt;YOUR_MINT_ADDRESS&gt; --owner bob.json</code></pre></div><h5>The Fail Case</h5><p>Now that Bob is mathematically ready to receive encrypted funds, let&#8217;s execute the transfer. We must pass the <code>--confidential</code> flag, otherwise the CLI will attempt a standard public routing.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;f22ea592-dd0e-43b2-aaec-b280eff83a3b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token transfer &lt;MINT_ACCOUNT&gt; 50 &lt;BOB_TOKEN_ACCOUNT&gt; --confidential</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ia-b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ia-b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 424w, https://substackcdn.com/image/fetch/$s_!Ia-b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 848w, https://substackcdn.com/image/fetch/$s_!Ia-b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 1272w, https://substackcdn.com/image/fetch/$s_!Ia-b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ia-b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png" width="1384" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:1384,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:192056,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190953373?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ia-b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 424w, https://substackcdn.com/image/fetch/$s_!Ia-b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 848w, https://substackcdn.com/image/fetch/$s_!Ia-b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 1272w, https://substackcdn.com/image/fetch/$s_!Ia-b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ae072ea-9c72-4bf9-acf1-36c041e5edd9_1384x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The program fails because it specifically attempts to deduct 50 tokens from your <code>Available Balance</code>, which is mathematically zero. Your 100 tokens are still stuck in the <code>Pending Balance</code> ciphertext.</p><h5>The Success Case</h5><p>To actually spend these received funds, you must explicitly instruct the Token-2022 program to mathematically merge the two ciphertexts. This is done via the <code>ApplyPendingBalance</code> instruction.</p><blockquote><p><em><strong>Note:</strong></em> If you pass your Token Account address directly to this command, the CLI will hallucinate a fake Associated Token Account and throw an <code>Account not found</code> error. You must either pass the Mint address</p></blockquote><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;eb029c79-2bae-46ec-9e46-c34293a53e8d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token apply-pending-balance &lt;MINT_ADDRESS&gt;</code></pre></div><p>The Token-2022 takes the current <code>pending_balance</code> ciphertext, homomorphically adds it to the <code>available_balance</code> ciphertext, overwrites the state, and resets the pending balance to zero.</p><p>Now that the sweep is complete, our 100 tokens have safely moved into our <code>Available Balance</code>. If we run the exact same transfer command again</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;65cc36ba-ea37-459b-86f6-d0edae46a1e9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token transfer &lt;MINT_ACCOUNT&gt; 50 &lt;BOB_TOKEN_ACCOUNT&gt; --confidential</code></pre></div><p><em>Output</em></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!M0Dk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!M0Dk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 424w, https://substackcdn.com/image/fetch/$s_!M0Dk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 848w, https://substackcdn.com/image/fetch/$s_!M0Dk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 1272w, https://substackcdn.com/image/fetch/$s_!M0Dk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!M0Dk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png" width="1384" height="154" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:154,&quot;width&quot;:1384,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:129234,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190953373?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!M0Dk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 424w, https://substackcdn.com/image/fetch/$s_!M0Dk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 848w, https://substackcdn.com/image/fetch/$s_!M0Dk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 1272w, https://substackcdn.com/image/fetch/$s_!M0Dk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb64f438-d4fd-4370-a736-d89e0103f002_1384x154.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2><strong>The Withdraw Instruction - Exiting the Shielded State</strong></h2><p>Finally, what happens when a user wants to exit the shielded ecosystem? Perhaps you want to view your raw balance on a standard block explorer, or you need to interact with legacy Solana infrastructure that doesn&#8217;t support the <code>ConfidentialTransfer</code> extension.</p><p>You must transition the state from encrypted back to public using the <code>Withdraw</code> instruction.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;3773d98a-7076-423e-858c-072f9c763ca3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token withdraw-confidential-tokens &lt;MINT_ADDRESS&gt; 100
</code></pre></div><p><em>Output after running the display command</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lcXX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lcXX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 424w, https://substackcdn.com/image/fetch/$s_!lcXX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 848w, https://substackcdn.com/image/fetch/$s_!lcXX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 1272w, https://substackcdn.com/image/fetch/$s_!lcXX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lcXX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png" width="1384" height="806" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:806,&quot;width&quot;:1384,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:515584,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190953373?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lcXX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 424w, https://substackcdn.com/image/fetch/$s_!lcXX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 848w, https://substackcdn.com/image/fetch/$s_!lcXX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 1272w, https://substackcdn.com/image/fetch/$s_!lcXX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed936b0c-6ad5-47ec-bc9a-c7b0dbe55f92_1384x806.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This instruction is the exact inverse of the <code>Deposit</code> we ran in preiously. When you deposit, you are just encrypting a public number. But when you withdraw, you are asking the public network to subtract a specific amount from an <em>encrypted</em> balance.</p><p>To execute this, your local client must do the heavy lifting:</p><ol><li><p><strong>It uses your local ElGamal private key to decrypt your current </strong><code>available_balance</code><strong>.</strong></p></li><li><p><strong>It verifies you actually have &gt;= 100 tokens.</strong></p></li><li><p><strong>It generates a ZK proof mathematically verifying that subtracting 100 from the ciphertext is valid and will not result in a negative number.</strong></p></li></ol><p>It sends this proof to the network. The Token-2022 program verifies the proof via the ZK Token Proof program, homomorphically subtracts the ciphertext of 100 from your <code>available_balance</code>, and explicitly adds the raw integer <code>100</code> back to your public  amount field. Your funds are now public again.</p><h4><strong>A Brief Note on Smart Contract Integration</strong></h4><blockquote><p><strong>Note:</strong> If you are writing a smart contract, say, a payment router or an automated market maker, and it receives a confidential transfer, the contract cannot immediately route those tokens in the next instruction. The funds will be stuck in the contract&#8217;s <code>pending_balance</code>. Your program <em>must</em> execute a Cross-Program Invocation to trigger <code>ApplyPendingBalance</code> to merge the ciphertexts <em>before</em> attempting any outgoing transfers. Forgetting this single CPI will cause every incoming transaction to your protocol to hard-fail.</p></blockquote><h2><strong>Summary</strong></h2><p>The Token-2022 Confidential Transfer extension is a masterclass in natively integrating zero-knowledge cryptography into a high-performance runtime.</p><p>By pushing the encryption directly to the L1 standard:</p><ol><li><p><strong>Liquidity Remains Unified:</strong> Developers no longer have to bridge assets to isolated Layer-2 ZK-rollups to achieve privacy.</p></li><li><p><strong>State is Protected:</strong> The <code>pending</code> vs. <code>available</code> balance split brilliantly neutralizes the cryptographic griefing vectors that plague other privacy networks.</p></li><li><p><strong>Math Replaces Trust:</strong> Linear homomorphism ensures the base layer can blindly but flawlessly execute accounting without ever compromising user data.</p></li></ol>]]></content:encoded></item><item><title><![CDATA[Engineering Native Yield: A Deep Dive into Solana's Interest-Bearing Mint Extension]]></title><description><![CDATA[In the traditional DeFi stack, engineering a yield-bearing asset has always been a battle against state bloat and compute costs.]]></description><link>https://andreyobruchkov1996.substack.com/p/engineering-native-yield-a-deep-dive</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/engineering-native-yield-a-deep-dive</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Mon, 30 Mar 2026 19:20:21 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CRbG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>In the traditional DeFi stack, engineering a yield-bearing asset has always been a battle against state bloat and compute costs. If you saw systems around EVM rebasing tokens, think of <code>stETH</code> as example, you know the architectural headaches they introduce. To reflect accrued interest, an EVM protocol must either continuously update storage slots across thousands of user accounts (which is expensive) or rely on a complex &#8220;shares-to-balances&#8221; mathematical abstraction under the hood, forcing every integrating smart contract to implement custom logic just to read a user&#8217;s true balance.</p><p>Solana&#8217;s Token-2022 standard completely flips this paradigm. Instead of fighting state mutations, it eliminates them entirely.</p><p>With the introduction of the <strong>Interest-Bearing Mint Extension</strong>, Solana introduces the concept of &#8220;Cosmetic Yield.&#8221; At the state level, the raw token <code>amount</code> residing in a user&#8217;s Token Account <strong>never</strong><em> </em><strong>changes</strong>. There are no rebasing events, no automated yield-harvesting cranks, and no state bloat. Instead, the accrued interest is calculated mathematically and continuously at the UI and RPC layer based on the network&#8217;s clock. The protocol simply applies a continuous compounding formula: </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;A = Pe^{rt}&quot;,&quot;id&quot;:&quot;ZYNWDYHLTG&quot;}" data-component-name="LatexBlockToDOM"></div><p>to the static principal.</p><p>Just as Transfer Hooks modularized transfer logic by pushing it to external programs, the Interest-Bearing extension modularizes yield by separating the <em><strong>configuration</strong></em> of the interest rate from the <em><strong>execution</strong></em> of the math. It relies heavily on a strict separation between execution logic in <a href="https://github.com/solana-program/token-2022/blob/main/program/src/extension/interest_bearing_mint/processor.rs">processor.rs</a> and pure mathematical interfaces <a href="https://github.com/solana-program/token-2022/blob/639f59602c8ef348c909176e58115c0645068acb/interface/src/extension/interest_bearing_mint/mod.rs#L85">mod.rs</a>.</p><p>In this post, we are going to get under the hood of the <code>InterestBearingConfig</code>. We will break down the Rust implementation, explore the continuous compounding math, and look at the architecture.</p></blockquote><div><hr></div><h2>The Architecture of Cosmetic Yield vs. State Mutation</h2><p>To truly appreciate the engineering behind Solana&#8217;s Interest-Bearing extension, we first have to look at the problem it solves. In blockchain architecture, state is expensive, and mutating state across thousands of accounts simultaneously is almost impossible.</p><h4><strong>The EVM Bottleneck: Rebasing and Shares</strong></h4><p>If you look at the successful yield-bearing assets on the EVM for example <a href="https://docs.lido.fi/#what-is-lido">Lido&#8217;s </a><code>stETH</code><a href="https://docs.lido.fi/#what-is-lido"> </a><code>Token</code> it rely on state mutation. Because an ERC-20 <code>balanceOf</code> function needs to return a constantly growing number to reflect yield, EVM developers had to get creative.</p><p>They introduced the &#8220;shares&#8221; model. Under the hood, your wallet doesn&#8217;t actually hold <code>stETH</code>,  it holds a static number of <em>shares</em> of the total pool. When you call <code>balanceOf(user)</code>, the smart contract executes a calculation: </p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QIi0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QIi0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 424w, https://substackcdn.com/image/fetch/$s_!QIi0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 848w, https://substackcdn.com/image/fetch/$s_!QIi0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 1272w, https://substackcdn.com/image/fetch/$s_!QIi0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QIi0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png" width="1456" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42534,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190747549?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QIi0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 424w, https://substackcdn.com/image/fetch/$s_!QIi0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 848w, https://substackcdn.com/image/fetch/$s_!QIi0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 1272w, https://substackcdn.com/image/fetch/$s_!QIi0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1f87893-1eb5-40bc-872f-4cafcdc0f50c_1886x298.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">https://lido.fi/how-lido-works/rewards-and-penalties</figcaption></figure></div><p>Here is where the architectural bottleneck occurs. While <code>UserShares</code> and <code>TotalShares</code> remain relatively static, the <em>actual</em> amount of ETH backing the protocol on the Beacon Chain is constantly growing. However, the Ethereum execution layer smart contract is entirely isolated from this growth. The variable <code>TotalPooledEther</code> is just a static number sitting in contract storage. It cannot update itself.</p><p>For users to actually see their <code>stETH</code> balances increase, an external Oracle (a bot) must explicitly broadcast a transaction to the smart contract every period of time. This transaction calls a function that overwrites the global <code>TotalPooledEther</code> state variable to reflect the new yield. Because that global variable was changed, the math equation now outputs a higher number for every single user. But getting that higher number required an active transaction, gas fees, and a global state write.</p><h4><strong>Solana&#8217;s Token-2022 Approach: Computation over State</strong></h4><p>Solana&#8217;s architecture prioritizes parallel execution and isolated account state. Forcing a global state update to reflect yield across all token holders would create massive write-locks, crippling network throughput.</p><p>Token-2022 solves this by shifting the burden entirely from <em><strong>state storage</strong></em> to <em><strong>computation</strong></em>. This introduces the concept of <strong>Cosmetic Yield</strong>.</p><p>When an interest-bearing mint is initialized, the raw <code>amount</code> stored in a user&#8217;s Token Account (a simple <code>u64</code> integer) represents only the <strong>principal</strong>: the exact original baseline number of tokens the user received. <strong>This raw principal amount never changes, even as the tokens earn yield.</strong> Instead of mutating the state to add the newly accumulated profit, the yield is calculated dynamically. No automated cranks, no oracle updates, and no global state mutations are required to generate this yield. The state remains static, while the math handles the growth.</p><h4><strong>The </strong><code>uiAmount</code><strong> Abstraction (Where the Yield Lives)</strong></h4><p>If the raw balance doesn&#8217;t change, and no oracle is updating a global variable, how does the user actually see their money growing?</p><p>This is where the <code>uiAmount</code> comes in. In the Token-2022 standard, the interface makes a strict distinction between the raw <code>amount</code> (the state) and the <code>uiAmount</code> (the human-readable representation).</p><p>Instead of relying on an external party to push updates, the Solana Token-2022 program pulls its truth from the <strong>Network Clock</strong>. The Mint account holds an <code>InterestBearingConfig</code>, which stores the initialization timestamp and the current interest rate in basis points.</p><p>Because the network clock (<code>Clock::get()?.unix_timestamp</code>) inherently advances with every single block, the input to the continuous compounding formula</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;A = Pe^{rt}&quot;,&quot;id&quot;:&quot;HZZXMLXRPJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>is always growing automatically.</p><p>When a dApp, wallet, or RPC node queries a user&#8217;s balance, it doesn&#8217;t just read the static integer. Instead, it calls the program&#8217;s <code>amount_to_ui_amount</code> instruction (will be explained shortly). This routes the query directly through the extension&#8217;s math engine. The program fetches the current network timestamp, calculates the accrued interest purely in memory, and returns the combined total as a formatted string (the <code>uiAmount</code>). The protocol completely bypasses the need for state-mutating transactions; the state remains static, while the math handles the growth.</p><h2><strong>The Math: Continuous Compounding On-Chain</strong></h2><p>Now that we understand the yield is calculated dynamically rather than stored in state, we have to ask: <em>how is the math actually executed?</em> If you have written smart contracts before, you know that floating-point math and exponential calculations are generally the enemy of blockchain development. They consume massive amounts of compute units and introduce rounding errors that can easily be exploited. Yet, the Token-2022 program handles this seamlessly using continuous compounding.</p><h4><strong>The Formula</strong></h4><p>The core mathematical engine of the Interest-Bearing extension relies on the standard continuous compound interest formula:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;A = Pe^{rt}&quot;,&quot;id&quot;:&quot;CEJYLMQORS&quot;}" data-component-name="LatexBlockToDOM"></div><p></p><p>Here is exactly how these variables map to the Solana Token-2022 architecture:</p><ul><li><p><strong>A (Final Amount):</strong> The final <code>ui_amount</code> that includes the accrued yield.</p></li><li><p><strong>P (Principal):</strong> The raw <code>amount</code> of tokens stored in the user&#8217;s Token Account (which never mutates).</p></li><li><p><strong>e (Euler&#8217;s Number):</strong> The mathematical constant <strong>~2.71828.</strong></p></li><li><p><strong>r (Rate):</strong> The annualized interest rate. On-chain, this is configured in <strong>basis points</strong> (where 10,000 bps = 100%) and converted to a decimal during the calculation.</p></li><li><p><strong>t (Time):</strong> The time elapsed in years. Solana tracks this natively using the Unix timestamp in seconds, which the math engine divides by 31,556,736 (the number of seconds in a 365.24-day year).</p></li></ul><p>reference can be found <strong><a href="https://github.com/solana-program/token-2022/blob/main/clients/js-legacy/src/actions/amountToUiAmount.ts">here</a>.</strong></p><h4><strong>The State Variables: Handling Rate Changes Without State Mutations</strong></h4><p>A major architectural challenge arises when the <code>rate_authority</code> decides to change the interest rate. If a token yielded 5% for the first six months, and the authority updates it to 10%, the protocol cannot retroactively apply 10% to the entire lifespan of the token.</p><p>If this were an EVM rebasing token, changing the rate would require a massive, O(N) state-mutating &#8220;checkpoint&#8221; transaction to lock in every individual user&#8217;s balance at the 5% rate before starting the 10% epoch.</p><p><strong>Token-2022</strong> solves this elegantly within the <code>InterestBearingConfig</code> Type-Length-Value (TLV) layout stored directly on the Mint account. It tracks three crucial variables (<strong>will be explained more in the next section</strong>):</p><ul><li><p><strong>current_rate</strong>: The active interest rate in basis points.</p></li><li><p><strong>last_update_timestamp</strong>: The exact Unix timestamp of the last rate change.</p></li><li><p><strong>pre_update_average_rate</strong>: The time-weighted average rate of all compounding periods prior to the last update.</p></li></ul><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;24cccb59-e0b1-4fa3-94e3-c21f28e3df43&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">fn process_update_rate(
    program_id: &amp;Pubkey,
    accounts: &amp;[AccountInfo],
    new_rate: &amp;BasisPoints,
) -&gt; ProgramResult {
    // ... Authority validation checks omitted for brevity

    let clock = Clock::get()?;
    
    // Calculate the historical average up to this exact second
    let new_average_rate = extension
        .time_weighted_average_rate(clock.unix_timestamp)
        .ok_or(TokenError::Overflow)?;
        
    // Lock in the historical average
    extension.pre_update_average_rate = new_average_rate.into();
    
    // Move the checkpoint timestamp forward to the current block
    extension.last_update_timestamp = clock.unix_timestamp.into();
    
    // Set the new active rate
    extension.current_rate = *new_rate;
    Ok(())
}</code></pre></div><p>Notice what is completely absent from this function: <strong>there is no iteration over token accounts.</strong> Zero user balances are touched.</p><p>The protocol simply calculates the new <code>pre_update_average_rate</code> up to the current block time, moves the <code>last_update_timestamp</code> forward, and sets the new <code>current_rate</code> inside the Mint&#8217;s TLV extension. When a user&#8217;s balance is calculated tomorrow, the math engine simply uses the locked historical average for the past, and the new active rate for the present.</p><p>Full code can be found <a href="https://github.com/solana-program/token-2022/blob/main/program/src/extension/interest_bearing_mint/processor.rs">here</a>.</p><h3><strong>The Checkpoint in Practice</strong></h3><p>Now that we have seen the raw Rust logic, let&#8217;s watch the Token-2022 program execute this checkpointing in real-time. To see exactly how the state mutates without touching user accounts, we are going to use the Solana CLI to create a token, inspect its abstracted state, and trigger an interest rate change.</p><h4><strong>Creating the Mint</strong></h4><p>First, we initialize a Token-2022 mint and tell the program to allocate space for the <code>InterestBearingConfig</code> TLV extension. We are going to set the initial rate to <strong>5%</strong> (which is <code>500</code> basis points).</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;1e81fb80-59ae-4303-a0d9-6f3b8f67e257&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token --interest-rate 500
</code></pre></div><p><em>Output:</em></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;5388cd29-e6bd-4a43-92b5-20d10c6822d9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Creating token Dmcd... (your new mint address)
Signature: 4j...
</code></pre></div><h4><strong>Inspecting the Abstracted State</strong></h4><p>Now that the mint is live, we can look at the exact bytes stored in the account data using the <code>display</code> command.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;79477953-5cf4-4aa9-b228-5baeb171cf46&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">
spl-token display &lt;YOUR_MINT_ADDRESS&gt;</code></pre></div><p>If you look closely at the output, the CLI decodes the <code>Interest-bearing</code> extension block. It will look like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;c1bd9461-8246-4e7c-83b5-2b234a83c982&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Extensions
  Interest-bearing:
    Current rate: 500bps
    Average rate: 500bps
    Rate authority: &lt;YOUR_WALLET&gt;
</code></pre></div><p>Because the token was just created, the <code>Average rate</code> and <code>Current rate</code> are identical (500 bps). However, the CLI is actually hiding some crucial data here for the sake of a clean UI. Under the hood, the raw Rust struct is also storing the initialization and update timestamps. The math engine knows that, as of this exact moment, the token earns 5%.</p><h4><strong>Triggering the Checkpoint (Updating the Rate)</strong></h4><p>Let&#8217;s say six months pass. We want to update the rate to <strong>10%</strong> (1000 basis points). As the Rate Authority, we fire the <code>set-interest-rate</code> command:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;12f5741a-07e3-4169-9e8a-c9fac5594613&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">spl-token set-interest-rate &lt;YOUR_MINT_ADDRESS&gt; 1000
</code></pre></div><p>If we immediately run <code>spl-token display &lt;YOUR_MINT_ADDRESS&gt;</code> again, we will see the Token-2022 math engine has safely mutated the Mint&#8217;s state:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;356cf85d-b732-4d79-9ffe-8c3818063f3e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Extensions
  Interest-bearing:
    Current rate: 1000bps
    Average rate: 500bps
    Rate authority: L6h2ugreR3oNmKtBCpcsMPSLg3BwLR31fSPB1iU76mp</code></pre></div><p>Look at what just happened: <strong>Zero user accounts were touched.</strong> Instead, the Mint locked its own history.</p><p>Behind the scenes, the program updated its hidden <code>last_update_timestamp</code> to the current network clock. When a frontend UI or an RPC node calculates a user&#8217;s yield tomorrow, it simply breaks the continuous compounding formula into two distinct chunks:</p><ol><li><p><strong>The Past:</strong> It looks at the time between the creation and the hidden update timestamp, applying the <code>Average rate</code> (1 year  5%).</p></li><li><p><strong>The Present:</strong> It looks at the time <em>since</em> the last update up to the current block time, applying the new <code>Current rate</code> (0.5 years 10%).</p></li></ol><h4><strong>Peeking Under the Hood: Finding the Hidden Timestamps</strong></h4><p>While the CLI conveniently formats the output for a clean terminal experience, as developers, we need to know what the math engine is actually reading.</p><p>If you take that same Mint address, plug it into a Solana block explorer, and look at the raw on-chain data, the UI abstraction falls away. You will see the complete JSON representation of the <code>interestBearingConfig</code> extension exactly as it lives on the network:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CRbG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CRbG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 424w, https://substackcdn.com/image/fetch/$s_!CRbG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 848w, https://substackcdn.com/image/fetch/$s_!CRbG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 1272w, https://substackcdn.com/image/fetch/$s_!CRbG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CRbG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png" width="1456" height="606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:606,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:127316,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190747549?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CRbG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 424w, https://substackcdn.com/image/fetch/$s_!CRbG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 848w, https://substackcdn.com/image/fetch/$s_!CRbG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 1272w, https://substackcdn.com/image/fetch/$s_!CRbG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31b74cd5-3f0c-4d35-84f8-14c26c9299ce_1984x826.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here, the hidden architecture is fully exposed. You can clearly see the <code>initializationTimestamp</code> and the <code>lastUpdateTimestamp</code> that the continuous compounding formula inherently relies on to slice the yield curve into the &#8220;Past&#8221; and the &#8220;Present&#8221; without touching a single user&#8217;s token account.</p><h2><strong>The Abstraction Layer and the BPF Math Engine</strong></h2><p>The entire architecture of the Interest-Bearing extension hinges on a strict separation between state storage and presentation. In the Token-2022 standard, there is a hard boundary between the <code>amount</code> (the raw <code>u64</code> integer stored in the database) and the <code>uiAmount</code> (the mathematically yielded float presented to the user).</p><p>If a developer or indexer bypasses this abstraction layer and attempts to read the raw state directly, the architecture breaks down visually. Look at this screenshot from a popular Solana block explorer attempting to render the exact Mint we just updated:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fTbb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fTbb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 424w, https://substackcdn.com/image/fetch/$s_!fTbb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 848w, https://substackcdn.com/image/fetch/$s_!fTbb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 1272w, https://substackcdn.com/image/fetch/$s_!fTbb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fTbb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png" width="986" height="530" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:530,&quot;width&quot;:986,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:61621,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/190747549?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fTbb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 424w, https://substackcdn.com/image/fetch/$s_!fTbb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 848w, https://substackcdn.com/image/fetch/$s_!fTbb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 1272w, https://substackcdn.com/image/fetch/$s_!fTbb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09bc07c2-5c36-44b8-aeef-41a2c0848671_986x530.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h4><strong>The Raw Data Trap</strong></h4><p>When we used the CLI to update the rate, we passed the integer <code>1000</code>. In the Rust engine, this strictly represents <strong>1000 basis points (bps)</strong>, which equates to exactly <strong>10%</strong>.</p><p>Because the block explorer bypassed the program&#8217;s native math engine and directly deserialized the raw <code>currentRate</code> bytes from the Mint&#8217;s TLV extension and appended a <code>%</code> sign to the raw integer. The result is a frontend mistakenly advertising a 1,000% APY.</p><p>This highlights a critical architectural constraint: <strong>You cannot natively trust the raw state of an Interest-Bearing token.</strong></p><p>To safely bridge the gap between the static state and the continuous compounding math, the Solana developers introduced two specific, native instructions to the Token-2022 program:</p><ol><li><p><code>AmountToUiAmount</code></p></li><li><p><code>UiAmountToAmount</code></p></li></ol><p>Instead of forcing RPC nodes or other programs to import a math library and attempt to recreate the </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;A = Pe^{rt}&quot;,&quot;id&quot;:&quot;WWKSBKHDUU&quot;}" data-component-name="LatexBlockToDOM"></div><p>formula locally, these instructions allow you to query the program directly. You pass the raw <code>u64</code> amount into the instruction, and the Token-2022 program executes the exponential math using the Mint&#8217;s exact timestamps, returning the formatted string.</p><h4><strong>Querying the Math Engine</strong></h4><p>To safely bridge the gap between the static state and the continuous compounding math, the Solana developers built the yield conversion directly into the RPC layer.</p><p>Before we can query a balance, we actually need a token account with some principal in it. Let&#8217;s use the CLI to create an account for our Mint and give ourselves <strong>100</strong> tokens.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;23c8ff1c-fdc6-46c7-9b81-e3cd56613bf5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">
spl-token create-account &lt;YOUR_MINT_ADDRESS&gt;
spl-token mint &lt;YOUR_MINT_ADDRESS&gt; 100</code></pre></div><p>Now, let&#8217;s imagine a few hours pass. To see the yield in action, we can query that specific token account using a standard <code>curl</code> request to the <code>getTokenAccountBalance</code> RPC method:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;bash&quot;,&quot;nodeId&quot;:&quot;7091a728-4c4b-4b95-bba1-29dfae806f7f&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-bash">curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTokenAccountBalance",
  "params": [
    "&lt;YOUR_TOKEN_ACCOUNT_ADDRESS&gt;"
  ]
}'
</code></pre></div><p>When the RPC node receives this request, it detects the Interest-Bearing extension, runs the exponential math using the Mint&#8217;s hidden timestamps, and returns both numbers simultaneously:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;json&quot;,&quot;nodeId&quot;:&quot;104456c3-3073-407e-bb82-35e6ee6f6823&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-json">
{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "3.1.10",
      "slot": 448286815
    },
    "value": {
      "amount": "100000000000",   &lt;-- The raw principal (100 tokens with 9 decimals)
      "decimals": 9,
      "uiAmount": 100.009224922,  &lt;-- The mathematically yielded balance!
      "uiAmountString": "100.009224922"
    }
  },
  "id": 1
}</code></pre></div><p>Notice what is completely absent from this JSON response? The interest rate itself.</p><p>You don&#8217;t have to fetch the <code>1000</code> basis points from the Mint, and you don&#8217;t have to manually calculate whether that means 10% or 1,000%. The RPC node handled the basis-point conversion, fetched the network clock, and ran the </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;A = Pe^{rt}&quot;,&quot;id&quot;:&quot;UBUKRZGGEZ&quot;}" data-component-name="LatexBlockToDOM"></div><p>formula entirely under the hood. The <code>uiAmount</code> already contains the correct yield.</p><p>By relying on the RPC (or the native on-chain CPI) to execute the math, your protocol becomes completely immune to the kind of frontend parsing bugs we saw on the block explorer. The hard boundary is perfectly clear: the <code>amount</code> stays static, but the <code>uiAmount</code> safely delivers the continuously compounding truth.</p><h2>Summary</h2><p>To wrap up, the Token-2022 Interest-Bearing extension represents a fundamental architectural shift from <strong>state storage to computation</strong>:</p><ul><li><p><strong>No State Bloat:</strong> Unlike EVM rebasing tokens that require constant Oracle transactions to mutate global state, Solana calculates yield dynamically using continuous compounding </p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;A = Pe^{rt}&quot;,&quot;id&quot;:&quot;NLAHVSERCJ&quot;}" data-component-name="LatexBlockToDOM"></div><p>driven by the network clock.</p></li><li><p><strong>Checkpointing the Mint:</strong> Interest rate changes lock historical averages directly into the Mint&#8217;s TLV configuration. This completely bypasses the need to iterate over or mutate individual user accounts.</p></li><li><p><strong>The Abstraction Layer:</strong> A user&#8217;s raw <code>amount</code> is strictly static principal. To get the true yielded balance, developers cannot just read the raw state, they must query the program&#8217;s math engine directly via RPC or on-chain CPI.</p></li></ul><p>Ultimately, it is an elegant, compute-heavy solution to EVM state bloat, paving the way for highly scalable, natively yielding assets.</p>]]></content:encoded></item><item><title><![CDATA[Transfer Hooks on Solana: Anchor 0.31 & Token-2022]]></title><description><![CDATA[The Solana Token-2022 standard introduces a paradigm shift in how developers manage digital assets.]]></description><link>https://andreyobruchkov1996.substack.com/p/transfer-hooks-on-solana-anchor-031</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/transfer-hooks-on-solana-anchor-031</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Wed, 18 Mar 2026 20:19:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!J7pq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>The Solana Token-2022 standard introduces a paradigm shift in how developers manage digital assets. Among these extensions, <strong>Transfer Hooks</strong> represent the most powerful capability, allowing protocols to execute custom, synchronous on-chain logic every time a token is transferred. This enables use cases ranging from compliance enforcement and dynamic royalties to sophisticated whitelisting systems.</p><p>Implementing Transfer Hooks within the modern Solana toolchain requires a precise understanding of cross-program invocations (CPIs) and account resolution. In this comprehensive guide, we will architect a production-ready Transfer Hook that enforces a maximum transfer limit constraint. We will cover environment configuration, the mechanics of the <code>ExtraAccountMetaList</code>, the optimal Rust implementation bypassing common CPI pitfalls, and a robust TypeScript integration test suite. </p><p>In this blog post we will understand the hands-on side of the hooks extension and how to use it.</p></blockquote><div><hr></div><h2>Overview</h2><p>The Solana Token-2022 standard and the Transfer Hook Interface introduce the ability to create Mint Accounts that execute custom instruction logic on every token transfer. This architectural shift unlocks a massive design space for token transfers, enabling use cases such as:</p><ul><li><p>Enforcing NFT royalties.</p></li><li><p>Building black or white lists for wallets authorized to receive tokens.</p></li><li><p>Implementing dynamic, custom fees on token transfers.</p></li><li><p>Creating custom token transfer events.</p></li><li><p>Tracking on-chain statistics over your token transfers.</p></li></ul><p>To achieve this, developers must build a program that implements the Transfer Hook Interface and initialize a Mint Account with the Transfer Hook extension enabled. For every token transfer involving tokens from the Mint Account, the Token Extensions program makes a Cross Program Invocation (CPI) to execute an instruction on the Transfer Hook program.</p><p>Crucially, when the Token Extensions program CPIs to a Transfer Hook program, all accounts from the initial transfer are converted to <strong>read-only accounts</strong>. This means the signer privileges of the sender do not extend to the Transfer Hook program a deliberate design decision implemented at the runtime level to prevent the malicious use of Transfer Hook programs.</p><h3>Transfer Hook Interface Overview</h3><p>The Transfer Hook Interface provides a standardized method for developers to implement custom instruction logic that is executed on every token transfer for a specific Mint Account. The interface specifies the following core instructions:</p><ul><li><p><strong>Execute:</strong> The primary instruction that the Token Extension program invokes on every token transfer.</p></li><li><p><strong>InitializeExtraAccountMetaList (optional):</strong> Creates an account that stores a list of additional accounts required by the custom Execute instruction.</p></li><li><p><strong>UpdateExtraAccountMetaList (optional):</strong> Updates the list of additional accounts by overwriting the existing list.</p></li></ul><p>While it is technically not required to implement the <code>InitializeExtraAccountMetaList</code> instruction directly through the interface (the account can be created by any custom instruction), the Program Derived Address (<strong>PDA</strong>) for the account must be derived deterministically using specific seeds:</p><ol><li><p>The hardcoded string <code>"extra-account-metas"</code></p></li><li><p>The Mint Account address</p></li><li><p>The Transfer Hook program ID</p></li></ol><p>By storing the extra accounts required by the <code>Execute</code> instruction in this predefined PDA, these accounts can be automatically resolved and added to a token transfer instruction from the client.</p><h2>Environment and Toolchain Configuration</h2><p>Recent updates to the Solana ecosystem require strict version alignment between the Anchor framework, the Agave toolchain, and the SPL interface crates. To establish a stable build environment for Token-2022 development, In this blog we will use:</p><ul><li><p><strong>Agave CLI:</strong> <code>v3.1.9</code>.</p></li><li><p><strong>Anchor CLI:</strong> <code>0.31.1</code>.</p></li><li><p><strong>SPL Interface Crates:</strong> <code>0.10.0</code></p></li></ul><p><strong>Cargo.toml example:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;toml&quot;,&quot;nodeId&quot;:&quot;5dc0e8cc-be0a-4f36-bfe7-f4350b5f4add&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-toml">...
[dependencies]
anchor-lang = "0.31.1"
anchor-spl = { version = "0.31.1", features = ["token_2022", "token_2022_extensions"] }

spl-transfer-hook-interface = "0.10.0"
spl-tlv-account-resolution = "0.10.0"</code></pre></div><h2>The Architecture of Account Resolution</h2><p>To truly master Transfer Hooks, developers must first understand the fundamental limitation of Solana&#8217;s execution model: <strong>A program cannot read from or write to an account that was not explicitly passed to it in the transaction.</strong> In a standard token transfer, the client (or wallet) only passes four core accounts: the Source, the Mint, the Destination, and the Owner. However, custom Transfer Hook will likely need access to other accounts, such as a global configuration state, a whitelist directory or any other use-case.</p><p>Since the client initiating the transfer has no knowledge of your protocol&#8217;s internal architecture, how does the Token-2022 program know which additional accounts to fetch and pass to your Hook?</p><p>The answer lies in <strong>On-Chain Account Resolution</strong> via the <code>ExtraAccountMetaList</code>.</p><h3>The <code>ExtraAccountMetaList</code> PDA</h3><p>The <code>ExtraAccountMetaList</code> is a stateless, on-chain roadmap. It is a Program Derived Address (<strong>PDA</strong>) deterministically generated using three seeds:</p><ol><li><p>The hardcoded byte string <code>b"extra-account-metas"</code></p></li><li><p>The SPL Token Mint address</p></li><li><p>Your Transfer Hook Program ID</p></li></ol><p>Before executing the cross-program invocation (CPI) to your Hook, the Token-2022 program queries this specific PDA. If the PDA exists, Token-2022 reads its data, unpacks the roadmap, and automatically appends the requested accounts to the transfer transaction.</p><h4>Defining the Architecture in Anchor</h4><p>To set this up safely, we first define the validation constraints for the initialization context.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;79eeffa1-2250-4ad3-acef-f4581d145139&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">#[derive(Accounts)]
pub struct InitializeExtraAccountMetaList&lt;'info&gt; {
    #[account(
        init,
        seeds = [b"extra-account-metas", mint.key().as_ref()],
        bump,
        payer = payer,
        // 8 bytes for the Anchor discriminator + 64 bytes for the TLV data
        space = 8 + 64 
    )]
    /// CHECK: PDA storing the metadata roadmap for the Token-2022 program
    pub extra_metas_account: AccountInfo&lt;'info&gt;,
    /// CHECK: The SPL Token Mint
    pub mint: AccountInfo&lt;'info&gt;,
    #[account(mut)]
    pub payer: Signer&lt;'info&gt;,
    pub system_program: Program&lt;'info, System&gt;,
}</code></pre></div><blockquote><p><strong>Note:</strong> Notice how the PDA is derived using the exact seeds required by the Token-2022 specification that was defined above.</p></blockquote><h4>How Anchor Fulfills the Specification</h4><p>If you look at the <code>seeds = [...]</code> array in the code above, you only see two items explicitly written:</p><ol><li><p><code>b"extra-account-metas"</code> (The hardcoded byte string).</p></li><li><p><code>mint.key().as_ref()</code> (The Mint&#8217;s Public Key).</p></li></ol><p><strong>So, where is the third seed (the Program ID)?</strong></p><p>This is where the Anchor framework steps in. Whenever you use the <br><code>seeds = [...]</code> macro in an Anchor <code>#[account(...)]</code> constraint, Anchor automatically appends the <strong>currently executing Program ID</strong> as the final seed during compilation. Full docs <a href="https://www.anchor-lang.com/docs/basics/pda">here</a>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;3967c057-1dac-4b52-b285-918c2b2db005&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">// Under the hood, Anchor translates that macro into the raw Solana runtime function: 
Pubkey::find_program_address(&amp;[b"extra-account-metas", mint.key().as_ref()], program_id)</code></pre></div><h4>The <code>initialize_extra_account_meta_list</code> Function</h4><p>With the account structure validated, developers must implement the <code>initialize_extra_account_meta_list</code> instruction. This function&#8217;s sole responsibility is to pack the <code>ExtraAccountMeta</code> configurations into the PDA.</p><p>Instead of hardcoding static addresses (which breaks composability), professional implementations use <strong>Seed-Based Resolution</strong>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;68be597d-6561-4f33-b68b-53cbc52c51c2&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">    pub fn initialize_extra_account_meta_list(ctx: Context&lt;InitializeExtraAccountMetaList&gt;) -&gt; Result&lt;()&gt; {
        let account_metas = vec![
            // Instructs Token-2022 to derive the required PDA using dynamic seeds
            spl_tlv_account_resolution::account::ExtraAccountMeta::new_with_seeds(
                &amp;[
                    spl_tlv_account_resolution::seeds::Seed::Literal { bytes: b"extra-account-metas".to_vec() },
                    spl_tlv_account_resolution::seeds::Seed::AccountKey { index: 1 }, // Resolves using the Mint (Index 1)
                ],
                false, // is_signer
                false, // is_writable
            )?
        ];

        let mut data = ctx.accounts.extra_metas_account.try_borrow_mut_data()?;
        
        // Formats the data as a Type-Length-Value (TLV) structure for the Execute instruction
        spl_tlv_account_resolution::state::ExtraAccountMetaList::init::&lt;
            spl_transfer_hook_interface::instruction::ExecuteInstruction
        &gt;(&amp;mut data, &amp;account_metas)?;

        msg!("Extra Account Meta List Initialized");
        Ok(())
    }</code></pre></div><p>By utilizing <code>ExtraAccountMeta::new_with_seeds()</code>, you instruct the Token-2022 program to dynamically derive the required accounts at the exact moment of transfer. </p><p>For example, by specifying <code>Seed::AccountKey { index: 1 }</code>, you are telling the Token-2022 runtime: <em>&#8220;To find the extra account my Hook needs, derive a PDA using the Public Key of the account currently sitting at Index 1 of this transaction (the Mint field in the </em>InitializeExtraAccountMetaList <em>struct ).&#8221;</em></p><p>This dynamic resolution mechanism ensures that your Transfer Hook remains entirely deterministic, allowing the Token-2022 program to bridge the gap between standard wallet transfers and complex, multi-account protocol logic.</p><p>The line:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;ca2a94a6-c49a-4452-84a6-f66b9e18ebe1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">let mut data = ctx.accounts.extra_metas_account.try_borrow_mut_data()?;</code></pre></div><p>In Solana, an account's data payload is protected behind a <code>RefCell</code>. By calling <code>try_borrow_mut_data()?</code>, we bypass standard Anchor struct serialization to safely borrow a mutable reference to the PDA's underlying raw byte slice (<code>&amp;mut [u8]</code>). We need this raw access because Token-2022 expects a very specific memory layout that we are about to construct manually.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;b1ec5f87-dc95-49eb-8a3d-510f8400a0b3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">spl_tlv_account_resolution::state::ExtraAccountMetaList::init::&lt;
    spl_transfer_hook_interface::instruction::ExecuteInstruction
&gt;(&amp;mut data, &amp;account_metas)?;</code></pre></div><p>Once we have our raw byte slice (<code>&amp;mut data</code>), we invoke the <code>init</code> function from the <code>spl_tlv_account_resolution</code> crate. This function formats your <code>account_metas</code> vector and writes it into the PDA using a <strong>Type-Length-Value (TLV)</strong> encoding scheme. TLV is the backbone of Token-2022, allowing multiple extensions and state variables to be packed sequentially into a single account without data collisions.</p><h3>On-Chain Implementation</h3><p>With the account resolution architecture defined, we can move on to the core logic. Writing a Transfer Hook requires stepping slightly outside the standard Anchor boundaries.</p><p>Below is the complete implementation for our Transfer Hook. This program initializes the metadata roadmap and enforces a strict volume constraint on transfers.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;rust&quot;,&quot;nodeId&quot;:&quot;808854a1-cfc7-4fcc-8acb-db3d129e2b51&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-rust">use anchor_lang::prelude::*;
use spl_transfer_hook_interface::instruction::TransferHookInstruction;

declare_id!("&lt;YourGeneratedAddress&gt;");

#[program]
pub mod transfer_hook_project {
    use super::*;

    /// Initializes the PDA that Token-2022 reads to resolve extra accounts.
    pub fn initialize_extra_account_meta_list(ctx: Context&lt;InitializeExtraAccountMetaList&gt;) -&gt; Result&lt;()&gt; {
        let account_metas = vec![
            // Instructs Token-2022 to derive the required PDA using dynamic seeds
            spl_tlv_account_resolution::account::ExtraAccountMeta::new_with_seeds(
                &amp;[
                    spl_tlv_account_resolution::seeds::Seed::Literal { bytes: b"extra-account-metas".to_vec() },
                    spl_tlv_account_resolution::seeds::Seed::AccountKey { index: 1 }, // Resolves using the Mint (Index 1)
                ],
                false, // is_signer
                false, // is_writable
            )?
        ];

        let mut data = ctx.accounts.extra_metas_account.try_borrow_mut_data()?;
        
        spl_tlv_account_resolution::state::ExtraAccountMetaList::init::&lt;
            spl_transfer_hook_interface::instruction::ExecuteInstruction
        &gt;(&amp;mut data, &amp;account_metas)?;

        msg!("Extra Account Meta List Initialized");
        Ok(())
    }

    pub fn transfer_hook(_ctx: Context&lt;TransferHook&gt;, amount: u64) -&gt; Result&lt;()&gt; {
        msg!("Hook executed for transfer amount: {}", amount);
        
        if amount &gt; 1_000_000_000_000 { 
            return err!(ErrorCode::TransferVolumeExceeded);
        }
        Ok(())
    }

    pub fn fallback&lt;'info&gt;(program_id: &amp;Pubkey, accounts: &amp;'info [AccountInfo&lt;'info&gt;], data: &amp;[u8]) -&gt; Result&lt;()&gt; {
        let instruction = TransferHookInstruction::unpack(data)?;
        match instruction {
            TransferHookInstruction::Execute { amount } =&gt; {
                let amount_bytes = amount.to_le_bytes();
                
                // Routes execution to the Anchor instruction without requiring a standard CPI
                __private::__global::transfer_hook(program_id, accounts, &amp;amount_bytes)
            }
            _ =&gt; return Err(ProgramError::InvalidInstructionData.into()),
        }
    }
}

#[derive(Accounts)]
pub struct InitializeExtraAccountMetaList&lt;'info&gt; {
    #[account(
        init,
        seeds = [b"extra-account-metas", mint.key().as_ref()],
        bump,
        payer = payer,
        space = 8 + 64 
    )]
    pub extra_metas_account: AccountInfo&lt;'info&gt;,
    pub mint: AccountInfo&lt;'info&gt;,
    #[account(mut)]
    pub payer: Signer&lt;'info&gt;,
    pub system_program: Program&lt;'info, System&gt;,
}

#[derive(Accounts)]
pub struct TransferHook&lt;'info&gt; {
    /// CHECK: Source Token Account
    pub source: AccountInfo&lt;'info&gt;,
    /// CHECK: The SPL Token Mint
    pub mint: AccountInfo&lt;'info&gt;,
    /// CHECK: Destination Token Account
    pub destination: AccountInfo&lt;'info&gt;,
    /// CHECK: Owner of the Source Account
    pub owner: AccountInfo&lt;'info&gt;, 
    /// CHECK: The resolved ExtraAccountMetaList PDA
    #[account(
        seeds = [b"extra-account-metas", mint.key().as_ref()], 
        bump
    )]
    pub extra_metas_account: UncheckedAccount&lt;'info&gt;,
}

#[error_code]
pub enum ErrorCode {
    #[msg("Transfer amount exceeds the maximum protocol limit.")]
    TransferVolumeExceeded,
}</code></pre></div><h4>Architectural Deep Dive: The Discriminator Mismatch</h4><p>If you look closely at the code above, a glaring architectural question arises: <em>If our core logic lives inside an Anchor function, why doesn&#8217;t the Token-2022 program call it directly? Why do we need a </em><code>fallback</code><em> function to intercept the call? Furthermore, why not just name our function </em><code>execute</code><em> to match the Token-2022 standard?</em></p><p>The answer lies in how the Solana Virtual Machine (SVM) and the Anchor framework handle instruction routing via <strong>Instruction Discriminators</strong>.</p><p>When the Anchor framework compiles your Rust code, it automatically generates an 8-byte discriminator for every function to route incoming transactions. To prevent naming collisions between different programs, Anchor automatically injects a namespace prefix before hashing the string. For a standard instruction, it uses the prefix <code>global:</code>.</p><p>If you were to name your function <code>pub fn execute(...)</code>, Anchor would generate the discriminator by taking the SHA-256 hash of exactly this string: <code>"global:execute"</code>.</p><p>However, the Token-2022 program has no knowledge of your Anchor program&#8217;s internal namespaces. When a token transfer fires, Token-2022 blindly issues a CPI using the standardized SPL interface discriminator, which is generated by hashing this specific string: <code>"spl_transfer_hook_interface:execute"</code>.</p><p>Because the input strings are different, the resulting 8-byte hashes are entirely different. If the Token-2022 program hits your standard Anchor instruction, Anchor will immediately reject the transaction as an &#8220;Invalid Instruction,&#8221; even if the human-readable function names are identical.</p><h4>The Fallback Router &amp; Context Isolation</h4><p>To bridge this gap, we must implement the <code>fallback</code> function. In Anchor, the <code>fallback</code> acts as a catch-all safety net. If an incoming instruction does not match any known Anchor discriminator, the framework passes the raw transaction data directly to the fallback.</p><p>Inside this fallback, we manually assume control of the routing logic:</p><ol><li><p>We unpack the raw SPL instruction data.</p></li><li><p>We cryptographically verify it is the standard <code>Execute</code> command.</p></li><li><p>We dynamically route the execution to our actual <code>transfer_hook</code> function.</p></li></ol><p><strong>The Context Routing Trick:</strong> Notice the <code>__private::__global::transfer_hook</code> call inside the fallback.</p><p>A common critical mistake developers make here is attempting to use a standard Solana <code>invoke</code> (a self-CPI) to jump from the fallback to the target function. This will cause an immediate crash (<code>Account missing error</code>) because the Token-2022 runtime actively strips out non-essential accounts (like the System Program or your Program ID) from the transfer payload to maintain strict security boundaries.</p><p>By utilizing <code>__private::__global</code>, we tap into an internal Anchor dispatcher. It takes the raw accounts passed by Token-2022 and feeds them directly into our Anchor instruction <em>within the exact same call stack</em>. This completely bypasses the SVM&#8217;s CPI limitations, allowing your program to retain all of Anchor&#8217;s robust security constraints without the overhead or limitations of a cross-program invocation.</p><h3>Deploying to Solana</h3><p>Deploying a Token-2022 Transfer Hook to Solana's Devnet requires a few specific configuration shifts to ensure both the Anchor framework and the SPL CLI are targeting the correct RPC cluster.</p><h4>Configure and Deploy the Anchor Program</h4><p>First, open your <code>Anchor.toml</code> file at the root of your project. You need to switch the provider cluster from <code>Localnet</code> to <code>Devnet</code> and explicitly declare your program ID for the new network.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;toml&quot;,&quot;nodeId&quot;:&quot;501dec7a-ef3e-4954-9121-097dd67391df&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-toml">[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json" # Ensure this points to your active deployment keypair

[programs.devnet]
transfer_hook_project = "&lt;YOUR_NEW_PROGRAM_ID&gt;"</code></pre></div><p>Once configured and <a href="https://faucet.solana.com/">funded</a> with Devnet SOL, run a clean build and deploy the program to the live network:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;882efa96-a4fc-4744-ad03-ac0f4ff1bf93&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">anchor clean
anchor build
anchor deploy</code></pre></div><p><strong>you should see something like:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!myno!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!myno!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 424w, https://substackcdn.com/image/fetch/$s_!myno!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 848w, https://substackcdn.com/image/fetch/$s_!myno!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 1272w, https://substackcdn.com/image/fetch/$s_!myno!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!myno!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png" width="1248" height="144" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:144,&quot;width&quot;:1248,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36149,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!myno!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 424w, https://substackcdn.com/image/fetch/$s_!myno!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 848w, https://substackcdn.com/image/fetch/$s_!myno!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 1272w, https://substackcdn.com/image/fetch/$s_!myno!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06701eee-7b4d-4ae8-b3cf-a05379129533_1248x144.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong>on Explorer:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J7pq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J7pq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 424w, https://substackcdn.com/image/fetch/$s_!J7pq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 848w, https://substackcdn.com/image/fetch/$s_!J7pq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 1272w, https://substackcdn.com/image/fetch/$s_!J7pq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J7pq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png" width="1456" height="650" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:650,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:333618,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J7pq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 424w, https://substackcdn.com/image/fetch/$s_!J7pq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 848w, https://substackcdn.com/image/fetch/$s_!J7pq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 1272w, https://substackcdn.com/image/fetch/$s_!J7pq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8154e1c1-2c3b-44e7-9ac6-5b27d5babe5f_2932x1308.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/tx/UEEbnRtutGDkkhKddQfKL2MaDevqvgmHbs3xMPx4RF97uiDnKFmAuf5K3BjwL4YK9EXUv7zDEXjVBwwmw1mZvBM?cluster=devnet</figcaption></figure></div><h4>Provisioning the Token on Devnet</h4><p>Once the Anchor program is successfully deployed, we can leverage the <code>spl-token</code> CLI to create the actual Token-2022 Mint, attach the hook, and prepare our test accounts.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;dc373815-3351-4c6f-8e6e-0064f3ae722e&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"># Create the Token-2022 Mint and attach the newly deployed Hook
spl-token create-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --transfer-hook &lt;YOUR_PROGRAM_ID&gt; --url devnet

# output:
# Creating token 71H52RsWiLbMXQuBtH5a1hLSzUnbTgiDjFhktuJrJT1m under program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

# Address:  71H52RsWiLbMXQuBtH5a1hLSzUnbTgiDjFhktuJrJT1m
# Decimals:  9

# Signature: 2rtXMX3TaXZoWMJqcYj7vwhtWng3eLUBfDHbG4N45QxnQnuwrNReKZgPoLPNfHKBXofuoH1daFAP73FoWJAFRWJV</code></pre></div><p><strong>on Explorer:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TM2f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TM2f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 424w, https://substackcdn.com/image/fetch/$s_!TM2f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 848w, https://substackcdn.com/image/fetch/$s_!TM2f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 1272w, https://substackcdn.com/image/fetch/$s_!TM2f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TM2f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png" width="1456" height="250" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:250,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:140877,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TM2f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 424w, https://substackcdn.com/image/fetch/$s_!TM2f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 848w, https://substackcdn.com/image/fetch/$s_!TM2f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 1272w, https://substackcdn.com/image/fetch/$s_!TM2f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24e6924-07e3-453c-8531-152e3c3eaad7_3004x516.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">https://solscan.io/token/71H52RsWiLbMXQuBtH5a1hLSzUnbTgiDjFhktuJrJT1m?cluster=devnet</figcaption></figure></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;e75ed345-9c09-4142-99e3-2acdf99ffacc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext"># Create your Devnet Associated Token Account (ATA)
spl-token create-account &lt;TOKEN_MINT_ADDRESS&gt; --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --url devnet

# Mint the initial test supply to your wallet
spl-token mint &lt;TOKEN_MINT_ADDRESS&gt; 2000 --url devnet</code></pre></div><blockquote><p><strong>Note:</strong> Now your account should have 2000 of the minted token</p></blockquote><h4>Initialize the PDA Roadmap</h4><p>At this point, the Token-2022 program is aware of your Hook, but any token transfer will still fail because the <code>ExtraAccountMetaList</code> PDA has not been instantiated on Devnet. You must invoke your <code>initialize_extra_account_meta_list</code> instruction to write the resolution map to the blockchain.</p><p>Since this logic lives entirely inside your custom Anchor program, the standard SPL CLI cannot do this for you. We bridge this gap with a standalone TypeScript script:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;64a84763-b19c-4f32-9025-bd2b7fe67c29&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { TransferHookProject } from "../target/types/transfer_hook_project";

async function main() {
    // 1. Force the provider to use Devnet
    process.env.ANCHOR_PROVIDER_URL = "https://api.devnet.solana.com";

    const provider = anchor.AnchorProvider.env();
    anchor.setProvider(provider);

    // 2. Define your specific Devnet addresses
    const programId = new anchor.web3.PublicKey("&lt;ProgramID&gt;");
    const mintAddress = new anchor.web3.PublicKey("&lt;MintID&gt;");

    console.log("Connecting to Devnet as:", provider.wallet.publicKey.toBase58());

    // 3. Load the workspace program
    const program = anchor.workspace.TransferHookProject as Program&lt;TransferHookProject&gt;;

    // 4. Deterministically derive the PDA using the exact Token-2022 seeds
    // Only for the print, not needed for the program its done automatically.
    const [extraMetasPDA] = anchor.web3.PublicKey.findProgramAddressSync(
        [Buffer.from("extra-account-metas"), mintAddress.toBuffer()],
        programId
    );

    console.log("Target Mint:", mintAddress.toBase58());
    console.log("Derived Metadata PDA:", extraMetasPDA.toBase58());
    console.log("Broadcasting initialization transaction...");

    // 5. Execute the Anchor instruction
    try {
        const tx = await program.methods
            .initializeExtraAccountMetaList()
            .accounts({
                mint: mintAddress,
            })
            .rpc();

        console.log("\nSuccess! The ExtraAccountMetaList roadmap is live on Devnet.");
        console.log(`View on Explorer: https://explorer.solana.com/tx/${tx}?cluster=devnet`);
    } catch (error) {
        console.error("Transaction failed:", error);
    }
}

main();</code></pre></div><p><strong>The output should look like:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!p-HJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!p-HJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 424w, https://substackcdn.com/image/fetch/$s_!p-HJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 848w, https://substackcdn.com/image/fetch/$s_!p-HJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 1272w, https://substackcdn.com/image/fetch/$s_!p-HJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!p-HJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png" width="1456" height="197" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:197,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82816,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!p-HJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 424w, https://substackcdn.com/image/fetch/$s_!p-HJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 848w, https://substackcdn.com/image/fetch/$s_!p-HJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 1272w, https://substackcdn.com/image/fetch/$s_!p-HJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22cee625-f369-49a5-b783-b6e57d3b1bed_1698x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong>On Explorer:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MQkv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MQkv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 424w, https://substackcdn.com/image/fetch/$s_!MQkv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 848w, https://substackcdn.com/image/fetch/$s_!MQkv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 1272w, https://substackcdn.com/image/fetch/$s_!MQkv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MQkv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png" width="1456" height="334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:334,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:138841,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MQkv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 424w, https://substackcdn.com/image/fetch/$s_!MQkv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 848w, https://substackcdn.com/image/fetch/$s_!MQkv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 1272w, https://substackcdn.com/image/fetch/$s_!MQkv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbde0461-6ef1-4c0f-953f-5ec75d4f902d_2978x684.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">https://solscan.io/account/FnchjEr1FuknaNoEpTEvKkH96mcBu8L1VGYig3oCc2WW?cluster=devnet</figcaption></figure></div><h4>Live Network Validation</h4><p>With the PDA roadmap live on Devnet, your token is now 100% locked into the Transfer Hook. The modern <code>spl-token</code> CLI natively supports Transfer Hooks and Account Resolution. Let&#8217;s trigger a transfer that violates our 1,000-token limit:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;a31316fa-5911-437f-8bc7-9e39fe2740ce&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">spl-token transfer &lt;TOKEN_MINT_ADDRESS&gt; 1500 &lt;DESTINATION_WALLET&gt; --fund-recipient --url devnet</code></pre></div><p><strong>The Error:</strong></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;7094fc1f-7b75-41b1-b088-6bc4306885e3&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">Error: Client(Error { request: Some(SendTransaction), kind: RpcError(RpcResponseError { code: -32002, message: "Transaction simulation failed: Error processing Instruction 1: custom program error: 0x1770", data: SendTransactionPreflightFailure(RpcSimulateTransactionResult { err: Some(UiTransactionError(InstructionError(1, Custom(6000)))), logs: Some([
  ...
  "Program log: Instruction: TransferHook", 
  "Program log: Hook triggered for amount: 1500000000000", 
  "Program log: AnchorError thrown in programs/transfer-hook-project/src/lib.rs:36. Error Code: AmountTooBig. Error Number: 6000. Error Message: Transfer amount exceeds the blog's demo limit.", 
  "Program AgfLd9BmQcLZj9h13gYTsAgWdEnXKAArnRypuPd9hCub failed: custom program error: 0x1770"
])</code></pre></div><p><em>The Hook perfectly intercepted the transfer and threw our custom Anchor error!</em></p><p>Now, let&#8217;s drop the volume to 500 tokens to satisfy the protocol constraint:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;d0198a2a-5bac-4258-b894-6fe3896598da&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">spl-token transfer &lt;TOKEN_MINT_ADDRESS&gt; 500 &lt;DESTINATION_WALLET&gt; --fund-recipient --url devnet</code></pre></div><p><strong>The output</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NyHn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NyHn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 424w, https://substackcdn.com/image/fetch/$s_!NyHn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 848w, https://substackcdn.com/image/fetch/$s_!NyHn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 1272w, https://substackcdn.com/image/fetch/$s_!NyHn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NyHn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png" width="1456" height="211" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:211,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71641,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NyHn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 424w, https://substackcdn.com/image/fetch/$s_!NyHn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 848w, https://substackcdn.com/image/fetch/$s_!NyHn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 1272w, https://substackcdn.com/image/fetch/$s_!NyHn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b60111d-36c4-41e4-a282-1e14c70647a6_1516x220.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p><strong>On Explorer:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bisf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bisf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 424w, https://substackcdn.com/image/fetch/$s_!bisf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 848w, https://substackcdn.com/image/fetch/$s_!bisf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 1272w, https://substackcdn.com/image/fetch/$s_!bisf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bisf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png" width="1456" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:301442,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/188620851?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!bisf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 424w, https://substackcdn.com/image/fetch/$s_!bisf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 848w, https://substackcdn.com/image/fetch/$s_!bisf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 1272w, https://substackcdn.com/image/fetch/$s_!bisf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d7d3051-2186-47a4-82fb-0225d321a476_2962x1004.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Summary</h2><p>If you followed along and triggered those final Devnet transactions, you just successfully intercepted a native token transfer and injected your own custom business logic directly into the execution flow.</p><p>Token Extensions have fundamentally changed what is possible on Solana. By mastering Account Resolution via the <code>ExtraAccountMetaList</code> and properly isolating Anchor execution contexts to bypass the CPI discriminator mismatch, you can implement complex, synchronous state updates with high cryptographic security and minimal compute overhead.</p>]]></content:encoded></item><item><title><![CDATA[Solana Token-2022 Transfer Hooks and the Fee-on-Transfer Extension]]></title><description><![CDATA[Token-2022 adds extensibility to SPL tokens, but not all extensibility lives at the same layer.]]></description><link>https://andreyobruchkov1996.substack.com/p/solana-token-2022-transfer-hooks</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/solana-token-2022-transfer-hooks</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Thu, 12 Mar 2026 15:49:52 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!iIdb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Token-2022 adds extensibility to SPL tokens, but not all extensibility lives at the same layer.</p><p>Two features are often discussed together: the <strong>fee-on-transfer extension</strong> and <strong>transfer hooks</strong>. Both are triggered during a token transfer, and both influence what happens when value moves between accounts.</p><p>This similarity is misleading.</p><p>These features exist at <strong>different layers of the system</strong>, solve different classes of problems, and are subject to very different execution constraints. Treating them as interchangeable leads to designs that work in theory, pass simulation, and then fail at runtime.</p><p>In this post, we&#8217;ll start with the fee-on-transfer extension, how it works and why it exists. Then move to transfer hooks, and finally compare what is and is not possible with each.</p></blockquote><div><hr></div><h2>Fee-on-Transfer Extension: Defining Token Economics</h2><p>When people hear &#8220;fee on transfer,&#8221; they usually imagine a separate program that runs during a transfer and skims a percentage to a treasury.</p><p>Token-2022 does it differently.</p><p>The <strong>Fee-on-Transfer extension</strong> makes the fee part of the token program&#8217;s <em>native transfer logic</em>. In other words, it changes what a &#8220;transfer&#8221; <em>means</em> for that mint, at the <strong>protocol level.</strong></p><h3>What it is</h3><p>A Token-2022 mint can be configured with:</p><ul><li><p>a <strong>fee rate</strong> (basis points), and</p></li><li><p>a <strong>maximum fee cap</strong> (so big transfers don&#8217;t create absurd fees).</p></li></ul><p>Once enabled, every transfer of that mint automatically applies the fee inside the Token-2022 program.</p><h3>What actually happens during a transfer</h3><p>Suppose Alice sends Bob 100 tokens and the mint charges a fee.</p><p>At execution time, Token-2022:</p><ol><li><p><strong>calculates the fee</strong> from the mint&#8217;s fee config,</p></li><li><p><strong>deducts it from the transferred amount</strong>, and</p></li><li><p><strong>withholds the fee</strong> rather than immediately paying it out to an arbitrary address.</p></li></ol><p>That &#8220;withheld&#8221; detail matters a lot.</p><h3>Where the fees go</h3><p>With transfer fees enabled, the fee amount is tracked in Token-2022 extension state (commonly recorded on the recipient token account via the transfer-fee amount extension). The recipient can&#8217;t spend those withheld tokens as part of their normal balance.</p><p>Later, a <strong>withdraw authority</strong> (configured on the mint) can withdraw accumulated withheld fees.</p><p>There&#8217;s also a practical mechanism to <strong>harvest</strong> withheld fees from many token accounts into the mint before withdrawing, which matters when a token has lots of holders.</p><h3>Why this extension exists</h3><p>This extension exists because &#8220;transfer tax&#8221; is not just <em>extra logic</em> it is <strong>token economics</strong>:</p><ul><li><p>It changes <strong>how much value moves</strong> in the transfer.</p></li><li><p>It must be <strong>deterministic</strong> (same inputs &#8594; same result).</p></li><li><p>It must be enforced <strong>atomically</strong> by the token program, without relying on external programs, CPIs, wallets, or simulation quirks.</p></li></ul><p>So you can think of it like this:</p><blockquote><p><strong>Fee-on-transfer is not a hook.</strong><br>It&#8217;s a rule baked into the token program&#8217;s definition of a transfer.</p></blockquote><h3>Example</h3><p><strong>lets set the environment</strong></p><pre><code>solana config set --url devnet</code></pre><p><strong>Create two wallets (sender + recipient) and fund them</strong></p><pre><code>solana-keygen new --no-bip39-passphrase -o ./alice.json
solana-keygen new --no-bip39-passphrase -o ./bob.json
solana airdrop 1 -k ./alice.json
solana airdrop 1 -k ./bob.json</code></pre><p><strong>Create a Token-2022 mint with transfer fees:</strong></p><blockquote><p><strong>Important:</strong> <code>spl-token</code> flag names can vary by version. First, confirm what your CLI supports:</p><pre><code><code>spl-token create-token --help | grep -i fee</code></code></pre><p>Most recent CLIs support enabling fees at mint creation using the flags for:</p><ul><li><p><code>--transfer-fee-basis-points</code></p></li><li><p><code>--transfer-fee-maximum-fee</code></p></li></ul></blockquote><p><strong>set the primary private key</strong></p><pre><code>solana config set --keypair ./alice.json</code></pre><p>Example: <strong>1% fee</strong>, max fee <strong>5 tokens</strong>, <strong>0 decimals</strong> (so it&#8217;s easy to see):</p><pre><code>MINT=$(spl-token create-token \
  --program-2022 \
  --decimals 0 \
  --transfer-fee-basis-points 100 \
  --transfer-fee-maximum-fee 5 \
  | awk '/Creating token/ {print $3}')

echo "MINT=$MINT"

# Output:
# MINT=9ncw8asA7P79w7vJ3XbUFWW4EKy7cSYRVdhp9xjXb9cf</code></pre><p><strong>On explorer it will look like this:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!iIdb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!iIdb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 424w, https://substackcdn.com/image/fetch/$s_!iIdb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 848w, https://substackcdn.com/image/fetch/$s_!iIdb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 1272w, https://substackcdn.com/image/fetch/$s_!iIdb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!iIdb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png" width="1456" height="861" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:861,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:197918,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185860383?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!iIdb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 424w, https://substackcdn.com/image/fetch/$s_!iIdb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 848w, https://substackcdn.com/image/fetch/$s_!iIdb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 1272w, https://substackcdn.com/image/fetch/$s_!iIdb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb788eff1-1ee7-4b13-97fd-232e54f21739_2236x1322.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Note: </strong>Max fee is set to 5 means that the fee will never take more than 5 tokens</p></blockquote><p></p><p><strong>create Alice&#8217;s token account:</strong></p><pre><code>ALICE_ATA=$(spl-token create-account $MINT --program-2022 | awk '/Creating account/ {print $3}')
echo "ALICE_ATA=$ALICE_ATA"

# Output:
# ALICE_ATA=J9V83VXc4H1LcCHu6DjxKLn6tkem5Se4ZYiybLnqoG35</code></pre><p><strong>On explorer it will look like this:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!naqX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!naqX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 424w, https://substackcdn.com/image/fetch/$s_!naqX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 848w, https://substackcdn.com/image/fetch/$s_!naqX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 1272w, https://substackcdn.com/image/fetch/$s_!naqX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!naqX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png" width="1456" height="484" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:484,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:125572,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185860383?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!naqX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 424w, https://substackcdn.com/image/fetch/$s_!naqX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 848w, https://substackcdn.com/image/fetch/$s_!naqX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 1272w, https://substackcdn.com/image/fetch/$s_!naqX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fcbfe12-afde-4559-b78e-5c5d10ba2f70_2236x744.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>create Bob&#8217;s token account</strong></p><pre><code># switch to bob
solana config set --keypair ./bob.json

BOB_ATA=$(spl-token create-account $MINT --program-2022 | awk '/Creating account/ {print $3}')
echo "BOB_ATA=$BOB_ATA"

# Output:
# BOB_ATA=G5DjAMaTSrTajDK3Y9EW1dwSsXz6SUWQdvpu9Ft3CNif

# Then switch back to Alice for minting/transferring:
solana config set --keypair ./alice.json </code></pre><p><strong>On explorer it will look like this:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VN2b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VN2b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 424w, https://substackcdn.com/image/fetch/$s_!VN2b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 848w, https://substackcdn.com/image/fetch/$s_!VN2b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 1272w, https://substackcdn.com/image/fetch/$s_!VN2b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VN2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png" width="1456" height="484" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:484,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:126432,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185860383?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VN2b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 424w, https://substackcdn.com/image/fetch/$s_!VN2b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 848w, https://substackcdn.com/image/fetch/$s_!VN2b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 1272w, https://substackcdn.com/image/fetch/$s_!VN2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa65bee9-f1ee-428b-83da-20b84f47340d_2236x744.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>After you have both ATAs lets mint and transfer:</strong></p><pre><code># Alice mints to herself
spl-token mint $MINT 1000 $ALICE_ATA --program-2022

# Alice transfers to Bob
spl-token transfer $MINT 100 $BOB_PUBKEY \
  --from $ALICE_ATA \
  --fund-recipient \
  --program-2022</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Hbvv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Hbvv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 424w, https://substackcdn.com/image/fetch/$s_!Hbvv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 848w, https://substackcdn.com/image/fetch/$s_!Hbvv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 1272w, https://substackcdn.com/image/fetch/$s_!Hbvv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Hbvv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png" width="1456" height="425" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:425,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:129213,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185860383?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Hbvv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 424w, https://substackcdn.com/image/fetch/$s_!Hbvv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 848w, https://substackcdn.com/image/fetch/$s_!Hbvv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 1272w, https://substackcdn.com/image/fetch/$s_!Hbvv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5230e362-0bc6-4b38-9c20-34eee65a1536_2236x652.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>lets check the balance of Bob to see how much he got</strong></p><pre><code>spl-token balance $MINT \
  --owner $BOB_PUBKEY \
  --program-2022

# Output:
# 99</code></pre><p><strong>On explorer it will look like this:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!sYxE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!sYxE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 424w, https://substackcdn.com/image/fetch/$s_!sYxE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 848w, https://substackcdn.com/image/fetch/$s_!sYxE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 1272w, https://substackcdn.com/image/fetch/$s_!sYxE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!sYxE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png" width="1456" height="503" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:503,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:129003,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185860383?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!sYxE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 424w, https://substackcdn.com/image/fetch/$s_!sYxE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 848w, https://substackcdn.com/image/fetch/$s_!sYxE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 1272w, https://substackcdn.com/image/fetch/$s_!sYxE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5e281a8f-1f1c-430a-9cf7-49b78c8e042a_2236x772.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Transfer Hooks</h2><p>If <strong>Fee-on-Transfer</strong> is a built-in &#8220;tax law&#8221; of the token program, a <strong>Transfer Hook</strong> is a &#8220;custom security checkpoint.&#8221; It allows a token creator to mandate that an external, custom program must approve every single transfer before it can finalize.</p><h3>What it is</h3><p>The Transfer Hook extension allows a Mint to specify a <strong>Program ID</strong> that must be called during any transfer. This essentially turns a standard token into a <strong>programmable asset</strong>.</p><p>When a transfer is initiated:</p><ol><li><p><strong>Token-2022</strong> sees the hook extension on the Mint.</p></li><li><p>It pauses the transfer logic and sends a <strong>Cross-Program Invocation (CPI)</strong> to your custom program.</p></li><li><p>Your hook program runs its logic and returns either a &#8220;Success&#8221; or an &#8220;Error&#8221;.</p></li></ol><h3>Why this is a game-changer</h3><p>Before Token-2022, if you wanted to enforce custom logic you had to &#8220;wrap&#8221; the token in a custom contract or &#8220;freeze&#8221; it and only &#8220;unfreeze&#8221; it via a proxy program. This destroyed <strong>composability</strong>, wallets and DEXs didn&#8217;t know how to talk to your proxy.</p><p>Hooks fix this by putting the logic directly into the standard <code>transfer</code> flow.</p><h3>Execution-Time Side Effects</h3><p>A <strong>Transfer Hook</strong> is Token-2022&#8217;s way to run <em>your program</em> during a token transfer. But the key detail is <strong>who initiates it</strong>:</p><blockquote><p>The user does <strong>not</strong> call your hook program. The <strong>Token-2022 program</strong> calls it as part of processing a transfer.</p></blockquote><p>So instead of &#8220;a token transfer plus my callback&#8221;, the mental model is:</p><blockquote><p><strong>A token transfer is a pipeline</strong>, and one stage of that pipeline can invoke your program.</p></blockquote><h4>What &#8220;hooked&#8221; actually means</h4><p>When a mint has the transfer-hook extension enabled, Token-2022 stores:</p><ul><li><p>the <strong>hook program id</strong></p></li><li><p>and a configuration describing <strong>extra accounts</strong> the hook will need</p></li></ul><p>Then, whenever someone transfers that mint, Token-2022 will:</p><ol><li><p>validate the transfer</p></li><li><p>perform its internal extension logic</p></li><li><p><strong>invoke the hook program</strong> (CPI) with the transfer context</p></li></ol><p>This invocation is automatic and deterministic. If the hook fails, the <strong>transfer fails</strong>.</p><h3>Why hooks exist</h3><p>Transfer hooks are not meant to redefine the economics of the token (that&#8217;s what fee-on-transfer is for). Hooks exist for things that are inherently <strong>conditional</strong> and <strong>policy-driven</strong>, like:</p><p><strong>Gating / compliance:</strong></p><ul><li><p>allowlist / blocklist</p></li><li><p>KYC / region gating</p></li><li><p>&#8220;only transfers to approved destinations&#8221;</p></li><li><p>&#8220;only transfers above/below a threshold&#8221;</p></li></ul><p><strong>Runtime enforcement</strong></p><ul><li><p>pause transfers based on some on-chain flag</p></li><li><p>rate-limit transfers</p></li><li><p>require &#8220;membership&#8221; / &#8220;badge&#8221; / &#8220;NFT ownership&#8221; to receive</p></li></ul><p>The common theme:</p><blockquote><p><strong>Note: </strong>Hooks let you add <em>policy</em> and <em>stateful side effects</em> to transfers.</p></blockquote><h3>The most important constraint</h3><p>When your hook runs, it&#8217;s not a top-level instruction. It&#8217;s running <strong>inside</strong> the Token-2022 transfer instruction. That means two consequences that matter a lot:</p><h4>1) No privilege escalation</h4><p>Accounts passed into your hook have fixed privileges (read-only / writable, signer / non-signer). Your program cannot &#8220;upgrade&#8221; an account&#8217;s permissions mid-flight.<br>If Token-2022 passed an account as read-only, your hook cannot treat it as writable.</p><h4>2) The hook cannot change what the transfer <em>is</em></h4><p>A hook can:</p><ul><li><p>validate</p></li><li><p>reject</p></li><li><p>record</p></li><li><p>perform side effects on explicitly provided accounts</p></li></ul><p>But it cannot retroactively redefine the transfer semantics the way a token-program-level extension can. This is exactly why &#8220;take a fee in the same token inside the hook&#8221; fails in practice.</p><h2>Why Hooks Cannot Implement Token-Level Fees</h2><p>After setting up a hook, the first instinct for many developers is to add a &#8220;<strong>tax</strong>&#8221; or &#8220;<strong>royalty</strong>&#8221; logic. However, if you try to make your hook move tokens to a treasury during a transfer, you will hit two fundamental architectural walls.</p><h3>1. The &#8220;Read-Only&#8221; Wall</h3><p>When the Token-2022 program invokes your hook, it provides the source and destination token accounts. However, for security reasons, these accounts are passed to your hook as <strong>read-only</strong>.</p><ul><li><p><strong>The Constraint:</strong> You cannot modify the data or the balance of a read-only account.</p></li><li><p><strong>The Result:</strong> Since your hook cannot &#8220;write&#8221; to the balances, it cannot deduct a fee.</p></li></ul><h3>2. The Re-entrancy Lock</h3><p>You might think: <em>&#8220;I&#8217;ll just issue a CPI (Cross-Program Invocation) from my hook back to Token-2022 to move the 1% fee.&#8221;</em></p><ul><li><p><strong>The Constraint:</strong> Solana prohibits &#8220;re-entrancy.&#8221; You cannot call the Token-2022 program to move a token while that same Token-2022 program is currently suspended, waiting for your hook to finish.</p></li><li><p><strong>The Result:</strong> The transaction will fail immediately.</p></li></ul><blockquote><p><strong>Note:</strong> In next blog post we will write code and see this in practice</p></blockquote><h2>What Is Possible With Each Mechanism</h2><p>The core difference lies in <strong>state mutation</strong> (writing) versus <strong>state validation</strong> (reading).</p><h3>Modifying the Transfer Amount</h3><ul><li><p><strong>Fee-on-Transfer:</strong> <strong>Yes.</strong> Because this logic lives <em>inside</em> the Token-2022 program, it has the authority to split the transfer amount. If Alice sends 100, the program physically writes 99 to Bob and 1 to a withheld bucket.</p></li><li><p><strong>Transfer Hooks:</strong> <strong>No.</strong> Your hook program is a guest. The accounts it receives are restricted by the caller (Token-2022). To prevent a hook from &#8220;stealing&#8221; tokens, the runtime passes the source and destination accounts as <strong>Read-Only</strong>. You can see the 100 tokens, but you cannot change that number to 99.</p></li></ul><h3>Gating by Identity or Metadata</h3><ul><li><p><strong>Fee-on-Transfer:</strong> <strong>No.</strong> This extension is &#8220;blind.&#8221; It applies the same math to every transfer regardless of who is involved. You cannot say &#8220;charge 1% except for these five wallets&#8221; using only this extension.</p></li><li><p><strong>Transfer Hooks:</strong> <strong>Yes.</strong> This is the hook&#8217;s superpower. Because the hook is a custom program, it can look at a <code>MemberAccount</code> PDA or an external <code>Registry</code> to see if Alice or Bob are authorized. If they aren&#8217;t, it returns an error and the whole transfer is aborted.</p></li></ul><h3>Side-Effects in External Programs</h3><ul><li><p><strong>Fee-on-Transfer:</strong> <strong>No.</strong> It does one thing: math. It cannot ping a different program to update a &#8220;Points&#8221; balance or log a trade on an external leaderboard.</p></li><li><p><strong>Transfer Hooks:</strong> <strong>Yes.</strong> Your hook can be passed <strong>writable extra accounts</strong>. For example, you could pass a <code>UserStats</code> PDA. Every time a user transfers tokens, your hook could increment a &#8220;Total Volume&#8221; counter on that PDA.</p></li></ul><h2>Choosing the Correct Layer</h2><p>The decision tree is simple:</p><ul><li><p><strong>Does it change the math of the transfer?</strong> Use <strong>Fee-on-Transfer</strong>. It is the only way to ensure the fee is deducted atomically and safely at the protocol level.</p></li><li><p><strong>Does it change the permission of the transfer?</strong> Use <strong>Transfer Hooks</strong>. It is the only way to check external state (like a denylist PDA or an NFT) to decide if a transfer should be allowed to happen at all.</p></li></ul><h2>Summary</h2><p>Token-2022 isn&#8217;t just about &#8220;more features&#8221;, it&#8217;s about putting those features at the correct layer of the stack.</p><ul><li><p><strong>Fee-on-Transfer</strong> handles the the math. It ensures your token economics are immutable, deterministic, and enforced natively.</p></li><li><p><strong>Transfer Hooks</strong> handle the the policy. They turn your token into a programmable asset that can react to the world around it.</p></li></ul><p><strong>Coming up next:</strong> In the next blog post, we&#8217;re going to get our hands dirty. We&#8217;ll write a real Transfer Hook in Anchor, deploy it, and see exactly how to pair it with a Fee-on-Transfer mint to build a fully regulated token economy.</p>]]></content:encoded></item><item><title><![CDATA[From Convention to Explicit State: Token-2022 and Metadata Pointers on Solana]]></title><description><![CDATA[Token-2022 did not introduce new token features.]]></description><link>https://andreyobruchkov1996.substack.com/p/from-convention-to-explicit-state</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/from-convention-to-explicit-state</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Thu, 05 Mar 2026 17:55:39 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!wNA-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Token-2022 did not introduce new token features. It introduced a new way for tokens to describe themselves. </p><p>For most of Solana&#8217;s history, SPL Token mints were structurally simple and intentionally rigid. Their layouts were fixed, their semantics minimal, and any additional meaning, metadata, rules, or auxiliary state had to live somewhere else. Metadata was one of the first things to be pushed out, formalized through conventions and external programs such as Metaplex.</p><p>That approach solved coordination, but not discoverability.</p><p>Nothing in a classic SPL Token mint indicates where its metadata lives. The runtime does not enforce a relationship between a mint and any metadata account. The link exists only because clients, wallets, and programs already know how to derive it.</p><p>Token-2022 changes this at the data-model level.</p><p>By introducing extensible mint layouts and Type-Length-Value encoded extensions, Token-2022 allows tokens to declare optional semantics directly inside their own accounts. One of the most important of these extensions is the Metadata Pointer: a field stored in the mint itself that explicitly identifies where the token&#8217;s metadata can be found.</p><p>With this shift, metadata discovery no longer depends on hardcoded derivation rules or mandatory external programs. The mint becomes self-describing, and the relationship between a token and its metadata becomes explicit, inspectable, and forward-compatible.</p><p>This post explores why the Metadata Pointer exists, what architectural problems it solves compared to convention-based metadata systems, and how Token-2022 moves Solana tokens from implicit assumptions toward explicit on-chain state.</p></blockquote><div><hr></div><h2>What is Token2022</h2><p>The original SPL Token program was intentionally minimal. A mint defined supply, decimals, and authorities. Token accounts held balances. The layout of both was fixed, and once an account was created, its structure could not evolve.</p><p>That simplicity was a strength early on, but it also imposed a hard limit: any new behavior had to live <em>outside</em> the token itself.</p><p>As Solana matured, tokens were expected to do more than represent balances. They needed fees, hooks, compliance rules, pausing, interest, metadata, grouping, and custom transfer logic. Since the mint layout could not change, each of these features had to be implemented through separate programs, additional accounts, and ecosystem-level conventions.</p><p>Token-2022 was developed to address this structural limitation.</p><p>Instead of redefining what a token is, Token-2022 extends how token state can be expressed. It introduces a model where mints and token accounts can include <strong>optional, well-scoped extensions</strong> alongside their base state.</p><h3>Extensions as Explicit, Optional State</h3><p>At the core of Token-2022 is the idea that token behavior should be <strong>declared, not inferred</strong>. A token mint or token account still has a base layout, the same fundamental fields that existed before. What changes is that this base state can now be followed by a structured region that holds extension data. Each extension contributes a specific piece of state: transfer rules, fee configuration, metadata references, group membership, or other optional behavior.</p><p>These extensions are:</p><ul><li><p><strong>Explicit</strong>: their presence is encoded directly in the account data</p></li><li><p><strong>Optional</strong>: only the features you enable are stored</p></li><li><p><strong>Composable</strong>: multiple extensions can coexist when compatible</p></li><li><p><strong>Forward-compatible</strong>: unknown extensions can be safely ignored</p></li></ul><h4>Existing Extensions</h4><ul><li><p><strong>TransferFeeConfig:</strong> Includes transfer fee rate info and accompanying authorities to withdraw and set the fee</p></li><li><p><strong>TransferFeeAmount: </strong>Stores transfer fees that have been withheld from token transfers and are pending withdrawal.</p></li><li><p><strong>MintCloseAuthority: </strong>Adds an optional authority that can permanently close the mint account.</p></li><li><p><strong>ConfidentialTransferMint: </strong>Configures a mint for confidential transfers, enabling privacy-preserving token movements.</p></li><li><p><strong>ConfidentialTransferAccount: </strong>Holds per-account state required to participate in confidential transfers.</p></li><li><p><strong>DefaultAccountState: </strong>Specifies the default state (initialized or frozen) for newly created token accounts.</p></li><li><p><strong>ImmutableOwner: </strong>Prevents the token account owner authority from ever being changed.</p></li><li><p><strong>MemoTransfer: </strong>Requires all inbound transfers to include a transaction memo.</p></li><li><p><strong>NonTransferable: </strong>Marks the mint as non-transferable, preventing tokens from being transferred between accounts.</p></li><li><p><strong>InterestBearingConfig: </strong>Enables tokens to accrue interest over time based on a configured rate.</p></li><li><p><strong>CpiGuard: </strong>Restricts privileged token operations from being invoked through cross-program invocations (CPI).</p></li><li><p><strong>PermanentDelegate: </strong>Assigns a permanent delegate with authority over token operations.</p></li><li><p><strong>NonTransferableAccount: </strong>Indicates that a token account belongs to a non-transferable mint.</p></li><li><p><strong>TransferHook: </strong>Requires token transfers to invoke an external program that implements a transfer hook interface.</p></li><li><p><strong>TransferHookAccount: </strong>Marks a token account as belonging to a mint that enforces transfer hooks.</p></li><li><p><strong>ConfidentialTransferFeeConfig: </strong>Configures encrypted transfer fees and the public key used for encryption.</p></li><li><p><strong>ConfidentialTransferFeeAmount: </strong>Stores encrypted transfer fees that have been withheld.</p></li><li><p><strong>MetadataPointer: </strong>Stores a pointer to another account (or the mint itself) that holds token metadata.</p></li><li><p><strong>TokenMetadata: </strong>Stores token metadata directly inside the mint account.</p></li><li><p><strong>GroupPointer: </strong>Stores a pointer to another account that holds token group configuration.</p></li><li><p><strong>TokenGroup: </strong>Defines group-level configuration for a set of related tokens.</p></li><li><p><strong>GroupMemberPointer: </strong>Stores a pointer to another account that holds group membership configuration.</p></li><li><p><strong>TokenGroupMember: </strong>Defines group membership information for a specific token.</p></li><li><p><strong>ConfidentialMintBurn: </strong>Enables confidential minting and burning of tokens.</p></li><li><p><strong>ScaledUiAmount: </strong>Applies a scaling factor to the token&#8217;s UI amount representation.</p></li><li><p><strong>Pausable: </strong>Allows minting, burning, and transferring of tokens to be paused.</p></li><li><p><strong>PausableAccount: </strong>Indicates that a token account belongs to a pausable mint.</p></li></ul><blockquote><p><strong>Note:</strong> In this blog we will talk about <strong>MetadataPointer </strong>and<strong> TokenMetadata</strong></p></blockquote><h3>Why Token-2022 Exists</h3><p>Token-2022 exists because convention does not scale indefinitely.</p><p>Under the original model, advanced token behavior relied on <strong>shared knowledge</strong>:<br>which extra accounts to look for, which programs to trust, which derivation rules to apply, and which assumptions held in practice. None of this was visible from the mint itself.</p><p>Token-2022 shifts that burden into explicit state.</p><p>Instead of asking clients and programs to <em>know</em> how a token behaves, the mint can now <em>declare</em> its behavior. Tools can inspect the account, see which extensions are present, and reason about the token without relying on ecosystem-wide conventions or hardcoded assumptions.</p><h4>Example:</h4><p>The Metadata Pointer extension that we will learn about in this post is one concrete example of this shift.</p><p>It doesn&#8217;t invent metadata. It changes how metadata is <em>located</em> from convention-based discovery to explicit declaration inside the mint itself. To understand why that matters, it&#8217;s important to first understand Token-2022 as an extensibility framework, not as a collection of features.</p><p>With that foundation in place, we can now examine how metadata fits into this model, and why Solana moved from implicit relationships toward explicit, inspectable state.</p><p><strong>Example of token creation without metadata attached:</strong></p><pre><code>spl-token create-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --decimals 9</code></pre><blockquote><p><strong>Note:</strong> the <code>TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb address the program address of token-2022</code></p></blockquote><h2>Metadata Pointer Extension: Making Metadata Location Explicit</h2><p>The Metadata Pointer extension is the simplest possible application of Token-2022&#8217;s extensibility model. It does not define metadata, It does not interpret metadata, It only answers one question:</p><p><strong>Where should metadata for this token be found?</strong></p><p>Under the original SPL Token model, the mint does not reference its metadata in any way. Clients are expected to derive the metadata account using well-known seeds and a known program ID. This works only because every participant already knows the rules.</p><p>The Metadata Pointer extension removes that assumption.</p><p>When enabled, the mint stores a single piece of information: a public key pointing to the account that holds the token&#8217;s metadata. That account may be owned by any program, follow any schema, or even be the mint itself.</p><p>From an architectural perspective, this is a shift from <em>convention-based discovery</em> to <em>explicit declaration</em>. Programs and tools no longer need to guess where metadata lives. They can read the mint account, inspect its extensions, and discover the metadata location directly from on-chain state.</p><p>Importantly, the Metadata Pointer does not enforce correctness. The runtime does not verify that the pointed account actually contains metadata, nor that it conforms to any standard. The pointer only makes the relationship visible and inspectable.</p><h3>Why it was added</h3><p>The Metadata Pointer solves a structural problem rather than a functional one.</p><ul><li><p>It removes hardcoded derivation rules from clients</p></li><li><p>It reduces the number of assumptions required to interpret a token</p></li><li><p>It allows metadata systems to evolve independently</p></li><li><p>It enables metadata to be stored wherever it makes sense for a given use case</p></li></ul><p>Crucially, this does <strong>not</strong> replace existing metadata programs. A pointer can reference a Metaplex metadata account, a custom metadata account, or any future standard.</p><h4>Example:</h4><p>Lets create a new token-2022 and assign it a metadata pointer. You can see how we created a metadata address in the <a href="https://andreyobruchkov1996.substack.com/p/where-token-metadata-lives-on-solana">previous blog</a>.</p><pre><code>spl-token create-token \                                        
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --decimals 9 \
  --metadata-address &lt;METADATA-ADDRESS&gt;</code></pre><p>And with this command we can see that the newly created token supports the metadata pointer:</p><pre><code>spl-token display &lt;MINT-ADDRESS&gt; \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb</code></pre><p>The output should look like that:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wNA-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wNA-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 424w, https://substackcdn.com/image/fetch/$s_!wNA-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 848w, https://substackcdn.com/image/fetch/$s_!wNA-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 1272w, https://substackcdn.com/image/fetch/$s_!wNA-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wNA-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png" width="988" height="346" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:346,&quot;width&quot;:988,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:182630,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185125021?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wNA-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 424w, https://substackcdn.com/image/fetch/$s_!wNA-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 848w, https://substackcdn.com/image/fetch/$s_!wNA-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 1272w, https://substackcdn.com/image/fetch/$s_!wNA-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0ce132a6-9b59-4733-aae6-0dd92adeb128_988x346.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>TokenMetadata Extension: When the Mint Carries the Meaning</h2><p>Metadata Pointer solves discovery by introducing indirection: the mint declares <em>where</em> metadata lives.</p><p>TokenMetadata is the opposite approach.</p><p>Instead of pointing somewhere else, the mint can store metadata <strong>directly inside its own account data</strong> as an extension. There is no separate metadata account to derive, no pointer to resolve, and no external program that must be consulted to retrieve a token&#8217;s human-facing fields.</p><p>From an architectural perspective, TokenMetadata turns metadata from an ecosystem convention into explicit mint state.</p><p>A Token-2022 mint with the TokenMetadata extension can include fields such as:</p><ul><li><p>name</p></li><li><p>symbol</p></li><li><p>URI</p></li></ul><p>These are not &#8220;runtime fields&#8221;. They are still program-defined bytes inside an account. The Solana runtime does not interpret them. But unlike the Metaplex model, the relationship between the token and its metadata is no longer implicit or derived. The metadata is literally part of the mint&#8217;s declared state.</p><h3>What This Enables and What It Trades Off</h3><p>TokenMetadata optimizes for simplicity and locality:</p><ul><li><p>One account read to fetch both mint parameters and metadata</p></li><li><p>No extra accounts to manage</p></li><li><p>No dependency on external derivation rules</p></li></ul><p>But the tradeoff is equally structural:</p><ul><li><p>Metadata now shares the mint&#8217;s lifecycle and authority model</p></li><li><p>Schemas must remain conservative because they live inside the mint</p></li><li><p>Tooling still needs Token-2022 awareness to decode it</p></li></ul><p>This is not a replacement for richer metadata systems. It&#8217;s a native baseline: a minimal on-chain description that can be resolved without conventions.</p><h3>TokenMetadata vs Metadata Pointer</h3><p>These two extensions are not redundant. They are complementary primitives:</p><ul><li><p><strong>MetadataPointer</strong> makes metadata <em>discoverable</em> without assuming where it lives.</p></li><li><p><strong>TokenMetadata</strong> makes metadata <em>local</em> by embedding it directly into the mint.</p></li></ul><p>Token-2022 doesn&#8217;t force a single standard. It provides the ability to express both models explicitly.</p><p>In practice, you&#8217;ll see combinations like:</p><ul><li><p>Pointer &#8594; Metaplex metadata that will provide compatibility with existing ecosystem</p></li><li><p>Pointer &#8594; custom metadata account application-specific semantics</p></li><li><p>Inline TokenMetadata small, native, self-contained metadata</p></li></ul><h3>Example</h3><p>with this next lines it is possible to create a new token-2022 token and enable the <strong>TokenMetadata</strong> extension:</p><pre><code>spl-token create-token \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --decimals 9 \
  --enable-metadata</code></pre><p>After that the attachment of the small native metadata looks like that:</p><pre><code>spl-token initialize-metadata &lt;MINT-ADDRESS&gt; \                  
  "Example Token" \
  "EXMPL" \                            
  "https://example.com/metadata.json" \                   
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb</code></pre><p>and the output is:</p><pre><code>spl-token display &lt;MINT-ADDRESS&gt; \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb </code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EJ6s!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EJ6s!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 424w, https://substackcdn.com/image/fetch/$s_!EJ6s!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 848w, https://substackcdn.com/image/fetch/$s_!EJ6s!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 1272w, https://substackcdn.com/image/fetch/$s_!EJ6s!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EJ6s!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png" width="984" height="536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:536,&quot;width&quot;:984,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:272780,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/185125021?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EJ6s!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 424w, https://substackcdn.com/image/fetch/$s_!EJ6s!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 848w, https://substackcdn.com/image/fetch/$s_!EJ6s!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 1272w, https://substackcdn.com/image/fetch/$s_!EJ6s!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1ec13421-b1da-4026-8e43-7d2465a2ced6_984x536.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Note:</strong> When a Token-2022 mint uses <strong>inline metadata</strong> via the <code>TokenMetadata</code> extension, it will often also include a <strong>MetadataPointer</strong> extension that points back to the mint itself. This is intentional.</p></blockquote><h2>Tooling Reality: Explicit State vs Ecosystem Support</h2><p>Token-2022 makes token semantics explicit on-chain, but explicit state does not automatically imply universal tooling support. At the protocol level, MetadataPointer and TokenMetadata are just bytes in an account owned by the Token-2022 program. The runtime enforces ownership and access, but it does not interpret extensions, nor does it require clients to understand them.</p><p>As a result, much of today&#8217;s tooling still treats Token-2022 mints as generic SPL tokens. Explorers may show the correct owning program while omitting extension data entirely. CLI tools can initialize metadata, but often cannot decode or display it back to the user. This is not a contradiction in the design. It is an expected phase of adoption.</p><p>Token-2022 intentionally prioritizes backward compatibility. Programs and clients that do not understand extensions must be able to safely ignore them. Full visibility requires extension-aware tooling that explicitly opts into parsing Token-2022&#8217;s extensible layouts.</p><p>In practice, this means that inspecting metadata pointers or inline metadata often requires programmatic clients rather than generic interfaces.</p><p><strong>The important distinction is this:</strong><br>the data exists, the relationship is explicit, and the semantics are declared on-chain even if the surrounding ecosystem has not yet caught up.</p><h2>Summary</h2><p>Token-2022 represents a shift in how token meaning is expressed on Solana.</p><p>The original SPL Token model relied heavily on convention. Relationships between a mint and its metadata were not encoded on-chain, but reconstructed by clients that already knew where to look and how to derive it. This worked, but only as long as those assumptions were shared universally. Token-2022 moves those assumptions into explicit state.</p><p>By introducing extensible mint layouts, tokens can now declare optional semantics directly inside their own accounts. Extensions such as <strong>MetadataPointer</strong> and <strong>TokenMetadata</strong> do not change what metadata is, but they change how it is discovered and interpreted.</p><ul><li><p><strong>MetadataPointer</strong> makes metadata location explicit, replacing convention-based discovery with on-chain declaration.</p></li><li><p><strong>TokenMetadata</strong> allows small, native metadata to live directly inside the mint, removing indirection entirely. The pointer and is created is simply resolves to the mint itself, unifying discovery regardless of where metadata is stored.</p></li></ul><p>The Solana runtime still does not understand metadata. Nothing is enforced globally. Meaning remains program-defined. What has changed is <strong>where that meaning is declared.</strong></p><p>Token-2022 does not eliminate ecosystem standards like Metaplex. It makes them optional, inspectable, and composable. Tokens no longer depend on shared off-chain knowledge to describe themselves. They can state their semantics explicitly, in their own on-chain data.</p>]]></content:encoded></item><item><title><![CDATA[Where Token Metadata Lives on Solana: From Convention to Explicit Data]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/where-token-metadata-lives-on-solana</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/where-token-metadata-lives-on-solana</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Mon, 19 Jan 2026 23:40:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!j0ej!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>In the <a href="https://andreyobruchkov1996.substack.com/p/spl-token-program-architecture-a">previous post</a>, we examined how the SPL Token program models fungible assets on Solana using accounts, authorities, and runtime enforcement.</p><p>One important question was left intentionally unanswered:</p><p><strong>Where does token metadata live, and how does the runtime know how to find it?</strong></p><p>On Solana, accounts do not carry intrinsic meaning. They are byte arrays interpreted by programs. Any relationship between accounts must be explicitly encoded, derived, or conventionally agreed upon.</p><p>Token metadata is a perfect example of this design tension.</p><p>Early Solana token designs relied on well-known conventions such as Program-Derived Addresses, to associate metadata with a mint. This approach worked, but it also introduced implicit assumptions that the runtime itself does not enforce.</p><p>In this post, we&#8217;ll examine how token metadata is modeled on Solana, why it was originally externalized into separate accounts, and why newer designs are moving toward explicit, data-driven references instead of convention.</p><p>The goal is not to describe a specific framework, but to understand the architectural trade-offs behind where token meaning lives on-chain.</p></blockquote><div><hr></div><h2><strong>Accounts Do Not Carry Meaning</strong></h2><p>At the runtime level, an <a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-architecture-account-model-and-transactions-part-1-1bffae449650">account on </a><strong><a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-architecture-account-model-and-transactions-part-1-1bffae449650">Solana</a></strong> is nothing more than a byte array with an owner, lamports, and a data length. The runtime does not interpret that data, validate its structure, or attach any semantic meaning to it.</p><p>There is no native concept of:</p><ul><li><p>a token</p></li><li><p>a balance</p></li><li><p>metadata</p></li><li><p>ownership semantics</p></li></ul><p>All of that meaning is introduced <strong>entirely by programs</strong>.</p><p>This distinction is easy to miss because most developers interact with Solana through SDKs and frameworks that present a much richer mental model. A &#8220;Mint account&#8221; feels like a first-class object. A &#8220;Token Account&#8221; feels like a balance record. But those are <em>program-level interpretations</em>, not runtime primitives.</p><p>From the runtime&#8217;s perspective:</p><ul><li><p>an SPL Token mint account is just an account owned by the <strong>SPL Token Program</strong></p></li><li><p>a token account is just another account owned by the same program</p></li><li><p>the relationship between them is not enforced globally</p></li></ul><p>The runtime does not know that a token account &#8220;belongs&#8221; to a mint.<br>It only enforces that <strong>the owning program</strong> is the one allowed to read and mutate the data.</p><p>This is a crucial property of Solana&#8217;s design:</p><blockquote><p><strong>Meaning is not intrinsic. It is contextual.</strong></p></blockquote><p>Every invariant you rely on: <strong>total supply, balances, authorities, decimals</strong>, exists only because a specific program chooses to enforce it when invoked.</p><p><strong>The same principle applies to metadata.</strong></p><p>There is nothing in the runtime that says:</p><ul><li><p>&#8220;this account contains metadata&#8221;</p></li><li><p>&#8220;this metadata describes that mint&#8221;</p></li><li><p>&#8220;this metadata must exist&#8221;</p></li></ul><p>Those are <em>assumptions layered on top</em> of the account model.</p><h2><strong>Why Token Metadata Started Outside the Mint</strong></h2><p>Given this model, the natural question is: <strong>Why wasn&#8217;t metadata stored directly inside the mint account from the beginning? </strong>The answer is not ideological, it is structural.</p><p>Early SPL Token mint accounts had:</p><ul><li><p>a fixed layout</p></li><li><p>a fixed size</p></li><li><p>no mechanism for optional or extensible fields</p></li></ul><p>Once a mint was created, its data layout was effectively frozen.<br>There was no safe way to append arbitrary information without breaking compatibility.</p><p>At the same time, metadata had very different requirements from the core token state:</p><ul><li><p>variable-length fields: names, symbols and URIs</p></li><li><p>evolving schemas</p></li><li><p>different authority rules</p></li><li><p>different lifecycle expectations</p></li></ul><p>Putting metadata into the mint would have tightly coupled all of that complexity to the token program itself. Instead, Solana developers leaned on one of the runtime&#8217;s strongest primitives: <strong>Program-Derived Addresses (PDAs)</strong>.</p><p>A metadata account could be:</p><ul><li><p>deterministically derived from the mint address</p></li><li><p>owned by a separate program</p></li><li><p>versioned independently</p></li><li><p>optional</p></li></ul><p>This led to the now-familiar pattern: The mint account contains only core token state, lives in a separate program-owned account and the relationship between them is defined by a derivation rule, Crucially, this relationship is <strong>not enforced by the runtime</strong>. All of those guarantees exist only because clients and programs agree to look in the same place.</p><h2><strong>Metadata as Data, introduction to Metaplex</strong></h2><p>Once metadata was separated from the mint, the ecosystem needed more than an informal pattern. It needed a shared interpretation layer something that could describe what metadata looks like, who can update it, and how clients should reason about it.</p><p>This is the role played by <strong><a href="https://developers.metaplex.com/smart-contracts/token-metadata">Metaplex</a></strong>. Metaplex does not change how tokens work at the runtime level. It does not introduce a new asset type, nor does it modify the SPL Token program. Instead, it introduces a dedicated program whose only responsibility is to interpret metadata stored in accounts.</p><p>That program, the <strong>Metaplex Token Metadata Program</strong>, formalizes a simple but powerful idea: a token&#8217;s human-meaningful description should live in a separate account, owned and governed independently from the token&#8217;s supply and balances.</p><p>The relationship between a mint and its metadata is established through a deterministic derivation rule. Given a mint address, the metadata account can be derived as a Program-Derived Address using a fixed seed and the metadata program ID. This makes the relationship predictable without embedding any metadata logic into the token program itself.</p><p>The metadata account contains structured fields such as the token&#8217;s name, symbol, URI, and update authority. From the token program&#8217;s point of view, none of this exists. From Metaplex&#8217;s point of view, this is the authoritative description of what the token represents.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!j0ej!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!j0ej!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 424w, https://substackcdn.com/image/fetch/$s_!j0ej!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 848w, https://substackcdn.com/image/fetch/$s_!j0ej!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 1272w, https://substackcdn.com/image/fetch/$s_!j0ej!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!j0ej!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png" width="1456" height="1020" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1020,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:188343,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/184427800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!j0ej!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 424w, https://substackcdn.com/image/fetch/$s_!j0ej!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 848w, https://substackcdn.com/image/fetch/$s_!j0ej!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 1272w, https://substackcdn.com/image/fetch/$s_!j0ej!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4d6068-4954-42dc-b751-b0321d0e448d_1528x1070.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Note:</strong> Metaplex&#8217;s seeds can be found <strong><a href="https://github.com/metaplex-foundation/mpl-token-metadata/blob/main/programs/token-metadata/program/src/pda.rs">here</a></strong></p></blockquote><h3>Example</h3><p>Lets mint a new token and deploy the Metadata for this token, this example assumes you read the <a href="https://andreyobruchkov1996.substack.com/p/spl-token-program-architecture-a">previous blog post </a>and understand whats going on,</p><p>Token minting:</p><pre><code>spl-token create-token --decimals 9</code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wbp3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wbp3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 424w, https://substackcdn.com/image/fetch/$s_!Wbp3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 848w, https://substackcdn.com/image/fetch/$s_!Wbp3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 1272w, https://substackcdn.com/image/fetch/$s_!Wbp3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wbp3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png" width="1456" height="176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:176,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56787,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/184427800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Wbp3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 424w, https://substackcdn.com/image/fetch/$s_!Wbp3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 848w, https://substackcdn.com/image/fetch/$s_!Wbp3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 1272w, https://substackcdn.com/image/fetch/$s_!Wbp3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72af5b79-ec41-4069-abd3-3404ab47c06b_1688x204.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Token account creation:</p><pre><code><code>spl-token create-account H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wUYH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wUYH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 424w, https://substackcdn.com/image/fetch/$s_!wUYH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 848w, https://substackcdn.com/image/fetch/$s_!wUYH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 1272w, https://substackcdn.com/image/fetch/$s_!wUYH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wUYH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png" width="1456" height="91" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:91,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34053,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/184427800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wUYH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 424w, https://substackcdn.com/image/fetch/$s_!wUYH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 848w, https://substackcdn.com/image/fetch/$s_!wUYH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 1272w, https://substackcdn.com/image/fetch/$s_!wUYH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe3f4e4f4-bfad-4395-8e3b-a6591a97afa8_1688x106.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Minting tokens to the created account:</p><pre><code><code>spl-token mint H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D 1000</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GIua!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GIua!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 424w, https://substackcdn.com/image/fetch/$s_!GIua!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 848w, https://substackcdn.com/image/fetch/$s_!GIua!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 1272w, https://substackcdn.com/image/fetch/$s_!GIua!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GIua!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png" width="1456" height="135" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/db2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:135,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48904,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/184427800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GIua!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 424w, https://substackcdn.com/image/fetch/$s_!GIua!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 848w, https://substackcdn.com/image/fetch/$s_!GIua!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 1272w, https://substackcdn.com/image/fetch/$s_!GIua!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb2a30cf-5100-4b87-a0dc-f8c5d69f930f_1688x156.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Metadata creation:</p><pre><code>import { createMetadataAccountV3 } from "@metaplex-foundation/mpl-token-metadata";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { keypairIdentity, createSignerFromKeypair } from "@metaplex-foundation/umi";
import { publicKey } from "@metaplex-foundation/umi-public-keys";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";

const keypairPath = path.join(os.homedir(), ".config", "solana", "id.json");

const secretKey = JSON.parse(fs.readFileSync(keypairPath, "utf8"));

const mint = "H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D";

async function main() {
  // Create UMI instance
  const umi = createUmi("https://api.devnet.solana.com");

  // Set up the keypair identity
  const keypair = umi.eddsa.createKeypairFromSecretKey(
    new Uint8Array(secretKey)
  );
  umi.use(keypairIdentity(keypair));

  // Create signer from keypair
  const mintAuthoritySigner = createSignerFromKeypair(umi, keypair);

  // Create metadata account
  const tx = createMetadataAccountV3(umi, {
    mint: publicKey(mint),
    mintAuthority: mintAuthoritySigner,
    data: {
      name: "Example Token",
      symbol: "EXMPL",
      uri: "https://example.com/metadata.json",
      sellerFeeBasisPoints: 0,
      creators: null,
      collection: null,
      uses: null,
    },
    isMutable: true,
    collectionDetails: null,
  });

  const signature = await tx.sendAndConfirm(umi);
  console.log("Transaction signature:", signature);
}

main().catch((error) =&gt; {
  console.error("Error:", error);
  process.exit(1);
});
</code></pre><p>This code created this account: </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1hNJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1hNJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 424w, https://substackcdn.com/image/fetch/$s_!1hNJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 848w, https://substackcdn.com/image/fetch/$s_!1hNJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!1hNJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1hNJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png" width="1456" height="738" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:738,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:338074,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/184427800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1hNJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 424w, https://substackcdn.com/image/fetch/$s_!1hNJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 848w, https://substackcdn.com/image/fetch/$s_!1hNJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!1hNJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa3835020-7cf1-497f-a77d-2c0cda7a6f63_2908x1474.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">https://solscan.io/tx/5Zu5v2Hicwi4UuutHK6XEah6hc1FXRvSG71zdhoqdq2BotjBvonPMBfEp1TK8CAyfn85G6kRdx4viJrLe32PuB3B?cluster=devnet</figcaption></figure></div><p>To read the Metadata of the token we can use this code:</p><pre><code>import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { publicKey } from "@metaplex-foundation/umi-public-keys";
import { fetchDigitalAsset } from "@metaplex-foundation/mpl-token-metadata";

const MINT_ADDRESS = "H3NmgE4YBxzkNY94vV7z6UpHQaWse7VhQeYYrnHr3g5D";

const RPC_URL = "https://api.devnet.solana.com";

async function main() {
  console.log("Checking metadata for mint:", MINT_ADDRESS);

  // Create UMI instance
  const umi = createUmi(RPC_URL);

  try {
    // Fetch the digital asset (mint + metadata + edition info)
    const mint = publicKey(MINT_ADDRESS);
    const digitalAsset = await fetchDigitalAsset(umi, mint);

    const metadata = digitalAsset.metadata;

    console.log("\nMetadata found!");
    console.log("\nToken Metadata:");
    console.log("&#9472;".repeat(50));
    console.log("Name:                ", metadata.name);
    console.log("Symbol:              ", metadata.symbol);
    console.log("URI:                 ", metadata.uri);
    console.log("Seller Fee (bps):    ", metadata.sellerFeeBasisPoints);
    console.log("Is Mutable:          ", metadata.isMutable);
    console.log("Update Authority:    ", metadata.updateAuthority);
    console.log("\n Mint Info:");
    console.log("Mint Address:        ", digitalAsset.mint.publicKey);
    console.log("Decimals:            ", digitalAsset.mint.decimals);
    console.log("Supply:              ", digitalAsset.mint.supply);
    console.log("Mint Authority:      ", digitalAsset.mint.mintAuthority);
    console.log("Freeze Authority:    ", digitalAsset.mint.freezeAuthority);
  } catch (error) {
    if (error.message?.includes("Account Not Found")) {
      console.log("\n No metadata found for this token mint");
      console.log("The token exists but doesn't have metadata associated with it yet");
      console.log("Use metadata.ts to create metadata for this token");
    } else {
      throw error;
    }
  }
}

main().catch((error) =&gt; {
  console.error("Error:", error);
  process.exit(1);
});</code></pre><p>Output:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SVXJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SVXJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 424w, https://substackcdn.com/image/fetch/$s_!SVXJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 848w, https://substackcdn.com/image/fetch/$s_!SVXJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 1272w, https://substackcdn.com/image/fetch/$s_!SVXJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SVXJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png" width="1030" height="626" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:626,&quot;width&quot;:1030,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109358,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/184427800?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SVXJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 424w, https://substackcdn.com/image/fetch/$s_!SVXJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 848w, https://substackcdn.com/image/fetch/$s_!SVXJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 1272w, https://substackcdn.com/image/fetch/$s_!SVXJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2b8d3918-c640-45c4-9ad3-67a7161feee2_1030x626.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2><strong>The Hidden Cost of Convention</strong></h2><p>The Metaplex model worked and still works because it created a shared convention across the ecosystem. Wallets, marketplaces, indexers, and SDKs all agree on where metadata lives and how to interpret it. But that agreement exists outside the protocol.</p><p>The Solana runtime does not enforce the relationship between a mint and its metadata. A mint does not contain a reference to its metadata account. The metadata account does not declare which mint it describes. The link exists only because every participant in the ecosystem already knows how to derive it.</p><p>This means correctness depends on shared knowledge rather than explicit state.</p><p>If metadata is missing, nothing fails at the protocol level. If metadata is malformed, the runtime does not object. If a token chooses to store metadata somewhere else, the chain does not prevent it. These are all valid states from Solana&#8217;s point of view.</p><p>The cost of this design is not immediately visible in happy paths. It appears at the boundaries: when programs disagree, when tooling makes assumptions, or when new use cases stretch old conventions.</p><p>At that point, the chain cannot help you. There is no on-chain way to verify that &#8220;this account is the metadata for that mint&#8221; unless every program involved already agrees on the same rules.</p><h3><strong>Summary</strong></h3><p>Token metadata on Solana has always existed outside the runtime&#8217;s understanding of token relationships. The difference between early designs is not what metadata contains, but how its location and association are communicated.</p><p>Convention-based designs rely on shared assumptions between programs and tooling. The Metaplex Token Metadata program standardized those assumptions, but did not make them part of the runtime&#8217;s enforcement.</p><p>This creates a clear boundary between what the protocol enforces and what the ecosystem agrees upon.</p><p>In the next post, we&#8217;ll examine how newer token program designs move beyond convention, and what it means to make token relationships explicit rather than assumed.</p>]]></content:encoded></item><item><title><![CDATA[SPL Token Program Architecture: A Technical Overview]]></title><description><![CDATA[The SPL Token program is the foundation of fungible assets on Solana.]]></description><link>https://andreyobruchkov1996.substack.com/p/spl-token-program-architecture-a</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/spl-token-program-architecture-a</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Tue, 13 Jan 2026 12:22:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zGAU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>The SPL Token program is the foundation of fungible assets on Solana.</p><p>It defines how tokens are represented on-chain, how ownership is enforced, and how balances are created, transferred, and destroyed at runtime.</p><p>While most developers interact with SPL tokens through high-level SDKs, those abstractions hide important architectural details. Understanding the SPL Token program at the protocol level is essential for building reliable token flows, integrating with DeFi protocols, and debugging unexpected on-chain behavior.</p><p>This post provides a technical overview of the SPL Token program&#8217;s architecture. We&#8217;ll focus on how the program models state using accounts, how authority is enforced, and how common token operations are executed by the runtime.</p><p>By the end of this article, you will understand:</p><ul><li><p>how the SPL Token program represents tokens using <strong>Mint</strong> and <strong>Token Account</strong> state</p></li><li><p>how balances are derived from account data, not stored globally</p></li><li><p>how authorities (mint, freeze, owner) are enforced by the program</p></li><li><p>how SPL token design differs fundamentally from account-based token models on other chains</p></li></ul><p>This post establishes the architectural foundation.</p><p>In the next blogs, we&#8217;ll build on this to explore transfers, authorities and extensions.</p></blockquote><div><hr></div><h2>How the SPL Token program represents</h2><p>Tokens are on-chain constructs used to represent ownership and transferability of assets. Tokenization allows property rights and value to be expressed and enforced programmatically on a blockchain.</p><p>On Solana, tokens are implemented through the <strong><a href="https://solscan.io/account/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA">SPL (Solana Program Library) Token program</a></strong><a href="http://TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA">.</a></p><p>This section introduces the core concepts behind how tokens are modeled and managed on Solana. For practical examples and code walkthroughs, refer to the <em>SPL Token Basics</em> section.</p><h4>Concepts:</h4><ul><li><p><strong>Token programs</strong> define the instruction set and validation logic for creating, minting, transferring, and burning tokens on the network. The same program supports both fungible and non-fungible tokens.</p></li><li><p>A <strong>Mint account</strong> represents a specific token type and stores global, token-wide state such as total supply, decimal precision, and mint authority (the address permitted to issue new tokens).</p></li><li><p>A <strong>Token account</strong> represents ownership of tokens for a given mint and owner. It holds the balance and ownership metadata for that specific combination.</p></li><li><p>An <strong>Associated Token Account (ATA)</strong> is a canonical Token account whose address is deterministically derived from the owner&#8217;s address and the mint address, ensuring a standard and predictable location for token balances.</p></li></ul><p>All tokens on Solana are effectively data accounts owned by a <a href="https://solscan.io/account/TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA">Token Program.</a></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zGAU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zGAU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 424w, https://substackcdn.com/image/fetch/$s_!zGAU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 848w, https://substackcdn.com/image/fetch/$s_!zGAU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 1272w, https://substackcdn.com/image/fetch/$s_!zGAU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zGAU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png" width="1366" height="748" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:748,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:68095,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zGAU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 424w, https://substackcdn.com/image/fetch/$s_!zGAU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 848w, https://substackcdn.com/image/fetch/$s_!zGAU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 1272w, https://substackcdn.com/image/fetch/$s_!zGAU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2de702d-7698-452a-b290-1dff1f1428b5_1366x748.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Mint Account</h3><p>On Solana, a token is uniquely defined by its <strong>Mint account address</strong>, which is owned by the SPL Token program. The Mint account represents the global state of a specific token and serves as the canonical source of truth for its configuration and supply.</p><p>The Mint account stores the following information:</p><ul><li><p><strong>Supply</strong>: the total number of tokens that have been minted</p></li><li><p><strong>Decimals</strong>: the decimal precision used to represent fractional units</p></li><li><p><strong>Mint authority</strong>: the account permitted to mint new tokens and increase the total supply</p></li><li><p><strong>Freeze authority</strong>: the account permitted to freeze Token accounts, preventing transfers or burns</p></li></ul><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ncd8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ncd8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 424w, https://substackcdn.com/image/fetch/$s_!Ncd8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 848w, https://substackcdn.com/image/fetch/$s_!Ncd8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 1272w, https://substackcdn.com/image/fetch/$s_!Ncd8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ncd8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png" width="1366" height="648" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/35190489-2e16-403c-8b28-660400116fd9_1366x648.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:648,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:76730,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ncd8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 424w, https://substackcdn.com/image/fetch/$s_!Ncd8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 848w, https://substackcdn.com/image/fetch/$s_!Ncd8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 1272w, https://substackcdn.com/image/fetch/$s_!Ncd8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F35190489-2e16-403c-8b28-660400116fd9_1366x648.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The full struct representation can be found <a href="https://github.com/solana-program/token/blob/6d18ff73b1dd30703a30b1ca941cb0f1d18c2b2a/program/src/state.rs#L16-L30">here</a> and looks like that:</p><pre><code>pub struct Mint {
    pub mint_authority: COption&lt;Pubkey&gt;,
    pub supply: u64,
    pub decimals: u8,
    pub is_initialized: bool,
    pub freeze_authority: COption&lt;Pubkey&gt;,
}</code></pre><p>For reference, here is a Solana Explorer link to the <a href="https://solscan.io/token/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB">USDT Mint Account</a>.</p><h3>Token Account</h3><p>To represent individual ownership, the Token program uses <strong>Token accounts</strong>. Each Token account is associated with a single Mint and is used to track the balance of that token for a specific owner.</p><p>A Token account stores the following information:</p><ul><li><p><strong>Mint</strong>: the token type to which the account belongs</p></li><li><p><strong>Owner</strong>: the account authorized to transfer tokens from the Token account</p></li><li><p><strong>Amount</strong>: the number of token units currently held in the account</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gMvB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gMvB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 424w, https://substackcdn.com/image/fetch/$s_!gMvB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 848w, https://substackcdn.com/image/fetch/$s_!gMvB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 1272w, https://substackcdn.com/image/fetch/$s_!gMvB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gMvB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png" width="1366" height="648" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:648,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69598,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gMvB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 424w, https://substackcdn.com/image/fetch/$s_!gMvB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 848w, https://substackcdn.com/image/fetch/$s_!gMvB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 1272w, https://substackcdn.com/image/fetch/$s_!gMvB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffde43950-85b2-4b09-b6e1-329d921ecc71_1366x648.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The full struct representation can be found <a href="https://github.com/solana-program/token/blob/6d18ff73b1dd30703a30b1ca941cb0f1d18c2b2a/program/src/state.rs#L87-L108">here</a> and looks like that:</p><pre><code>pub struct Account {
    pub mint: Pubkey,
    pub owner: Pubkey,
    pub amount: u64,
    pub delegate: COption&lt;Pubkey&gt;,
    pub state: AccountState,
    pub is_native: COption&lt;u64&gt;,
    pub delegated_amount: u64,
    pub close_authority: COption&lt;Pubkey&gt;,
}</code></pre><p>A wallet must have a dedicated Token account for each token (Mint) it intends to hold, with the wallet&#8217;s address set as the owner of that account. While a single wallet can control multiple Token accounts for the same Mint, each Token account is restricted to a single owner and can hold units of only one Mint.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9pir!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9pir!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 424w, https://substackcdn.com/image/fetch/$s_!9pir!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 848w, https://substackcdn.com/image/fetch/$s_!9pir!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 1272w, https://substackcdn.com/image/fetch/$s_!9pir!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9pir!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png" width="1366" height="722" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:722,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74950,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9pir!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 424w, https://substackcdn.com/image/fetch/$s_!9pir!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 848w, https://substackcdn.com/image/fetch/$s_!9pir!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 1272w, https://substackcdn.com/image/fetch/$s_!9pir!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3280d509-38f5-4bcc-bed1-770096b9bcbd_1366x722.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Associated Token Account</h3><p>Associated Token Accounts (ATAs) provide a standardized way to locate a Token account for a given Mint and owner. An ATA serves as the canonical, or default, Token account for a specific combination of owner and Mint.</p><p>An Associated Token Account is created at an address that is deterministically derived from the owner&#8217;s address and the Mint address. Functionally, an ATA is no different from any other Token account the distinction lies solely in how its address is derived.</p><p>This mechanism relies on a fundamental Solana concept: <strong>Program Derived Addresses (PDAs)</strong> that we learned <a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-account-model?r=2a5hnk&amp;utm_campaign=post&amp;utm_medium=web&amp;triedRedirect=true">here</a>.</p><h3>Cli examples</h3><h4><strong>Configuring the Solana CLI for devnet</strong></h4><pre><code>solana config set --url https://api.devnet.solana.com</code></pre><h4><strong>Create a new keypair or use an existing one:</strong></h4><pre><code><code>solana-keygen new</code></code></pre><h4><strong>Verify your active configuration:</strong></h4><pre><code><code>solana config get</code></code></pre><p>it should look like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ilmi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ilmi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 424w, https://substackcdn.com/image/fetch/$s_!Ilmi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 848w, https://substackcdn.com/image/fetch/$s_!Ilmi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 1272w, https://substackcdn.com/image/fetch/$s_!Ilmi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ilmi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png" width="1366" height="188" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:188,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:52082,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ilmi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 424w, https://substackcdn.com/image/fetch/$s_!Ilmi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 848w, https://substackcdn.com/image/fetch/$s_!Ilmi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 1272w, https://substackcdn.com/image/fetch/$s_!Ilmi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbd80e92e-f424-4495-90f6-e413b0380e38_1366x188.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4><strong>airdrop to your created account some SOL</strong></h4><pre><code>solana airdrop 1</code></pre><p>you should see output like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1vTi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1vTi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 424w, https://substackcdn.com/image/fetch/$s_!1vTi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 848w, https://substackcdn.com/image/fetch/$s_!1vTi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 1272w, https://substackcdn.com/image/fetch/$s_!1vTi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1vTi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png" width="1366" height="178" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:178,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38563,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1vTi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 424w, https://substackcdn.com/image/fetch/$s_!1vTi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 848w, https://substackcdn.com/image/fetch/$s_!1vTi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 1272w, https://substackcdn.com/image/fetch/$s_!1vTi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffffad67d-d314-4e81-844e-67c3ea558d77_1366x178.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4>Install SPL CLI:</h4><pre><code><code>cargo install spl-token-cli</code></code></pre><p>Run this to verify that it is installed successfully:</p><pre><code><code>spl-token --version

// The output will look like:
// spl-token-cli 5.4.0</code></code></pre><h4><strong>Creating a new token mint</strong></h4><pre><code>spl-token create-token

// To specify decimal precision explicitly:
// spl-token create-token --decimals 6</code></pre><p>output:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Psej!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Psej!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 424w, https://substackcdn.com/image/fetch/$s_!Psej!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 848w, https://substackcdn.com/image/fetch/$s_!Psej!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 1272w, https://substackcdn.com/image/fetch/$s_!Psej!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Psej!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png" width="1456" height="181" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df04488a-d678-4099-a799-a89bda794a8a_1670x208.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:181,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64332,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Psej!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 424w, https://substackcdn.com/image/fetch/$s_!Psej!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 848w, https://substackcdn.com/image/fetch/$s_!Psej!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 1272w, https://substackcdn.com/image/fetch/$s_!Psej!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf04488a-d678-4099-a799-a89bda794a8a_1670x208.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4><strong>Creating a token account</strong></h4><pre><code>spl-token create-account &lt;MINT_ADDRESS&gt;</code></pre><p>output:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!s3pW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!s3pW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 424w, https://substackcdn.com/image/fetch/$s_!s3pW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 848w, https://substackcdn.com/image/fetch/$s_!s3pW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 1272w, https://substackcdn.com/image/fetch/$s_!s3pW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!s3pW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png" width="1456" height="119" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:119,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:47457,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!s3pW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 424w, https://substackcdn.com/image/fetch/$s_!s3pW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 848w, https://substackcdn.com/image/fetch/$s_!s3pW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 1272w, https://substackcdn.com/image/fetch/$s_!s3pW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbffd764d-1c2d-4c8e-a97c-acdc0d9045c8_1670x136.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4><strong>Minting tokens</strong></h4><pre><code>spl-token mint &lt;MINT_ADDRESS&gt; 1000</code></pre><p>output:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FZFP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FZFP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 424w, https://substackcdn.com/image/fetch/$s_!FZFP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 848w, https://substackcdn.com/image/fetch/$s_!FZFP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 1272w, https://substackcdn.com/image/fetch/$s_!FZFP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FZFP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png" width="1456" height="167" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:167,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63240,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FZFP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 424w, https://substackcdn.com/image/fetch/$s_!FZFP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 848w, https://substackcdn.com/image/fetch/$s_!FZFP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 1272w, https://substackcdn.com/image/fetch/$s_!FZFP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2e6e49a0-53ef-4ac6-b28e-ac848b34e8eb_1670x192.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4><strong>Check balance</strong></h4><pre><code><code>spl-token balance &lt;MINT_ADDRESS&gt;</code></code></pre><p>output:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ggBb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ggBb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 424w, https://substackcdn.com/image/fetch/$s_!ggBb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 848w, https://substackcdn.com/image/fetch/$s_!ggBb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 1272w, https://substackcdn.com/image/fetch/$s_!ggBb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ggBb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png" width="1456" height="73" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8927c123-083b-46af-8180-dd006116840e_1670x84.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:73,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ggBb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 424w, https://substackcdn.com/image/fetch/$s_!ggBb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 848w, https://substackcdn.com/image/fetch/$s_!ggBb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 1272w, https://substackcdn.com/image/fetch/$s_!ggBb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8927c123-083b-46af-8180-dd006116840e_1670x84.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h4><strong>Transferring tokens</strong></h4><p>Automatically create the recipient&#8217;s associated token account if it does not exist:</p><pre><code><code>spl-token transfer &lt;MINT_ADDRESS&gt; 100 &lt;RECIPIENT_WALLET_ADDRESS&gt; --fund-recipient</code></code></pre><p>output:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ceIq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ceIq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 424w, https://substackcdn.com/image/fetch/$s_!ceIq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 848w, https://substackcdn.com/image/fetch/$s_!ceIq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 1272w, https://substackcdn.com/image/fetch/$s_!ceIq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ceIq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png" width="1456" height="192" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/edb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:192,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98916,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/182481234?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ceIq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 424w, https://substackcdn.com/image/fetch/$s_!ceIq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 848w, https://substackcdn.com/image/fetch/$s_!ceIq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 1272w, https://substackcdn.com/image/fetch/$s_!ceIq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fedb64260-0e98-4975-9e8d-c07e10efcb31_1924x254.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>Summary</h2><p>This article covered the foundational architecture of the SPL Token program and how tokens are represented on Solana. We examined the roles of Mint accounts, Token accounts, and Associated Token Accounts, and how they work together to model token supply and ownership on-chain.</p><p>This understanding provides the basis for analyzing token transfers, authority management, and interactions with higher-level protocols. In the next part of the series, we&#8217;ll explore how the SPL Token program enforces these rules during instruction execution.</p>]]></content:encoded></item><item><title><![CDATA[Understanding Solana – Part 6: Transactions, Messages, and Address Lookup Tables in Practice]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/understanding-solana-part-6-transactions</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/understanding-solana-part-6-transactions</guid><pubDate>Wed, 24 Dec 2025 19:10:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>In the previous post, we broke down how Solana transactions are structured: messages, signatures, serialization, and how the runtime executes them.</p><p>In this final part, we&#8217;ll stop theorizing and start sending real transactions.</p><p>We&#8217;ll build a small on-chain program and a client that calls it. Then we&#8217;ll send <strong>the exact same instruction</strong> two different ways:</p><ul><li><p>as a <strong>versioned (v0) transaction</strong></p></li><li><p>and as a <strong>v0 transaction using an Address Lookup Table</strong></p></li></ul><p>We&#8217;ll look at the <strong>actual code</strong> that builds each transaction, inspect the messages that get signed, and see how accounts are passed, locked, and resolved at runtime.</p><p>Along the way, we&#8217;ll make several things explicit that most tutorials hide:</p><ul><li><p>why some accounts live in .accounts() and others in remainingAccounts</p></li><li><p>how the same instruction can be executed under completely different messages</p></li><li><p>what Address Lookup Tables really do (and what they don&#8217;t)</p></li><li><p>what the IDL describes, and why it has nothing to do with execution</p></li></ul><p>Nothing here relies on magic helpers. Every transaction is constructed deliberately, every account is passed explicitly, and every optimization is visible in the message.</p><p>By the end, you&#8217;ll know how to build v0 transactions with and without ALTs, when they behave the same, and why scaling on Solana is mostly a message-level problem.</p><p>This post closes the series by connecting instructions, messages, and tooling into one concrete, executable mental model.</p></blockquote><div><hr></div><h2><strong>From theory to execution</strong></h2><p>In the <a href="https://substack.com/@andreyobruchkov/p-180901144">previous parts</a> of this series, we treated Solana transactions almost like a protocol specification.</p><p>We broke them down into:</p><ul><li><p>instructions and their serialized data</p></li><li><p>messages and account lists</p></li><li><p>signatures, fees, and blockhashes</p></li><li><p>and how the runtime executes a message once it&#8217;s accepted by the network</p></li></ul><p>All of that can feel abstract when looked at in isolation. In real programs, those same concepts reappear not as definitions, but as <strong>constraints you have to work with</strong>.</p><p>This post is about seeing those ideas surface in practice.</p><h2><strong>How real programs experience instructions</strong></h2><p>From the program&#8217;s point of view, an instruction is still just:</p><ul><li><p>a byte array</p></li><li><p>a list of accounts</p></li><li><p>and a program ID</p></li></ul><p>The program has no concept of:</p><ul><li><p>legacy vs versioned transactions</p></li><li><p>address lookup tables</p></li><li><p>fee payers or signatures</p></li></ul><p>It only sees the result of the message: a fixed set of accounts it is allowed to read or write, in a fixed order.</p><p>That&#8217;s why well-designed programs:</p><ul><li><p>avoid hardcoding account layouts</p></li><li><p>rely on passed-in accounts</p></li><li><p>and stay agnostic to how the client constructs the message</p></li></ul><p>In other words, programs live <strong>below</strong> the transaction abstraction.</p><h2><strong>How real clients experience messages</strong></h2><p>Clients live at the opposite end. They don&#8217;t care about program internals.</p><p>They care about:</p><ul><li><p>how many accounts must be passed</p></li><li><p>how large the message becomes</p></li><li><p>whether the transaction still fits on-chain</p></li><li><p>and whether it can be signed and broadcast successfully</p></li></ul><p>This is where concepts like:</p><ul><li><p>instruction batching</p></li><li><p>versioned messages</p></li><li><p>and Address Lookup Tables</p></li></ul><p>stop being optional and start being architectural. The logic hasn&#8217;t changed. The instruction hasn&#8217;t changed. Only the <strong>message</strong> has.</p><h2><strong>What we&#8217;ll do next</strong></h2><p>With that context in mind, the rest of this post will walk through a small, intentionally boring program and show how:</p><ul><li><p>the <strong>same instruction</strong> executes under different messages</p></li><li><p>nothing changes for the program</p></li><li><p>and everything changes for the client</p></li></ul><p>That&#8217;s where theory stops and real Solana development begins.</p><h2>Solana Program</h2><p>With the execution model in mind, we can now look at a concrete on-chain program. We won&#8217;t cover environment setup, deployment, or tooling configuration here those were already covered in <a href="https://substack.com/@andreyobruchkov/p-179197408">earlier posts</a> in the series. Nothing in this section depends on Anchor specifics beyond basic program structure.</p><p>Instead, we&#8217;ll focus entirely on the implementation and how it interacts with instructions and messages at runtime.</p><p>The program itself is intentionally simple.</p><p>It does not write state. It does not create accounts. It does not perform CPIs.</p><p>Its only job is to read token account data passed to it and return balances.</p><p>That simplicity is deliberate: it lets us isolate how <strong>accounts are passed</strong>, how <strong>instructions are executed</strong>, and how <strong>messages constrain execution</strong>, without unrelated complexity.</p><p><strong>Code:</strong></p><pre><code>use anchor_lang::prelude::*;
use anchor_lang::solana_program::{program::set_return_data, program_pack::Pack};
use anchor_spl::{token, token_2022};


declare_id!(&#8221;&lt;YOUR-GENERATED-PROGRAM-ID&gt;&#8221;);

fn ata_for_token(owner: &amp;Pubkey, mint: &amp;Pubkey) -&gt; Pubkey {
    Pubkey::find_program_address(&amp;[owner.as_ref(), anchor_spl::token::ID.as_ref(), mint.as_ref()],
        &amp;anchor_spl::associated_token::ID,           // Associated Token Program id
    ).0
}

fn ata_for_token22(owner: &amp;Pubkey, mint: &amp;Pubkey) -&gt; Pubkey {
    Pubkey::find_program_address(&amp;[owner.as_ref(), anchor_spl::token_2022::ID.as_ref(), mint.as_ref()],
        &amp;anchor_spl::associated_token::ID,           // Associated Token Program id
    ).0
}

#[program]
pub mod wallet_token_balances {
    use super::*;

    pub fn get_balances(ctx: Context&lt;GetBalances&gt;, wallet_mints: Vec&lt;WalletMints&gt;) -&gt; Result&lt;()&gt; {
        // Build output: Vec&lt;Balance&gt; (wallet, mint, amount)
        let mut out: Vec&lt;Balance&gt; = Vec::new();

        // Map remaining accounts for quick lookup
        use std::collections::HashMap;
        let mut rem: HashMap&lt;Pubkey, &amp;AccountInfo&gt; = HashMap::with_capacity(ctx.remaining_accounts.len());
        for ai in ctx.remaining_accounts.iter() {
            rem.insert(*ai.key, ai);
        }

        // For each wallet and its mint list
        for wm in wallet_mints.iter() {
            for mint in wm.mints.iter() {
                // let ata = get_associated_token_address(&amp;wm.wallet, mint);

                let ata_v1  = ata_for_token(&amp;wm.wallet, mint);
                let ata_v22 = ata_for_token22(&amp;wm.wallet, mint);

                // Prefer whichever ATA the client actually passed in remaining_accounts
                let ai_opt = rem.get(&amp;ata_v1).copied().or_else(|| rem.get(&amp;ata_v22).copied());

                let amount: u64 = match ai_opt {
                    Some(ai) if *ai.owner == token::ID =&gt; {
                        let data_ref = ai.data.borrow();
                        match token::spl_token::state::Account::unpack_from_slice(&amp;data_ref) {
                            Ok(ta) if ta.owner == wm.wallet &amp;&amp; ta.mint == *mint =&gt; ta.amount,
                            _ =&gt; 0,
                        }
                    }
                    Some(ai) if *ai.owner == token_2022::ID =&gt; {
                        let data_ref = ai.data.borrow();
                        match token_2022::spl_token_2022::state::Account::unpack_from_slice(&amp;data_ref) {
                            Ok(ta) if ta.owner == wm.wallet &amp;&amp; ta.mint == *mint =&gt; ta.amount,
                            _ =&gt; 0,
                        }
                    }
                    _ =&gt; 0,
                };

                out.push(Balance {
                    wallet: wm.wallet,
                    mint: *mint,
                    amount,
                });
            }
        }

        // Return all results
        let bytes = out.try_to_vec()?;
        set_return_data(&amp;bytes);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct GetBalances {}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct WalletMints {
    pub wallet: Pubkey,
    pub mints: Vec&lt;Pubkey&gt;,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy)]
pub struct Balance {
    pub wallet: Pubkey,
    pub mint: Pubkey,
    pub amount: u64,
}</code></pre><h3><strong>What this program shows</strong></h3><p>This program is intentionally simple. It doesn&#8217;t own state, create accounts, or perform CPIs. Its purpose is to <strong>read token balances in batches</strong> for a list of wallets and mints provided by the client.</p><p>Instead of querying balances one-by-one, the instruction accepts a vector of (wallet, mints) and processes them in a single execution. This makes it suitable for aggregators, dashboards, or off-chain systems that need to fetch many balances efficiently.</p><p>The instruction declares <strong>no required accounts</strong>. All token accounts are passed via ctx.remaining_accounts, which means the program only sees the accounts the message explicitly provides.</p><p>For each wallet and mint, the program derives the expected associated token account addresses for both <strong>SPL Token v1</strong> and <strong>Token-2022</strong>, checks which one was included in the message, and reads the balance if it exists.</p><p>The result is returned using set_return_data, without writing anything on-chain.</p><p>The key takeaway is that this batching logic is completely <strong>agnostic to transaction format</strong>. Whether the client uses a legacy transaction, a v0 transaction, or a v0 transaction with an Address Lookup Table, the instruction behaves exactly the same. Only the message changes.</p><p><strong>we will run:</strong></p><pre><code>anchor build</code></pre><p>after the build is successfully finished, we can deploy it.</p><pre><code>anchor deploy</code></pre><p>After deploying, you can see the program on Solana Explorer (devnet) by its program-ID</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DvG7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DvG7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 424w, https://substackcdn.com/image/fetch/$s_!DvG7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 848w, https://substackcdn.com/image/fetch/$s_!DvG7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 1272w, https://substackcdn.com/image/fetch/$s_!DvG7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DvG7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png" width="1456" height="205" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:205,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:76112,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/181908254?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DvG7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 424w, https://substackcdn.com/image/fetch/$s_!DvG7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 848w, https://substackcdn.com/image/fetch/$s_!DvG7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 1272w, https://substackcdn.com/image/fetch/$s_!DvG7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51a8087a-91fb-4854-9b81-ed61e0405bd6_1836x258.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>you can see that on <a href="https://solscan.io/account/ATUwBNQMJRM4cVEVGLGZksaU3i5cAMoTN8xoZ64BBGzN?cluster=devnet">devnet</a>.</p><h2><strong>Client side: building the message the program will execute</strong></h2><p>The on-chain program is simple. The complexity lives on the client side, because <strong>Solana programs can only read accounts that the message explicitly provides</strong>. So the client has two jobs:</p><ol><li><p>figure out which token accounts (ATAs) might hold the balances</p></li><li><p>build a transaction message that includes those accounts and calls the instruction</p></li></ol><p>In this example, we&#8217;ll also walk through creating an Address Lookup Table. As we&#8217;ve seen earlier, there&#8217;s a limit to how many account addresses can be included directly in a transaction message. In real-world scenarios, especially when batching or aggregating data, you quickly hit that limit. Address Lookup Tables are the mechanism Solana provides to scale beyond it, and this example shows how and why they&#8217;re used.</p><blockquote><p><em><strong>Note on setup: </strong></em>For this example, I&#8217;m using two pre-created token mints and a pre-created Address Lookup Table:</p><p><strong>SPL TOKEN: </strong><a href="https://solscan.io/token/86uVjmYf4ehGPRZYhMWzHM4yMmEj13TNtutSnvESPLLN?cluster=devnet">86uVjmYf4ehGPRZYhMWzHM4yMmEj13TNtutSnvESPLLN</a></p><p><strong>TOKEN2022</strong>: <a href="https://solscan.io/token/2hyxpEgEH7gAFTPQi6Yguqxg4gmn9KSDyaGUfL7dJm7p?cluster=devnet">2hyxpEgEH7gAFTPQi6Yguqxg4gmn9KSDyaGUfL7dJm7p</a></p><p><strong>ALT: </strong><a href="https://solscan.io/account/5h6TFGPPaF2r9unFdS2ExqGveVNhFRWGQtURQ1RZvxSd?cluster=devnet">5h6TFGPPaF2r9unFdS2ExqGveVNhFRWGQtURQ1RZvxSd</a></p><p>The details of how these tokens were created aren&#8217;t important for understanding this post. What matters is how their <strong>addresses are used inside the transaction message</strong>. I&#8217;ll cover token creation(SPL Token vs Token-2022), minting in a separate post. Here, the focus is entirely on <strong>transaction construction, message layout, and execution. </strong>I covered the motivation behind ALTs in the <a href="https://substack.com/@andreyobruchkov/p-179143694">previous blog post</a>, here we focus on using them in practice.</p></blockquote><h4><strong>create alts table code:</strong></h4><pre><code><code>import * as anchor from &#8220;@coral-xyz/anchor&#8221;;
import {
  AddressLookupTableProgram,
  PublicKey,
} from &#8220;@solana/web3.js&#8221;;
import fs from &#8220;fs&#8221;;

async function main() {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);
  const connection = provider.connection;
  const payer = provider.wallet;

  const slot = await connection.getSlot(&#8221;confirmed&#8221;);

  const [createIx, lookupTableAddress] =
    AddressLookupTableProgram.createLookupTable({
      authority: payer.publicKey,
      payer: payer.publicKey,
      recentSlot: slot,
    });

  const tx = new anchor.web3.Transaction().add(createIx);
  const sig = await provider.sendAndConfirm(tx, []);

  console.log(&#8221;ALT created&#8221;);
  console.log(&#8221;sig:&#8221;, sig);
  console.log(&#8221;lookupTableAddress:&#8221;, lookupTableAddress.toBase58());

  fs.writeFileSync(
    &#8220;alt.json&#8221;,
    JSON.stringify({ lookupTableAddress: lookupTableAddress.toBase58() }, null, 2),
  );
}

main().catch((e) =&gt; {
  console.error(e);
  process.exit(1);
});</code></code></pre><h4>Client.ts</h4><pre><code>import { PublicKey, TransactionSignature, AddressLookupTableProgram } from &#8220;@solana/web3.js&#8221;;
import * as anchor from &#8220;@coral-xyz/anchor&#8221;;
import {
  getAssociatedTokenAddressSync,
  TOKEN_2022_PROGRAM_ID,
} from &#8220;@solana/spl-token&#8221;;
import {WalletTokenBalances}from&#8220;../target/types/wallet_token_balances&#8221;;
import {TransactionMessage, VersionedTransaction} from &#8220;@solana/web3.js&#8221;;

/**
 * Types mirroring your Rust structs
 */
export type WalletMints = {
  wallet: PublicKey;
  mints: PublicKey[];
};

export type BalanceResult = {
  wallet: PublicKey;
  mint: PublicKey;
  amount: bigint;
};

async function ensureAltHasAddresses(
  provider: anchor.AnchorProvider,
  altPubkey: PublicKey,
  addrs: PublicKey[],
) {
  const connection = provider.connection;

  const altResp = await connection.getAddressLookupTable(altPubkey);
  const altAccount = altResp.value;
  if (!altAccount) {
    throw new Error(&#8221;ALT not found (maybe not finalized yet?)&#8221;);
  }

  const existing = new Set(altAccount.state.addresses.map((a) =&gt; a.toBase58()));
  const missing = addrs.filter((a) =&gt; !existing.has(a.toBase58()));

  if (missing.length === 0) {
    return altAccount; // nothing to do
  }

  const extendIx = AddressLookupTableProgram.extendLookupTable({
    payer: provider.wallet.publicKey,
    authority: provider.wallet.publicKey,
    lookupTable: altPubkey,
    addresses: missing,
  });

  const tx = new anchor.web3.Transaction().add(extendIx);
  const sig = await provider.sendAndConfirm(tx, []);
  console.log(`ALT extended with ${missing.length} addresses. sig:`, sig);

  // refetch updated ALT
  const altResp2 = await connection.getAddressLookupTable(altPubkey);
  if (!altResp2.value) throw new Error(&#8221;ALT refetch failed after extend&#8221;);
  return altResp2.value;
}

export async function getBalancesClient(
  program: anchor.Program&lt;WalletTokenBalances&gt;,
  walletMints: { wallet: PublicKey; mints: PublicKey[] }[],
  altPubkey: PublicKey, // pass ALT separately
): Promise&lt;{ sig: TransactionSignature; balances: BalanceResult[] }&gt; {
  const provider = program.provider as anchor.AnchorProvider;
  const connection = provider.connection;

  // 1) Compute candidate ATAs (v1 + 2022)
  const candidateAtas: PublicKey[] = [];
  for (const wm of walletMints) {
    for (const mint of wm.mints) {
      candidateAtas.push(getAssociatedTokenAddressSync(mint, wm.wallet, true));
      candidateAtas.push(
        getAssociatedTokenAddressSync(mint, wm.wallet, true, TOKEN_2022_PROGRAM_ID),
      );
    }
  }

  // 2) Filter only existing ATAs -&gt; these will be remaining accounts AND ALT entries
  const infos = await connection.getMultipleAccountsInfo(candidateAtas);
  const existingAtas: PublicKey[] = [];
  for (let i = 0; i &lt; candidateAtas.length; i++) {
    if (infos[i]) existingAtas.push(candidateAtas[i]);
  }

  const remainingAccounts: anchor.web3.AccountMeta[] = existingAtas.map((pk) =&gt; ({
    pubkey: pk,
    isWritable: false,
    isSigner: false,
  }));

  // 3) Ensure ALT contains these ATA addresses (one-time-ish)
  const altAccount = await ensureAltHasAddresses(provider, altPubkey, existingAtas);

  // 4) Build the instruction
  const ix = await program.methods
    .getBalances(
      walletMints.map((wm) =&gt; ({ wallet: wm.wallet, mints: wm.mints })),
    )
    .accounts({})
    .remainingAccounts(remainingAccounts)
    .instruction();

  // 5) Build and send v0 transaction USING ALT
  const latest = await connection.getLatestBlockhash(&#8221;confirmed&#8221;);

  const msgV0 = new TransactionMessage({
    payerKey: provider.wallet.publicKey,
    recentBlockhash: latest.blockhash,
    instructions: [ix],
  }).compileToV0Message([altAccount]);

  const vtx = new VersionedTransaction(msgV0);
  await provider.wallet.signTransaction(vtx);

  const sig = await connection.sendTransaction(vtx, { maxRetries: 3 });
  await connection.confirmTransaction({ signature: sig, ...latest }, &#8220;confirmed&#8221;);

  // 6) Fetch tx + return data (same as you already do)
  const txInfo = await connection.getTransaction(sig, {
    commitment: &#8220;confirmed&#8221;,
    maxSupportedTransactionVersion: 0,
  });
  if (!txInfo) throw new Error(&#8221;getTransaction returned null&#8221;);

  const metaAny = txInfo.meta as any;
  const rd = metaAny?.returnData as { programId: string; data: [string, string] } | null | undefined;
  if (!rd) throw new Error(&#8221;Transaction succeeded but meta.returnData is null&#8221;);

  const raw = Buffer.from(rd.data[0], &#8220;base64&#8221;);
  const balances = decodeBalances(raw);
  return { sig, balances };
}


/**
 * Decode Vec&lt;Balance&gt; from Borsh:
 * struct Balance { wallet: Pubkey; mint: Pubkey; amount: u64 }
 *
 * Layout:
 * - u32 length (little endian)
 * - then `len` * (32 + 32 + 8) bytes
 */
function decodeBalances(buf: Buffer): BalanceResult[] {
  let offset = 0;
  const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);

  const len = dv.getUint32(offset, true);
  offset += 4;

  const results: BalanceResult[] = [];
  for (let i = 0; i &lt; len; i++) {
    const walletBytes = buf.slice(offset, offset + 32);
    offset += 32;
    const mintBytes = buf.slice(offset, offset + 32);
    offset += 32;
    const amount = dv.getBigUint64(offset, true);
    offset += 8;

    results.push({
      wallet: new PublicKey(walletBytes),
      mint: new PublicKey(mintBytes),
      amount,
    });
  }

  return results;
}

async function main() {
  // 1) Setup provider from env / solana config
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  // Use Anchor workspace (uses generated IDL/types under target/)
  const program = anchor.workspace
  .walletTokenBalances as anchor.Program&lt;WalletTokenBalances&gt;;
  const wallet = provider.wallet as anchor.Wallet;

  // 4) Paste real mint pubkeys from spl-token CLI
  const MINT_V1 = new PublicKey(&#8221;86uVjmYf4ehGPRZYhMWzHM4yMmEj13TNtutSnvESPLLN&#8221;);
  const MINT_22 = new PublicKey(&#8221;2hyxpEgEH7gAFTPQi6Yguqxg4gmn9KSDyaGUfL7dJm7p&#8221;);
  const altPubkey = new PublicKey(&#8221;5h6TFGPPaF2r9unFdS2ExqGveVNhFRWGQtURQ1RZvxSd&#8221;);

  const walletMints = [{
    wallet: wallet.publicKey,
    mints: [MINT_V1, MINT_22],
  }];

  // 5) Call your on-chain program
  const { sig, balances } = await getBalancesClient(program, walletMints, altPubkey);

  console.log(&#8221;tx:&#8221;, sig);
  for (const b of balances) {
    console.log(
      &#8220;wallet&#8221;,
      b.wallet.toBase58(),
      &#8220;mint&#8221;,
      b.mint.toBase58(),
      &#8220;amount&#8221;,
      b.amount.toString(),
    );
  }
}

main().catch((err) =&gt; {
  console.error(err);
  process.exit(1);
});</code></pre><blockquote><p><em><strong>Note:  </strong>We still pass accounts as metas via remainingAccounts because that&#8217;s what grants the program access. The ALT only compresses addresses inside the message; it doesn&#8217;t &#8220;provide&#8221; accounts by itself. Transaction without alt table will look the same but without </em>altAccount variable.</p></blockquote><p><strong>Then we will run it:</strong></p><pre><code> npx ts-node scripts/client.ts</code></pre><p>In the logs you should see Program return: and the decoded balances printed by the client.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!neYz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!neYz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 424w, https://substackcdn.com/image/fetch/$s_!neYz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 848w, https://substackcdn.com/image/fetch/$s_!neYz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 1272w, https://substackcdn.com/image/fetch/$s_!neYz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!neYz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png" width="1456" height="78" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:78,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56400,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/181908254?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!neYz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 424w, https://substackcdn.com/image/fetch/$s_!neYz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 848w, https://substackcdn.com/image/fetch/$s_!neYz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 1272w, https://substackcdn.com/image/fetch/$s_!neYz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbebb7f94-a702-4590-b427-aacd0b02bc93_1836x98.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>this is the <a href="https://solscan.io/tx/4GxRiVve9qC45eBPmNnfWyLeXWWxSc6DoXmmerNgu3P8BNrDGqkL47hhDWdaQJxkEArCTuHCzAwmgpDXiRb9HT3t?cluster=devnet">link to the transaction in the scanner</a>. </p><h3><strong>Step 1: derive the candidate accounts (ATAs)</strong></h3><p>The input to the instruction is just a list of wallets and mints:</p><pre><code><code>[{ wallet, mints: [MINT_V1, MINT_22] }]</code></code></pre><p>But the program doesn&#8217;t receive token accounts from that. It receives <em>accounts</em>. So the client derives the associated token account addresses for <strong>both token programs</strong>:</p><ul><li><p>classic SPL Token (v1)</p></li><li><p>Token-2022</p></li></ul><p>That&#8217;s why for every (wallet, mint) pair we compute two candidates:</p><pre><code><code>getAssociatedTokenAddressSync(mint, wallet) // v1
getAssociatedTokenAddressSync(mint, wallet, ..., TOKEN_2022_PROGRAM_ID) // 2022</code></code></pre><p>This mirrors the Rust helpers that derive ATAs on-chain. The client and the program must agree on these addresses.</p><h3><strong>Step 2: only pass accounts that actually exist</strong></h3><p>Deriving ATAs is cheap, but many of them might not exist on-chain. Passing non-existent accounts would just waste space or fail during account loading, so the client filters them:</p><pre><code>const infos = await connection.getMultipleAccountsInfo(candidateAtas);</code></pre><p>Only existing accounts are turned into remainingAccounts.</p><p>This is the crucial Solana rule in action:</p><blockquote><p>If it&#8217;s not in the instruction&#8217;s account list, the program can&#8217;t read it. So the client must bring the world with it.</p></blockquote><h3><strong>Step 3: remaining accounts are message wiring, not &#8220;program API&#8221;</strong></h3><p>When we build the instruction we call:</p><pre><code>.accounts({})
.remainingAccounts(remainingAccounts)</code></pre><p>.accounts({}) is empty because the program&#8217;s Anchor context is empty.</p><p>But remainingAccounts is where the real work happens: it becomes the instruction&#8217;s account metas list, which becomes part of the message, which becomes what the runtime loads and locks.</p><p>This is why this program scales with batching: the instruction data stays small while the message can carry lots of accounts.</p><h3><strong>Step 4: ALTs optimize the message, not the instruction</strong></h3><p>With batching, the message grows quickly because it has to include many account addresses. That&#8217;s where <strong>Address Lookup Tables</strong> come in.</p><p>The helper:</p><pre><code>ensureAltHasAddresses(...)</code></pre><p>is just a convenience wrapper that:</p><ul><li><p>fetches the ALT</p></li><li><p>checks which addresses are already stored there</p></li><li><p>extends the table with missing ones (only if needed)</p></li></ul><p>Importantly: the ALT does not &#8220;send accounts to the program&#8221;.</p><p>It only lets the message reference account addresses more efficiently.</p><p>We still pass the token accounts via remainingAccounts.</p><p>The ALT only changes <em>how the message encodes those addresses</em>.</p><h3><strong>Step 5: send a v0 transaction (where ALT actually matters)</strong></h3><p>ALTs only work with <strong>versioned (v0) messages</strong>, so the client builds a v0 message explicitly:</p><pre><code>new TransactionMessage({...}).compileToV0Message([altAccount]);</code></pre><p>Then signs and sends a VersionedTransaction:</p><pre><code>const vtx = new VersionedTransaction(msgV0);
await provider.wallet.signTransaction(vtx);
await connection.sendTransaction(vtx);</code></pre><p>This is the key conceptual point:</p><blockquote><p>The instruction is identical. The program is identical. Only the message format changes.</p></blockquote><p>v0 vs v0+ALT is purely a client-side message construction choice.</p><h2><strong>Summary</strong></h2><p>In this post, we took Solana&#8217;s transaction model out of theory and into practice.</p><p>We built a real program, constructed real messages, and sent the same instruction using v0 transactions with and without Address Lookup Tables. Nothing changed for the program. Everything changed for the client.</p><p>That&#8217;s the core lesson: <strong>scaling on Solana is a message-level problem, not a program-level one</strong>. Programs execute instructions. Clients decide how those instructions are wrapped, encoded, and optimized.</p><p>In the next post, we&#8217;ll focus on the tokens used here, SPL Token vs Token-2022 and how their accounts differ, how ATAs are derived, and why those details matter when building real systems.</p>]]></content:encoded></item><item><title><![CDATA[Understanding Solana - Part 5: Transaction, Serialization, Signatures, Fees, and Runtime Execution ]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/understanding-solana-part-5-transaction</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/understanding-solana-part-5-transaction</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Wed, 17 Dec 2025 17:08:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!D99Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>In the <a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-part4-instructions?r=2a5hnk">previous part</a> of this series, we unpacked how Solana structures <strong>instructions</strong> and <strong>messages</strong>, and why the network eventually outgrew the legacy message format.</p><p>We saw how v0 messages and Address Lookup Tables allowed developers to reference far more accounts than the original ~35 account limit, and how the message, not the transaction is the canonical record of &#8220;what should happen.&#8221;</p><p>But understanding message formats is only the first half of the story.</p><p>Because once you know how messages are built, the natural next question is:</p><p><strong>&#8220;What actually happens when this message becomes a real transaction on the network?&#8221;</strong></p><ul><li><p>How is it serialized?</p></li><li><p>How does a wallet sign it?</p></li><li><p>Why do blockhashes expire?</p></li><li><p>Where do fees come from?</p></li></ul><p>How does the runtime decide which instructions can run in parallel?</p><p>And why does simulation sometimes produce results that differ from real execution?</p><p>This post goes one layer deeper.</p><p>We&#8217;re going to follow a transaction end-to-end from the moment a message is assembled, through signature verification, account loading, and runtime execution, all the way to atomic state commit.</p></blockquote><div><hr></div><h2><strong>What is a Solana Transaction?</strong></h2><p>To interact with Solana, you submit a transaction. As we discussed in the <a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-part4-instructions?r=2a5hnk">previous post</a>, a transaction doesn&#8217;t hold raw instructions directly, it holds a <strong>message</strong> that defines which instructions will run, which accounts they require, and which recent blockhash anchors the transaction to the current chain state.</p><p>Each instruction inside that message represents a specific operation the network should perform. In the example below, the first transaction contains a message with a single instruction, while the second contains a message with three instructions that will execute sequentially: instruction 1, then instruction 2, then instruction 3.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D99Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D99Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 424w, https://substackcdn.com/image/fetch/$s_!D99Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 848w, https://substackcdn.com/image/fetch/$s_!D99Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 1272w, https://substackcdn.com/image/fetch/$s_!D99Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D99Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png" width="1198" height="562" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:562,&quot;width&quot;:1198,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/180901144?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!D99Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 424w, https://substackcdn.com/image/fetch/$s_!D99Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 848w, https://substackcdn.com/image/fetch/$s_!D99Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 1272w, https://substackcdn.com/image/fetch/$s_!D99Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F830e3cd4-39d6-43ad-9522-c8b7224123f7_1198x562.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The transaction object looks like that:</strong></p><ul><li><p><code>signatures</code>: An array of signatures</p></li><li><p><code>message</code>: Transaction information, including the list of instructions to be processed</p></li></ul><pre><code><code>pub struct Transaction {
    #[serde(with = &#8220;short_vec&#8221;)]
    pub signatures: Vec&lt;Signature&gt;,
    pub message: Message,
}</code></code></pre><p>Transactions have a total size limit of <a href="https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/packet/src/lib.rs#L29">1232</a> bytes. This limit includes both the <code>signatures</code> array and the <code>message</code> struct.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rdk_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rdk_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 424w, https://substackcdn.com/image/fetch/$s_!rdk_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 848w, https://substackcdn.com/image/fetch/$s_!rdk_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!rdk_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rdk_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png" width="1456" height="915" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:915,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2935314,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/180901144?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rdk_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 424w, https://substackcdn.com/image/fetch/$s_!rdk_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 848w, https://substackcdn.com/image/fetch/$s_!rdk_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 1272w, https://substackcdn.com/image/fetch/$s_!rdk_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb149628c-dbf3-465e-9f5e-997ed0354b6b_2012x1264.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Signatures</h3><p>The transaction holds an array of 64-byte signatures.</p><p>Each one is produced by signing the transaction&#8217;s <strong>Message</strong> with the private key of an account that appears as a required signer. Every <a href="https://andreyobruchkov1996.substack.com/i/179143694/messageheader">signer</a> referenced by any instruction must provide a corresponding signature in this array.</p><p>The first signature always comes from the fee-payer. This is also the transaction&#8217;s primary signature the one you use to look up the transaction on explorers and RPC endpoints.</p><p><strong>The signature struct looks like <a href="https://github.com/anza-xyz/agave/blob/v2.1.13/sdk/signature/src/lib.rs#L30">that</a>:</strong></p><pre><code><code> pub struct Signature(GenericArray&lt;u8, U64&gt;);</code></code></pre><h3><strong>Transactions Are Atomic</strong></h3><p>A Solana transaction is an all-or-nothing operation.</p><p>Even if a message contains multiple instructions, the runtime treats the entire set as a single atomic unit. This means:</p><ul><li><p>All instructions succeed &#8594; state is committed</p></li><li><p>Any instruction fails &#8594; <strong>nothing</strong> is committed</p></li></ul><p>There is no partial success.</p><p>This is why Solana must know <em>every</em> account that will be touched before execution begins. It allows the runtime to lock those accounts, validate the message, and ensure that the entire transaction can be applied in one deterministic step.</p><p>Atomicity is also what makes Solana composable. You can pack multiple program calls into a single transaction (e.g., swap &#8594; transfer &#8594; stake), and the chain guarantees the entire sequence either completes or doesn&#8217;t happen at all.</p><h2><strong>Transaction Fees</strong></h2><p>Every Solana transaction includes a fee paid in SOL. This fee has two components: a <strong>base fee</strong> and an optional <strong>prioritization fee</strong>.</p><p>The base fee compensates validators for executing the transaction, while the prioritization fee allows a user to pay extra to increase the likelihood that the current leader will include their transaction in the block.</p><h3>What is the base Fee</h3><p>Solana charges a base fee of <strong>5,000 lamports per signature</strong> in a transaction. The fee-payer the first signer in the message is responsible for covering this amount, and only System Program&#8211;owned accounts are eligible to pay it.</p><p>The network divides the base fee into two equal parts:</p><ul><li><p><strong>Half is burned</strong>, reducing the total SOL supply.</p></li><li><p><strong>Half is given to the validator</strong> that included the transaction.</p></li></ul><h3>What is the prioritization fee</h3><p>A prioritization fee is an optional amount you can add to improve the likelihood that the current leader includes your transaction. The validator receives <strong>100%</strong> of this fee.</p><p>You set the prioritization fee by specifying a <strong>compute unit (CU) limit</strong> and a <strong>CU price</strong> on the transaction.</p><p>The fee is computed as:</p><pre><code><code>Prioritization fee = CU limit &#215; CU price</code></code></pre><blockquote><p><strong>Note:</strong> Solana only charges a <em>prioritization fee</em> when you <strong>explicitly</strong> set one using the ComputeBudgetProgram. If you do not include a compute budget instruction, then:</p><ul><li><p><strong>CU price = 0</strong></p></li><li><p><strong>Prioritization fee = 0</strong></p></li><li><p>Only the <strong>base fee</strong> is charged (5,000 lamports per signature)</p></li></ul></blockquote><p>This value helps determine how your transaction ranks against others in the leader&#8217;s queue. Solana derives a transaction&#8217;s overall priority using:</p><pre><code><code>Priority = (Prioritization fee + Base fee) / (1 + CU limit + Signature CUs + Write lock CUs)</code></code></pre><p>A higher priority increases the chance your transaction is selected during contention.</p><h3><strong>Compute Unit Limit</strong></h3><p>By default, Solana allocates <strong><a href="https://github.com/anza-xyz/agave/blob/v2.1.13/compute-budget/src/compute_budget_limits.rs#L10">200,000 compute units per instruction</a></strong> and up to <strong><a href="https://github.com/anza-xyz/agave/blob/v2.1.13/compute-budget/src/compute_budget_limits.rs#L14">1.4 million compute units per transaction</a></strong>.</p><p>You can override these defaults by adding a </p><pre><code>SetComputeUnitLimit </code></pre><p>instruction from the ComputeBudgetProgram.</p><p>A practical way to choose an appropriate limit is:</p><ol><li><p><strong>Simulate the transaction</strong> to see how many CUs it actually consumes. <br>(coming soon in the next blogs)</p></li><li><p><strong>Add X% overhead</strong> to avoid unexpected failures.</p></li></ol><p>It&#8217;s important to note that the prioritization fee uses the <strong>requested</strong> CU limit, not the compute units the transaction ultimately consumes.</p><p>If you set the limit too high (or rely on the large default), you may end up paying for unused compute capacity.</p><h3><strong>Compute Unit Price</strong></h3><p>The compute unit price is an <strong>optional</strong> amount, specified in micro-lamports, that you&#8217;re willing to pay <strong>per requested compute unit</strong>.</p><p>You can think of it as a tip that incentivizes the current leader to include your transaction ahead of others.</p><p>To set a CU price, include a </p><pre><code>SetComputeUnitPrice </code></pre><p>instruction from the ComputeBudgetProgram.</p><p>If you do not specify a CU price, it defaults to <strong>zero</strong>, and the transaction will not pay a prioritization fee, only the base fee.</p><blockquote><p><strong>Note:</strong> Compute Budget program instructions are ignored when calculating the prioritization fee. You can see this <a href="https://github.com/solana-labs/solana/blob/ca115594ff61086d67b4fec8977f5762e526a457/program-runtime/src/compute_budget.rs#L207-L209">here</a>.</p></blockquote><h3><strong>Example: lets take a look at this transaction</strong></h3><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!n-NJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n-NJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 424w, https://substackcdn.com/image/fetch/$s_!n-NJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 848w, https://substackcdn.com/image/fetch/$s_!n-NJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 1272w, https://substackcdn.com/image/fetch/$s_!n-NJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n-NJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png" width="1456" height="518" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:518,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:182394,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/180901144?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!n-NJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 424w, https://substackcdn.com/image/fetch/$s_!n-NJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 848w, https://substackcdn.com/image/fetch/$s_!n-NJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 1272w, https://substackcdn.com/image/fetch/$s_!n-NJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fee99c2f6-070a-4442-bba9-e99b0be9bb62_2940x1046.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vXWi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vXWi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 424w, https://substackcdn.com/image/fetch/$s_!vXWi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 848w, https://substackcdn.com/image/fetch/$s_!vXWi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 1272w, https://substackcdn.com/image/fetch/$s_!vXWi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vXWi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png" width="1456" height="501" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:501,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:165840,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://andreyobruchkov1996.substack.com/i/180901144?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vXWi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 424w, https://substackcdn.com/image/fetch/$s_!vXWi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 848w, https://substackcdn.com/image/fetch/$s_!vXWi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 1272w, https://substackcdn.com/image/fetch/$s_!vXWi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b1e6bb7-8f6d-4f11-91e3-d62cf6d1e482_2940x1012.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We see that the Priority fee here is 0.000004 SOL, and the total fee is 0.000009 SOL</p><p><strong>lets understand how it is computed:</strong></p><p>as we said before,  By default, Solana allocates <strong>200,000 compute units per instruction.</strong></p><p>In this transaction we have here 2 instructions, but as said previously the</p><pre><code>ComputeBudget</code></pre><p>Instructions are omitted from the calculation, so we end up only with 1 instruction and a price of 20000 micro-lamports which is 0.02 lamports/CU.</p><p>so the price will be: 200,000 * 0.02 = 4000 lamports, which is 0.000004 SOL.</p><p>In addition we have 1 transaction signer on the transfer instruction, which is costs 0.000005 SOL.</p><pre><code>Total fee = BaseFee + PriorityFee =  0.000004 + 0.000005 = 0.000009</code></pre><h2><strong>How the Wallet Actually Signs</strong></h2><p>A common point of confusion is how a signer can sign a message when the message itself already includes that signer&#8217;s account. This feels circular at first, how can the wallet include the signer in the message before the signer has produced the signature?</p><p>The key is understanding that the <strong>message contains the signer&#8217;s public key, not their signature</strong>.</p><p>The signature is added <strong>after</strong> the message is finalized.</p><h3><strong>Here&#8217;s how the process actually works:</strong></h3><p><strong>1. The wallet builds the complete message first</strong></p><p>Before any signature exists, the wallet constructs the message in its final form:</p><ul><li><p>the message header (signer counts),</p></li><li><p>all account public keys,</p></li><li><p>the recent blockhash,</p></li><li><p>the compiled instructions.</p></li></ul><p>At this stage, the message already includes the signer&#8217;s <strong>public key</strong>, because the runtime needs to know:</p><ul><li><p>which accounts must sign,</p></li><li><p>the order of signer accounts,</p></li><li><p>which signature corresponds to which key.</p></li></ul><p>No signatures exist yet.</p><p>The message is simply a byte array describing exactly what will be executed.</p><p><strong>2. The signer signs the message bytes</strong></p><p>Once the message is fully constructed, the wallet signs the <strong>exact serialized message bytes</strong>:</p><pre><code><code>signature = Ed25519_sign(private_key, message_bytes)</code></code></pre><p>Any change to the message, however small invalidates the signature.</p><p>Because the message contains only <strong>public keys</strong>, signing it has no circular dependency.</p><p>It&#8217;s like signing a legal document that lists your name:</p><p>your name is part of the document, but your signature is added afterward.</p><p><strong>3. The signature is inserted into the transaction</strong></p><p>After signing, the signature is placed into the transaction&#8217;s signature array according to the signer&#8217;s position in account_keys:</p><ul><li><p>signature at index 0 &#8594; first signer (fee-payer)</p></li><li><p>signature at index 1 &#8594; second signer</p></li><li><p>etc.</p></li></ul><p>The signature does <strong>not</strong> modify the message.</p><p>The message is now locked, changing it would break every signature.</p><p>Transaction packs all this data into its structure and sends the built transaction to the node.</p><p><strong>Overview</strong></p><ul><li><p><strong>Messages never contain signatures</strong>, only public keys.</p></li><li><p><strong>Signatures are created over the final message</strong>, not before it.</p></li><li><p><strong>Signatures live outside the message</strong>, in the transaction wrapper.</p></li></ul><p>This separation allows:</p><ul><li><p>deterministic signing,</p></li><li><p>canonical verification,</p></li><li><p>simple replay protection,</p></li><li><p>and lightweight transactions.</p></li></ul><p>There is no circular dependency, signers sign the message, and the message simply identifies who the signers are.</p><h2><strong>How Solana Executes a Transaction</strong></h2><p>Up to this point, we&#8217;ve focused on how a transaction is constructed, how messages are formed, how signatures are produced, how fees are calculated, and how the wallet packages everything together.</p><p>Once a validator receives a transaction, Solana processes it through a series of steps that ensure the transaction is valid, safe to run in parallel, and either fully succeeds or fully fails.</p><p><strong>1. Signature Verification</strong></p><ul><li><p>The validator hashes the message.</p></li><li><p>Each signature is checked against its corresponding public key.</p></li><li><p>Any invalid signature &#8594; the transaction is immediately rejected.</p></li></ul><p><strong>2. Account Loading</strong></p><ul><li><p>All accounts listed in the message (and ALTs, if used) are fetched.</p></li><li><p>The runtime checks:</p><ul><li><p>account existence</p></li><li><p>ownership rules</p></li><li><p>signer flags</p></li><li><p>read/write permissions</p></li></ul></li><li><p>If anything is invalid &#8594; the transaction stops here.</p></li></ul><p><strong>3. Account Locking</strong></p><ul><li><p>Writable accounts get a <strong>write lock</strong>.</p></li><li><p>Read-only accounts get a <strong>read lock</strong>.</p></li><li><p>These locks ensure no conflicting transactions execute in parallel.</p></li><li><p>If required accounts are already locked &#8594; the transaction may be delayed or skipped.</p></li></ul><p><strong>4. Instruction Execution</strong></p><ul><li><p>Instructions run <strong>in order</strong>, one by one.</p></li><li><p>Each instruction:</p><ul><li><p>invokes the target program</p></li><li><p>updates compute usage</p></li><li><p>may perform CPIs</p></li></ul></li><li><p>If any instruction fails &#8594; the entire transaction fails.</p></li></ul><p><strong>5. Compute Budget Enforcement</strong></p><ul><li><p>The runtime tracks consumed compute units.</p></li><li><p>If the CU limit is exceeded:</p><ul><li><p>execution stops</p></li><li><p>the transaction fails</p></li><li><p>the fee-payer still pays the fee</p></li></ul></li></ul><p><strong>6. Atomic Commit or Rollback</strong></p><ul><li><p>If all instructions succeed:</p><ul><li><p>account changes are committed atomically</p></li><li><p>locks are released</p></li></ul></li><li><p>If any instruction fails:</p><ul><li><p><strong>no state changes are applied</strong></p></li><li><p>the transaction is recorded as failed</p></li><li><p>the fee is still charged</p></li></ul></li></ul><h2>Summary</h2><p>A Solana transaction is more than a signed bundle of instructions. It&#8217;s a structured request that moves through a precise execution pipeline. The message defines what should happen, signatures authorize who approved it, and the runtime ensures everything is valid, safe to run in parallel, and fully atomic.</p><p>You&#8217;ve seen how fees are calculated, how compute budgets influence prioritization, how wallets sign messages, and how validators process transactions from signature checks to final commit. Together, these pieces form the foundation of Solana&#8217;s high-throughput, parallel execution model.</p><p>Understanding this flow gives you the tools to reason about transaction behavior, debug failures, and write programs that interact with the network reliably and efficiently.</p>]]></content:encoded></item><item><title><![CDATA[Understanding Solana - Part4: Instructions and Messages ]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/understanding-solana-part4-instructions</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/understanding-solana-part4-instructions</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 06 Dec 2025 18:10:11 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NenC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Before now, we explored Solana&#8217;s parallel architecture and account model how data lives on-chain, who can modify it, and why Sealevel enables true concurrency.</p><p>But accounts alone don&#8217;t change state. Every update on Solana, from transferring tokens to creating PDAs, happens through <strong>transactions</strong> composed of <strong>instructions</strong>.</p><p>In this post, we&#8217;ll unpack what really happens between a wallet click and on-chain execution:</p><p>1. how a transaction is structured under the hood,</p><p>2. how the runtime routes instructions to programs,</p><p>3. how accounts are locked for safe parallel execution,</p><p>4. what &#8220;signing,&#8221; &#8220;message,&#8221; and &#8220;recent blockhash&#8221; actually mean.</p><p>By the end, you&#8217;ll understand how Solana encodes, validates, and executes transactions step by step and how to build, simulate, and send them directly from your code.</p></blockquote><div><hr></div><h3>Instructions</h3><p>Instructions are the core unit of execution on Solana. Think of each instruction as a function call exposed by an on-chain program. Every program defines its own instruction set, the specific actions it can perform. When you interact with the network, you don&#8217;t call programs directly, you package one or more of their instructions into a transaction, sign it, and submit it for execution.</p><p><strong>Instruction consists of</strong></p><ul><li><p>ProgramId</p></li><li><p>Accounts</p></li><li><p>Instruction specific data</p></li></ul><p><strong>ProgramId</strong></p><p>The on-chain Id of the program (address) that contains the logic of the instruction</p><p><strong>Accounts</strong></p><p>Each instruction includes an array of AccountMeta entries, metadata describing every account it will read from or write to. By explicitly listing these accounts, Solana&#8217;s runtime can determine which instructions are independent and safely execute them in parallel, as long as they don&#8217;t modify the same account.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NenC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NenC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 424w, https://substackcdn.com/image/fetch/$s_!NenC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 848w, https://substackcdn.com/image/fetch/$s_!NenC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 1272w, https://substackcdn.com/image/fetch/$s_!NenC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NenC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png" width="1036" height="330" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:330,&quot;width&quot;:1036,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NenC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 424w, https://substackcdn.com/image/fetch/$s_!NenC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 848w, https://substackcdn.com/image/fetch/$s_!NenC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 1272w, https://substackcdn.com/image/fetch/$s_!NenC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ea9925-0653-43b0-af33-4c36a218e58a_1036x330.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p><code>is_signer</code>: Set to <code>true</code> if the account must sign the transaction</p></li><li><p><code>is_writable</code>: Set to <code>true</code> if the instruction modifies the account&#8217;s data</p></li><li><p><code>pubkey</code>: The account&#8217;s public key address</p></li></ul><p><strong>Data</strong></p><p>The instruction&#8217;s data field is a sequence of bytes that tells the program which specific function to execute and includes the parameters needed for that call.</p><p><strong>Example (</strong>Simple<strong> </strong>0.01<strong>SOL </strong>Transfer<strong>)</strong></p><pre><code>{
  &#8220;program_id&#8221;: &#8220;11111111111111111111111111111111&#8221;,
  &#8220;accounts&#8221;: [
    {
      &#8220;pubkey&#8221;: &#8220;6uR7N6oDgE3vJXvM6Eh4xVHw2g7o7YhA7FJxC4pXcZtT&#8221;,
      &#8220;is_signer&#8221;: true,
      &#8220;is_writable&#8221;: true
    },
    {
      &#8220;pubkey&#8221;: &#8220;3Nq8yVbGz7KpYw9sT6rF2LmHc4Qz5XvA1uJd8BvCzRy&#8221;,
      &#8220;is_signer&#8221;: false,
      &#8220;is_writable&#8221;: true
    }
  ],
  &#8220;data&#8221;: [2, 0, 0, 0, 128, 150, 152, 0, 0, 0, 0, 0]
}</code></pre><p><strong>program_id</strong></p><p>That&#8217;s the <strong>System Program</strong> Solana&#8217;s built-in program for managing native SOL transfers, creating accounts, and assigning ownership. Every validator includes this program by default. This tells the runtime <em><strong>Call the System Program to execute one of its functions</strong></em></p><p><strong>accounts:</strong></p><p><code>6uR7&#8230;ZtT</code>: Sender, must sign and can be modified (lamports will be deducted).</p><p><code>3Nq8&#8230;zRy</code>: Recipient, writable because lamports will be added, but no signature required.</p><p><strong>data:</strong></p><p>Each data variant encoded as <code>[variant_index:u32][variant_payload]</code> <br>in our case:</p><pre><code>pub enum SystemInstruction {
    CreateAccount { ... },
    Assign { ... },
    Transfer { lamports: u64 },
    ...
}</code></pre><p><em><strong>Transfer </strong></em>variant is at index 2 and its payload is u64 (8-bytes)</p><pre><code>[2, 0, 0, 0, 128, 150, 152, 0, 0, 0, 0, 0]

which is:

[02 00 00 00 | 80 96 98 00 00 00 00 00] // 4 + 8 = 12</code></pre><p><code>[0:4)</code> bytes</p><ul><li><p><em><strong>Type:</strong></em> u32</p></li><li><p><strong>Value: </strong>2&#8202;: &#8202;in this <a href="https://github.com/solana-labs/solana/blob/master/sdk/program/src/system_instruction.rs#L137">program source code</a> we can see that <strong>Transfer </strong>is at index 2 in the <strong>SystemInstruction </strong>enum</p></li></ul><p><code>[4:12)</code> bytes</p><ul><li><p><em><strong>Type:</strong></em> u64</p></li><li><p><strong>Value: </strong>10000000 lamports</p></li></ul><pre><code>[2, 0, 0, 0]  &#8594; discriminant = 2
[128, 150, 152, 0, 0, 0, 0, 0] &#8594; bytes to hex
[80 96 98 00 00 00 00 00] &#8594; hex representation
[0x00000000989680] &#8594; 0x00989680 = 10_000_000 lamports (0.01 SOL)</code></pre><h3>Messages</h3><p>A <strong>transaction</strong> on Solana is more than just a list of instructions it&#8217;s a <strong>message</strong> signed by one or more accounts. The message defines exactly <strong>what will run</strong>, <strong>which accounts are involved</strong>, and <strong>which recent blockhash</strong> anchors it to the chain&#8217;s current state.</p><p><strong>Solana signs the message, not each instruction individually</strong>. That design makes transactions compact and verifiable: a validator only needs to check that the signers authorized the same message bytes the network received.</p><p>Once signatures are verified, the runtime executes the instructions in order and commits all changes atomically either everything succeeds or the whole transaction reverts.</p><p>In other words:</p><ul><li><p><em>The <strong>message</strong> is the canonical record of &#8220;what should happen&#8221;</em></p></li><li><p><em>The <strong>signature</strong> just proves &#8220;who approved it&#8221;</em></p></li></ul><h4>Legacy and Versioned Messages</h4><p>Originally, all Solana transactions used a single message format, now referred to as the <strong>legacy message</strong>. It worked well for simple transfers and small programs, but every transaction must list <em>all</em> accounts it will read or write (For the purpose of parallel execution).</p><p>Since each account address is 32 bytes, the total message size grows quickly, and Solana enforces a strict upper bound (&#8776; 1232 bytes for serialized transactions) to fit inside the network&#8217;s MTU. That limited legacy transactions to about <strong>35 accounts</strong>, which became a problem for complex, composable DeFi protocols that interact with many programs at once.</p><p>To solve this, Solana introduced <strong>versioned transactions</strong>, starting with <strong>v0 messages</strong>, along with a new on-chain program called the <strong>Address Lookup Table (ALT)</strong> program.</p><p>Lookup tables let developers store groups of addresses on-chain and reference them later by small 1-byte indexes instead of full 32-byte keys. The transaction can then &#8220;look up&#8221; additional accounts during execution without exceeding the size limit.</p><h4>Legacy Messages</h4><pre><code>pub struct Message {
    pub header: MessageHeader,
    pub account_keys: Vec&lt;Address&gt;,
    pub recent_blockhash: Hash,
    pub instructions: Vec&lt;CompiledInstruction&gt;,
}

pub struct MessageHeader {
    pub num_required_signatures: u8,
    pub num_readonly_signed_accounts: u8,
    pub num_readonly_unsigned_accounts: u8,
}

// As learned previously
pub struct CompiledInstruction {
    pub program_id_index: u8,
    pub accounts: Vec&lt;u8&gt;,
    pub data: Vec&lt;u8&gt;,
}</code></pre><blockquote><p><em><strong>Note:</strong></em> the <code>#[serde(with = &#8220;short_vec&#8221;)]</code> is important and we will see later why</p></blockquote><p>can hold about 35 accounts (35 * 32(address size) = 1120bytes out of 1232) we can see that it is holds a fixed header, accounts, blockhash, instructions and can be used for simple or medium transactions.</p><h4>Versioned Messages</h4><pre><code>pub enum VersionedMessage {
    Legacy(LegacyMessage),
    V0(v0::Message),
}

pub struct Message {
  pub header: MessageHeader,
  pub account_keys: Vec&lt;Pubkey&gt;,
  pub recent_blockhash: Hash,
  pub instructions: Vec&lt;CompiledInstruction&gt;,
  /// List of address table lookups used to load additional accounts
  /// for this transaction.
  #[serde(with = &#8220;short_vec&#8221;)]
  pub address_table_lookups: Vec&lt;MessageAddressTableLookup&gt;,
}

pub struct MessageAddressTableLookup {
  pub account_key: Pubkey,
  #[serde(with = &#8220;short_vec&#8221;)]
  pub writable_indexes: Vec&lt;u8&gt;,
  #[serde(with = &#8220;short_vec&#8221;)]
  pub readonly_indexes: Vec&lt;u8&gt;,
}</code></pre><p>Transactions must fit in ~<strong>1232 bytes</strong> of packet payload. Listing every account inline (32 bytes each) caps legacy messages at ~<strong>35 accounts</strong> after signatures/overhead. V0 introduces <strong>Address Lookup Tables (ALTs)</strong> so a message can reference many accounts by <strong>1-byte indexes</strong> instead of inlining 32-byte pubkeys.</p><p><strong>How address lookup works</strong></p><p>At runtime, the validator constructs a single <strong>resolved account list</strong> that instructions index into:</p><pre><code>resolved_keys =
    [ message.account_keys
    , looked_up_writable_keys (from all ALTs, in order)
    , looked_up_readonly_keys (from all ALTs, in order)
    ]</code></pre><p>Each MessageAddressTableLookup points to a specific on-chain ALT (account_key).</p><p><em><strong>writable_indexes</strong></em> and <em><strong>readonly_indexes</strong></em> are <strong>u8 indexes</strong> into that ALT&#8217;s stored addresses.</p><p>The runtime fetches those pubkeys and <strong>appends</strong> them to resolved_keys in two groups:</p><ul><li><p>all <strong>writable</strong> lookups (preserves order across tables)</p></li><li><p>all <strong>readonly</strong> lookups (preserves order across tables).</p></li></ul><p><em><strong>program_id</strong></em> and <em><strong>accounts</strong></em> still point into this <strong>combined</strong> list exactly like legacy messages.</p><p><strong>how v0 is identified</strong></p><p>Versioned transactions use a <strong>leading version bit</strong> in the message encoding:</p><ul><li><p>If the top bit of the first byte is <strong>set</strong>, the remaining bits encode a <strong>version number</strong> (v0 = 0).</p></li><li><p>If not set, it&#8217;s treated as a <strong>legacy</strong> message.</p></li></ul><p>You won&#8217;t usually touch this directly, SDKs handle it but it&#8217;s why v0 and legacy can coexist.</p><p><strong>Size implications</strong></p><p>v0 adds small overhead but dramatically reduces inline key bytes when many accounts are needed</p><p><strong>Added overhead per transaction (approx) this is at least sizes:</strong></p><ul><li><p><strong>+1 byte</strong>: version tag</p></li><li><p><strong>+1 byte</strong>: address_table_lookups length</p></li><li><p><strong>+34 bytes per lookup table</strong> (32 for table pubkey + 1 + 1 for lengths of writable/readonly index arrays)</p></li><li><p><strong>+1 byte per looked-up index</strong> (each index into the ALT)</p></li></ul><p><strong>Saved space:</strong> each looked-up address replaces a <strong>32-byte</strong> inline pubkey with a <strong>1-byte</strong> index in the message.</p><p>When you reference dozens/hundreds of accounts, this tradeoff wins easily</p><h4>Limits and rules to remember</h4><ul><li><p><strong>256 max entries per ALT.</strong> Indexes are u8.</p></li><li><p><strong>Up to 256 unique accounts</strong> can be loaded overall (since compiled instruction account indexes are u8 as well).</p></li><li><p><strong>Signers cannot come from ALTs.</strong> All <strong>signer keys must appear in account_keys</strong> so their signatures can be verified efficiently.</p></li><li><p><strong>No duplicates.</strong> The same account may not be loaded more than once across account_keys and ALT lookups.</p></li><li><p><strong>ALT availability:</strong> Newly appended ALT entries become usable <strong>after one slot</strong> (warm-up).</p></li><li><p><strong>ALT lifecycle:</strong> Tables must be rent-exempt, are append-only, can be deactivated, and only closed after a cooldown to avoid censorship/race issues</p></li></ul><h4>Lets make sense of the numbers and why those are the limits</h4><p>Lets look at the confusing statements from before</p><p><em><strong>256 max entries per ALT. Indexes are u8:</strong></em><strong><br></strong>Each Address Lookup Table (ALT) can store up to <strong>256 addresses</strong> because the <strong>indexes used to reference them are u8 (1 byte)</strong>.</p><pre><code>pub writable_indexes: Vec&lt;u8&gt;,
pub readonly_indexes: Vec&lt;u8&gt;,</code></pre><p>Each element in those vectors is a single byte &#8594; range 0&#8211;255.<br>So when we say:</p><pre><code>writable_indexes = [0, 2, 4, 6]</code></pre><p>the runtime loads the <strong>0th, 2nd, 4th, and 6th</strong> addresses from that table&#8217;s on-chain storage. Hence <em><strong>Max 256</strong> because <strong>u8</strong> can encode 0&#8211;255 possible values, means <strong>256 unique entries</strong> per lookup table <br>(this is impossible: </em><code>writable_indexes = [0, 2, 310]</code><em>).</em></p><p><strong>1 byte: address_table_lookups length<br></strong>The first question that can come up to mind is, why only the lenght only 1 byte if it is a vector which might be unlimited. the magic is in <br><code>#[serde(with = &#8220;short_vec&#8221;)]</code><em> which means <br></em>This Vec&lt;&#8230;&gt; in Solana&#8217;s serialized structs uses a <strong>short_vec</strong> format.<br>That&#8217;s how Bincode (via the short_vec module) serializes variable-length arrays efficiently<code><br>[length (1-9 bytes)][elements...]<br></code>For small vectors (length &lt; 128), the <strong>length fits in a single byte<br></strong>If you have 3 lookup tables, that length byte would literally contain 0x03.<br>It means there&#8217;s <strong>one byte added to encode how many lookup tables are attached</strong>, before serializing their contents.</p><p><strong>34 bytes per lookup table (32 + 1 + 1)<br></strong>This is the same the prev statement, take a look that</p><pre><code>pub struct MessageAddressTableLookup {
  pub account_key: Pubkey,
  #[serde(with = &#8220;short_vec&#8221;)]
  pub writable_indexes: Vec&lt;u8&gt;,
  #[serde(with = &#8220;short_vec&#8221;)]
  pub readonly_indexes: Vec&lt;u8&gt;,
}</code></pre><p>serialized with <code>#[serde(with = &#8220;short_vec&#8221;)]</code><em> means that every </em>MessageAddressTableLookup needs at least 34 bytes + N-bytes for each entry.</p><h4>MessageHeader</h4><p>The header tells the runtime <strong>how many accounts must sign</strong>, and which accounts are <strong>read-only</strong>.</p><p>It&#8217;s the preamble the runtime uses to understand how to interpret the account_keys slice.</p><p><strong>num_required_signatures (u8)</strong></p><p>How many of the first account_keys <strong>must provide signatures</strong>.</p><ul><li><p>If this is 1, the first key in account_keys is the signer.</p></li><li><p>If this is 2, the first two keys are signers, etc.</p></li></ul><p>These signatures come from the transaction&#8217;s signature array. Every signer must match with its position in the account_keys.</p><p><strong>num_readonly_signed_accounts</strong> <strong>(u8)</strong></p><p>Among the signer accounts (the <strong>first num_required_signatures</strong>), how many of them are <strong>read-only</strong>. They can be <strong>read</strong>, but the program <strong>cannot modify</strong> their account data or lamports. This prevents accidental or malicious modification of signer accounts when they don&#8217;t need to be writable.</p><p>Example:</p><ul><li><p>A user signs a transaction using their wallet (signer),</p></li><li><p>But that wallet account is never written to so it should remain read-only.</p></li></ul><p><strong>num_readonly_unsigned_accounts (u8)</strong></p><p>Among the <strong>non-signers</strong> (the remaining accounts), how many of them are <strong>read-only</strong>.</p><p>Why this matters:<br>Some accounts are passed to a program as read-only e.g., system program, token program, metadata program. Marking them read-only improves security and performance.</p><h4>recent_blockhash</h4><p>This field prevents <strong>replay attacks</strong> and sets a <strong>liveness requirement</strong>.</p><p>Solana does not use nonces.</p><p>Instead, every transaction must include a <strong>recent blockhash</strong> from the last ~150 blocks (&#8776; 2 minutes).</p><h3>Why it&#8217;s required</h3><p><strong>Anti-replay<br></strong>If someone copies your signed transaction and tries to submit it again, it will be rejected because the blockhash is too old.</p><p><strong>Transaction expiration<br></strong>Solana requires transactions to be &#8220;fresh.&#8221;<br>If the blockhash is too old, the validator won&#8217;t accept the transaction.</p><p><strong>Fork commitment<br></strong>Validators agree that transactions built on a recent blockhash belong to the current fork, reducing ambiguity.</p><h3>Summary</h3><p>You should now understand how Solana structures the logic behind every transaction. An <strong>instruction</strong> defines what to run, the program, accounts, and data, while a <strong>message</strong> bundles those instructions together into an atomic unit that can be signed and verified. Legacy messages work for most cases, but <strong>v0 messages</strong> extend the format with <strong>Address Lookup Tables</strong>, allowing many more accounts to be referenced through compact 1-byte indexes.<br>In short, you now know how Solana encodes what happens in a transaction</p>]]></content:encoded></item><item><title><![CDATA[Understanding Solana - Part 3: Anchor Accounts, Seeds, Bumps, PDAs, and How the Client Really Works]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/understanding-solana-part-3-anchor</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/understanding-solana-part-3-anchor</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Tue, 18 Nov 2025 14:59:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!958h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>When you start building on Solana with Anchor, the hardest part isn&#8217;t Rust it&#8217;s understanding how accounts, PDAs, bumps, and the client all fit together.</p><p>In this post we&#8217;ll walk through a minimal Anchor program that creates, updates, and closes a user-owned PDA, and then call it end-to-end from a TypeScript script. Along the way you&#8217;ll learn how Anchor derives accounts, when you need to pass them manually, why storing the bump matters, and how the client auto-fills everything based on your constraints.</p></blockquote><div><hr></div><h3>What is Anchor and How it represents accounts and constraints in Rust</h3><p><strong><a href="https://www.anchor-lang.com/docs/installation">Anchor</a></strong> is the most popular framework for Solana programs. Think of it as:</p><ul><li><p>a <strong>Rust DSL</strong> for accounts &amp; constraints (declarative checks instead of manual <code>AccountInfo</code> plumbing),</p></li><li><p>a <strong>toolchain</strong> (build, deploy, test, IDL generation),</p></li><li><p>and a <strong>TypeScript client</strong> that auto-types your program interface from the IDL.</p></li></ul><p>In practice, Anchor gives you:</p><ul><li><p><code>#[account]</code> Rust structs that map 1:1 to on-chain <strong>data accounts</strong>,</p></li><li><p><code>#[derive(Accounts)]</code> contexts with <strong>constraints</strong> like <code>init</code>, <code>seeds</code>, <code>bump</code>, <code>has_one</code>, <code>close</code> (Will be covered soon)</p></li><li><p>auto-generated <strong>IDL</strong> + a TypeScript client (<code>@coral-xyz/anchor</code>) to call your instructions safely.</p></li></ul><h3>Installation</h3><h4><strong>Prereqs</strong></h4><ul><li><p>Rust toolchain (rustup)</p></li><li><p>Node 18+ (20 recommended)</p></li><li><p>Yarn or npm (or pnpm)</p></li></ul><h4>Install Solana CLI (Agave toolchain)</h4><p>This section will automatically install:</p><ul><li><p><strong>solana CLI</strong></p></li><li><p><strong>solana-test-validator</strong></p></li><li><p><strong>build-bpf pipeline</strong></p></li><li><p><strong>loader</strong></p></li><li><p><strong>runtime libs</strong></p></li><li><p><strong>all tooling that Anchor expects</strong></p></li></ul><blockquote><p><strong>Note: </strong>This installs into <code>$HOME/.local/share/solana/install/active_release/bin</code></p></blockquote><pre><code># 1. Download/install the Agave installer script
sh -c &#8220;$(curl -sSfL https://release.anza.xyz/stable/install)&#8221;

# 2. Setting up the PATH env var so everything work together
export PATH=&#8221;$HOME/.local/share/solana/install/active_release/bin:$PATH&#8221;

# To make it permanent (zsh), or change zshrc to your shell
# echo &#8216;export PATH=&#8221;$HOME/.local/share/solana/install/active_release/bin:$PATH&#8221;&#8217; &gt;&gt; ~/.zshrc
# source ~/.zshrc

# if avm --version is not 0.31.1. do this:
# avm install 0.31.1
# avm use 0.31.1

# 3. Verify installation
cargo-build-sbf --version
# You should see something like:
# solana-cargo-build-sbf 3.0.10
# platform-tools v1.51
# rustc 1.84.1</code></pre><p><strong>Scaffold a New Program</strong></p><pre><code>anchor init solana_accounts
cd solana_accounts</code></pre><p>We will go over a code now that should be pasted in <code>programs/solana_accounts/src/lib.rs</code></p><p>more about anchor directory layout can be found <a href="https://www.anchor-lang.com/docs/basics/program-structure">here</a>. We will cover only the interesting part: How the program behaves <strong>on-chain.</strong></p><p><strong>Rust code</strong></p><pre><code>use anchor_lang::prelude::*;

declare_id!(&#8221;&lt;YOUR-PROGRAM-ID&gt;&#8221;);

#[program]
pub mod solana_accounts {
    use super::*;

    /// Create a PDA for the user and store their name + creation time.
    pub fn create_user(ctx: Context&lt;CreateUser&gt;, name: String) -&gt; Result&lt;()&gt; {
        // Enforce max length in BYTES (UTF-8). Emojis count as multiple bytes.
        require!(
            name.as_bytes().len() &lt;= UserAccount::MAX_NAME,
            ErrorCode::NameTooLong
        );

        let user = &amp;mut ctx.accounts.user_account;
        user.owner = ctx.accounts.authority.key();
        user.name = name.clone(); // fits because we sized with MAX_NAME
        user.created_at = Clock::get()?.unix_timestamp;
        user.bump = ctx.bumps.user_account;

        msg!(&#8221;&#9989; Created user PDA: {}&#8221;, user.key());
        msg!(&#8221;   Owner: {}&#8221;, user.owner);
        msg!(&#8221;   Name: {}&#8221;, name);
        msg!(&#8221;   Created at: {}&#8221;, user.created_at);
        Ok(())
    }

    /// Update name field (only the owner can do it).
    /// No realloc needed as long as new_name.len() &#8804; MAX_NAME.
    pub fn update_name(ctx: Context&lt;UpdateUser&gt;, new_name: String) -&gt; Result&lt;()&gt; {
        require!(
            new_name.as_bytes().len() &lt;= UserAccount::MAX_NAME,
            ErrorCode::NameTooLong
        );

        let user = &amp;mut ctx.accounts.user_account;
        msg!(&#8221;&#9999;&#65039; Updating user: {}&#8221;, user.key());
        msg!(&#8221;   Old name: {}&#8221;, user.name);
        user.name = new_name.clone();
        msg!(&#8221;   New name: {}&#8221;, new_name);
        Ok(())
    }

    /// Close account and refund rent to the authority (owner).
    pub fn close_user(_ctx: Context&lt;CloseUser&gt;) -&gt; Result&lt;()&gt; {
        msg!(&#8221;&#129529; Closed user PDA and refunded rent.&#8221;);
        Ok(())
    }
}

#[account]
#[derive(InitSpace)]
pub struct UserAccount {
    pub owner: Pubkey,              // 32
    #[max_len(32)]
    pub name: String,               // 4 + up to 32 bytes
    pub created_at: i64,            // 8
    pub bump: u8,                   // 1
}

impl UserAccount {
    pub const MAX_NAME: usize = 32;
    // Total space to allocate at init time:
    // 8 (discriminator) + INIT_SPACE computed by Anchor from the struct
    pub const SPACE: usize = 8 + Self::INIT_SPACE;
}

#[derive(Accounts)]
pub struct CreateUser&lt;&#8217;info&gt; {
    #[account(mut)]
    pub authority: Signer&lt;&#8217;info&gt;,

    /// PDA: seeds = [&#8221;user&#8221;, authority]
    #[account(
        init,
        payer = authority,
        space = UserAccount::SPACE,
        seeds = [b&#8221;user&#8221;, authority.key().as_ref()],
        bump
    )]
    pub user_account: Account&lt;&#8217;info, UserAccount&gt;,

    pub system_program: Program&lt;&#8217;info, System&gt;,
}

#[derive(Accounts)]
pub struct UpdateUser&lt;&#8217;info&gt; {
    pub authority: Signer&lt;&#8217;info&gt;,

    #[account(
        mut,
        constraint = user_account.owner == authority.key(),
        seeds = [b&#8221;user&#8221;, authority.key().as_ref()],
        bump = user_account.bump
    )]
    pub user_account: Account&lt;&#8217;info, UserAccount&gt;,
}

#[derive(Accounts)]
pub struct CloseUser&lt;&#8217;info&gt; {
    #[account(mut)]
    pub authority: Signer&lt;&#8217;info&gt;,

    #[account(
        mut,
        close = authority,
        constraint = user_account.owner == authority.key(),
        seeds = [b&#8221;user&#8221;, authority.key().as_ref()],
        bump = user_account.bump
    )]
    pub user_account: Account&lt;&#8217;info, UserAccount&gt;,
}

#[error_code]
pub enum ErrorCode {
    #[msg(&#8221;Name too long (max 32 bytes).&#8221;)]
    NameTooLong,
}</code></pre><p>Overview:</p><p><strong>What it stores:</strong> A per-wallet UserAccount at a PDA derived from [&#8220;user&#8221;, authority].</p><h4>Instructions:</h4><ul><li><p><code>create_user(name)</code>: creates and initializes the PDA with <code>owner</code>, <code>name</code>, <code>created_at</code>, <code>bump</code>.</p></li><li><p><code>update_name(new_name)</code>: only the <strong>owner</strong> can change name, within the pre-allocated limit</p></li><li><p><code>close_user()</code>: closes the PDA and refunds rent to the owner.</p></li></ul><h4><strong>Program Id (</strong>why it matters)</h4><pre><code>declare_id!(&#8221;&lt;YOUR-PROGRAM-ID&gt;&#8221;);</code></pre><ul><li><p>Anchor <strong>bakes this value into the binary</strong>.</p></li><li><p>At runtime, it <strong>must equal</strong> the on-chain program account you&#8217;re invoking, or you&#8217;ll get <em><strong>DeclaredProgramIdMismatch</strong></em>.</p></li><li><p>Make sure <strong>declare_id!</strong>, <strong>Anchor.toml</strong>, and your client all use the <strong>same</strong> pubkey <strong>before</strong> you build/deploy.</p></li></ul><h4><strong>The account: layout &amp; size</strong></h4><pre><code>#[account]
#[derive(InitSpace)]
pub struct UserAccount {
    pub owner: Pubkey,              // 32
    #[max_len(32)]
    pub name: String,               // 4 + up to 32 bytes
    pub created_at: i64,            // 8
    pub bump: u8,                   // 1
}</code></pre><ul><li><p>Anchor will compute UserAccount::INIT_SPACE.</p></li><li><p>When initializing, allocate <strong>8 + UserAccount::INIT_SPACE</strong> (the extra 8 is the discriminator).</p></li><li><p><strong>Enforce the length at runtime</strong> (require!(name.as_bytes().len() &lt;= 32, &#8230;)).</p></li><li><p>This avoids &#8220;serialize to unexpected length&#8221; failures.</p></li><li><p><strong>Remember:</strong> emojis/multibyte chars count as multiple <strong>bytes</strong>.</p></li></ul><h4><strong>PDAs, seeds, and bump</strong></h4><p>PDA derivation:</p><ul><li><p>PDA = <code>find_program_address([&#8221;user&#8221;, authority_pubkey], program_id)</code></p></li><li><p><strong>Why bump exists:</strong> it&#8217;s the one-byte nonce that forces the derived address <strong>off the ed25519 curve</strong> so it can be program-owned.</p></li><li><p>Store bump on the account so you can reference it in constraints:</p></li></ul><pre><code>seeds = [b&#8221;user&#8221;, authority.key().as_ref()], bump = user_account.bump</code></pre><blockquote><p><strong>Note:</strong> The <code>bump</code> is the single byte Anchor/SDK found to make the address fall off the ed25519 curve (so it&#8217;s program-owned). You store it so you can re-derive the PDA later in constraints.</p></blockquote><p><strong>Important derivations overview</strong></p><p><code>#[account]</code> : This derivation above struct means <strong>THIS</strong> struct is an on-chain account (in our case <code>UserAccount</code>) that has: <code>lamports</code> , <code>owner</code> , <code>data</code> &#8230;</p><p><code>#[account(...)]</code> : Above each field in each struct (that was derived with <code>#derive(Accounts)</code>) we will have this derivation which mean that this field is an account and this will declare its constraints</p><p><code>#[derive(Accounts)]</code> : This macro tells Anchor &#8220;<em>This struct describes the accounts required to call the instruction&#8221;</em></p><p><strong>Anchor uses it to:</strong></p><ul><li><p>deserialize account inputs</p></li><li><p>enforce constraints (seeds, mutability, ownership)</p></li><li><p>automatically derive PDAs</p></li><li><p>perform runtime validation before entering your handler</p></li></ul><h4><strong>Instruction: create_user</strong></h4><p><strong>object overview</strong></p><pre><code>#[derive(Accounts)]
pub struct CreateUser&lt;&#8217;info&gt; {
    #[account(mut)]
    pub authority: Signer&lt;&#8217;info&gt;,

    /// PDA: seeds = [&#8221;user&#8221;, authority]
    #[account(
        init,
        payer = authority,
        space = UserAccount::SPACE,
        seeds = [b&#8221;user&#8221;, authority.key().as_ref()],
        bump
    )]
    pub user_account: Account&lt;&#8217;info, UserAccount&gt;,

    pub system_program: Program&lt;&#8217;info, System&gt;,
}</code></pre><p><code>pub authority: Signer&lt;&#8217;info&gt;</code> : This field represents the <strong>caller</strong> of the instruction, and the <code>Signer</code> represents that this account must sign the transaction, it have <code>#[account(mut)]</code> because this signer will <strong>pay for the account creation fee</strong>, so their balance will change.</p><p><code>pub user_account: Account&lt;&#8217;info, UserAccount&gt;</code> : This is the <strong>PDA account</strong> that we are creating. It is the on-chain data structure it will store what was declared in <code>UserAccount</code> object.</p><p><em><strong>Notes</strong></em></p><ul><li><p>init: creates the PDA and zero-inits data.</p></li><li><p>payer: authority funds rent.</p></li><li><p>space: uses <strong>8 + INIT_SPACE</strong> (as declared in <code>impl UserAccount</code>).</p></li><li><p>seeds: uses &#8220;user&#8221; constant string and authority address.</p></li><li><p>bump: anchor know how to autofill it (used to find address off curve).</p></li></ul><p><code>pub system_program: Program&lt;&#8217;info, System&gt;</code> : Any init must call <strong>the system program</strong> under the hood which <strong>allocate new account, assign ownership, transfer lamports from payer.</strong> This field is required for <strong>account creation, lamport transfers, PDA initialization</strong></p><p><strong>handler overview</strong></p><pre><code>pub fn create_user(ctx: Context&lt;CreateUser&gt;, name: String) -&gt; Result&lt;()&gt; {
        // Enforce max length in BYTES (UTF-8). Emojis count as multiple bytes.
        require!(
            name.as_bytes().len() &lt;= UserAccount::MAX_NAME,
            ErrorCode::NameTooLong
        );

        let user = &amp;mut ctx.accounts.user_account;
        // Stores the wallet that created this profile
        user.owner = ctx.accounts.authority.key();
        // This assigns into the fixed allocated space Anchor reserved via #[max_len]
        user.name = name.clone(); // fits because we sized with MAX_NAME
        // Clock sysvar contains the current cluster time
        user.created_at = Clock::get()?.unix_timestamp;
        // Why store the bump:
        //   we used: PDA = find_program_address([&#8221;user&#8221;, authority], bump)
        //   We don&#8217;t want to recompute bump manually later
        //   update_name and close_user enforce (We will see it later)
        user.bump = ctx.bumps.user_account;
        ...
}</code></pre><p><code>Context&lt;CreateUser&gt;</code>: Gives you <strong>validated access</strong> to all accounts declared in the CreateUser struct. Anchor has already validated all constraints and created/allocated required accounts</p><h4><strong>Instruction: update_name</strong></h4><p><strong>object overview</strong></p><pre><code>#[derive(Accounts)]
pub struct UpdateUser&lt;&#8217;info&gt; {
    // Same as before, why not mut? no lamports changed here
    pub authority: Signer&lt;&#8217;info&gt;,
    
    // mut - because we modify the name, If you remove mut, Solana will reject the transaction
    // This is our access control rule, It ensures that only the original creator of the profile can update it
    // seeds - ensure correct automatic derivation
    // bump - because the PDA was initialized earlier using ctx.bumps.user_account.Storing and reusing the bump guarantees PDA stability.
    #[account(
        mut,
        constraint = user_account.owner == authority.key(),
        seeds = [b&#8221;user&#8221;, authority.key().as_ref()],
        bump = user_account.bump
    )]
    pub user_account: Account&lt;&#8217;info, UserAccount&gt;,
}</code></pre><p><strong>handler overview</strong></p><pre><code>pub fn update_name(ctx: Context&lt;UpdateUser&gt;, new_name: String) -&gt; Result&lt;()&gt; {
        // Enforce max length in BYTES (UTF-8). Emojis count as multiple bytes.
        require!(
            new_name.as_bytes().len() &lt;= UserAccount::MAX_NAME,
            ErrorCode::NameTooLong
        );

        let user = &amp;mut ctx.accounts.user_account;
        msg!(&#8221;&#9999;&#65039; Updating user: {}&#8221;, user.key());
        msg!(&#8221;   Old name: {}&#8221;, user.name);
        user.name = new_name.clone();
        msg!(&#8221;   New name: {}&#8221;, new_name);
        Ok(())
    }</code></pre><p>This instruction is straight-forward, it updates the already existing PDA&#8217;s name with <code>new_name</code></p><h4><strong>Instruction: close_user</strong></h4><pre><code>#[derive(Accounts)]
pub struct CloseUser&lt;&#8217;info&gt; {
    // Same as previous, this time is mut because balance increases with the refunded rent
    #[account(mut)]
    pub authority: Signer&lt;&#8217;info&gt;,

    // mut: we&#8217;re modifying (actually zeroing/closing) this account.
    // close: tells Anchor to refund all lamports from user_account to authority and then close the account when the instruction finishes. You don&#8217;t call anything manually&#8212;Anchor performs the close in the account&#8217;s &#8220;drop&#8221; (teardown) phase.
    // seeds: same as previous
    // bump: same as previous
    #[account(
        mut,
        close = authority,
        constraint = user_account.owner == authority.key(),
        seeds = [b&#8221;user&#8221;, authority.key().as_ref()],
        bump = user_account.bump
    )]
    pub user_account: Account&lt;&#8217;info, UserAccount&gt;,
}</code></pre><p><strong>handler overview</strong></p><pre><code>// Everything is done automatically
pub fn close_user(_ctx: Context&lt;CloseUser&gt;) -&gt; Result&lt;()&gt; {
    msg!(&#8221;&#129529; Closed user PDA and refunded rent.&#8221;);
    Ok(())
}</code></pre><h4>client side (Ts)</h4><p>Save this file in <code>scripts/solana_accounts.ts</code></p><blockquote><p><strong>Note:</strong> For this code to run we need to export 2 env variables:</p><p><code>export ANCHOR_PROVIDER_URL=&#8221;http://127.0.0.1:8899&#8221;</code></p><p><code>export ANCHOR_WALLET=&#8221;$HOME/.config/solana/id.json&#8221;</code></p></blockquote><pre><code>import * as anchor from &#8220;@coral-xyz/anchor&#8221;;
import type { Program } from &#8220;@coral-xyz/anchor&#8221;;
import { LAMPORTS_PER_SOL, PublicKey } from &#8220;@solana/web3.js&#8221;;
import { SolanaAccounts } from &#8220;../target/types/solana_accounts&#8221;;

async function ensureAirdrop(connection: anchor.web3.Connection, pubkey: PublicKey, min = 2 * LAMPORTS_PER_SOL) {
  const bal = await connection.getBalance(pubkey);
  if (bal &gt;= min) return;
  const sig = await connection.requestAirdrop(pubkey, min);
  await connection.confirmTransaction(sig, &#8220;confirmed&#8221;);
}

(async () =&gt; {
  const provider = anchor.AnchorProvider.env();
  anchor.setProvider(provider);

  // Use Anchor workspace (uses generated IDL/types under target/)
  const program = anchor.workspace.solanaAccounts as Program&lt;SolanaAccounts&gt;;
  const wallet = provider.wallet as anchor.Wallet;

  // Make sure we have SOL (useful on localhost)
  try { await ensureAirdrop(provider.connection, wallet.publicKey); } catch {}

  // PDA: seeds = [&#8221;user&#8221;, authority]
  // Seeds must match the program&#8217;s #[account(seeds = [b&#8221;user&#8221;, authority])]. From Rust!
  const [userPda] = PublicKey.findProgramAddressSync(
    [Buffer.from(&#8221;user&#8221;), wallet.publicKey.toBuffer()],
    program.programId
  );

  console.log(&#8221;Wallet:&#8221;, wallet.publicKey.toBase58());
  console.log(&#8221;Program:&#8221;, program.programId.toBase58());
  console.log(&#8221;User PDA:&#8221;, userPda.toBase58());

  // 1) createUser
  //    Derivable accounts (PDAs) are autofilled by Anchor
  //    No need to pass programId or PDA here! it knows from the context.
  const sig1 = await program.methods
    .createUser(&#8221;0xByteBeetle&#8221;)
    .accounts({ authority: wallet.publicKey }) // derivable accounts are autofilled
    .rpc();
  console.log(&#8221;createUser tx:&#8221;, sig1);

  // Fethch and log the created account
  const acct1 = await program.account.userAccount.fetch(userPda);
  console.log(&#8221;After create:&#8221;, {
    owner: acct1.owner.toBase58(),
    name: acct1.name,
    created_at: new Date(acct1.createdAt.toNumber() * 1000).toISOString(),
    bump: acct1.bump,
  });

  // 2) updateName
  const sig2 = await program.methods
    .updateName(&#8221;bytebeetle&#8221;)
    .accounts({ authority: wallet.publicKey })
    .rpc();
  console.log(&#8221;updateName tx:&#8221;, sig2);

  const acct2 = await program.account.userAccount.fetch(userPda);
  console.log(&#8221;After update:&#8221;, { name: acct2.name });

  // 3) closeUser
  const sig3 = await program.methods
    .closeUser()
    .accounts({ authority: wallet.publicKey })
    .rpc();
  console.log(&#8221;closeUser tx:&#8221;, sig3);

  try {
    await program.account.userAccount.fetch(userPda);
  } catch {
    console.log(&#8221;PDA closed (fetch failed as expected).&#8221;);
  }

  console.log(&#8221;Done &#9989;&#8221;);
})().catch((e) =&gt; {
  console.error(e);
  process.exit(1);
});</code></pre><blockquote><p><strong>Note:</strong> Explanations inside the code.</p></blockquote><h4>Run it all together</h4><p>Open new terminal in our project root directory and run:</p><ul><li><p><code>solana config set --url https://api.devnet.solana.com</code>:will set the default node RPC URL</p></li><li><p><code>solana-keygen new -o ~/.config/solana/id.json</code>: It will generate a dummy wallet for this test</p></li><li><p><code>solana airdrop 1</code>: It will fund your dummy wallet with 10 SOL (needed for deployments and execution), probably more is needed you can use this <a href="https://faucet.solana.com/">faucet</a></p></li><li><p><code>solana-keygen new -o target/deploy/solana_accounts-keypair.json --no-bip39-passphrase</code>: It will generate the keypair for the program.<br>Paste this pubkey into: <br>1. <code>programs/solana_accounts/src/lib.rs: declare_id(&#8221;...&#8221;)<br></code> 2. <code>Anchor.toml: [programs.devnet].solana_accounts=&#8221;&#8220;</code></p></li><li><p><code>solana-keygen pubkey target/deploy/solana_accounts-keypair.json</code>: Will print the generated programID.</p></li><li><p>Put pubkey into both: <code>declare_id!(&#8221;&lt;YOUR-PROGRAM-ID&gt;&#8221;)</code> and in <code>Anchor.toml</code> under <code>[programs.devnet]</code> where <code>solana_accounts = &lt;YOUR-PROGRAM-ID&gt;</code>:</p></li></ul><p><strong>For example(For you the address will be different):</strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2U9f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2U9f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 424w, https://substackcdn.com/image/fetch/$s_!2U9f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 848w, https://substackcdn.com/image/fetch/$s_!2U9f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 1272w, https://substackcdn.com/image/fetch/$s_!2U9f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2U9f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png" width="1404" height="176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:176,&quot;width&quot;:1404,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2U9f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 424w, https://substackcdn.com/image/fetch/$s_!2U9f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 848w, https://substackcdn.com/image/fetch/$s_!2U9f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 1272w, https://substackcdn.com/image/fetch/$s_!2U9f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdc01bded-8dd5-4bc6-b33b-9248c5b5f7f8_1404x176.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!958h!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!958h!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 424w, https://substackcdn.com/image/fetch/$s_!958h!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 848w, https://substackcdn.com/image/fetch/$s_!958h!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 1272w, https://substackcdn.com/image/fetch/$s_!958h!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!958h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png" width="1414" height="708" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:708,&quot;width&quot;:1414,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!958h!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 424w, https://substackcdn.com/image/fetch/$s_!958h!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 848w, https://substackcdn.com/image/fetch/$s_!958h!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 1272w, https://substackcdn.com/image/fetch/$s_!958h!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f9e8eff-3c43-47bd-b8a8-e546a057468a_1414x708.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And run:</p><pre><code>solana-keygen new -o target/deploy/solana_accounts-keypair.json --no-bip39-passphrase
solana-keygen pubkey target/deploy/solana_accounts-keypair.json
# Paste this pubkey into:
#   - programs/solana_accounts/src/lib.rs: declare_id!(&#8221;...&#8221;)
#   - Anchor.toml: [programs.devnet].solana_accounts = &#8220;...&#8221;

anchor clean
anchor build
anchor deploy</code></pre><p>You should see something like:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XUBi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XUBi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 424w, https://substackcdn.com/image/fetch/$s_!XUBi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 848w, https://substackcdn.com/image/fetch/$s_!XUBi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 1272w, https://substackcdn.com/image/fetch/$s_!XUBi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XUBi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png" width="1404" height="216" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:216,&quot;width&quot;:1404,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XUBi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 424w, https://substackcdn.com/image/fetch/$s_!XUBi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 848w, https://substackcdn.com/image/fetch/$s_!XUBi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 1272w, https://substackcdn.com/image/fetch/$s_!XUBi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f59f65-2573-4cfe-9ba2-d3004fecedd9_1404x216.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yA8r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yA8r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 424w, https://substackcdn.com/image/fetch/$s_!yA8r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 848w, https://substackcdn.com/image/fetch/$s_!yA8r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 1272w, https://substackcdn.com/image/fetch/$s_!yA8r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yA8r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png" width="1456" height="636" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:636,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yA8r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 424w, https://substackcdn.com/image/fetch/$s_!yA8r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 848w, https://substackcdn.com/image/fetch/$s_!yA8r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 1272w, https://substackcdn.com/image/fetch/$s_!yA8r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d7f7ba2-1e45-40ad-b45a-1153e810b498_1600x699.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://solscan.io/account/Ga1UVR2AoZAazWCSZQUg7ZdKkpNXxLCXju4eN5YRrKQJ?cluster=devnet">https://solscan.io/account/Ga1UVR2AoZAazWCSZQUg7ZdKkpNXxLCXju4eN5YRrKQJ?cluster=devnet</a></figcaption></figure></div><p>then run</p><pre><code># Reminder: you need to run this before:
# export export ANCHOR_PROVIDER_URL=&#8221;https://api.devnet.solana.com&#8221;
# export ANCHOR_WALLET=&#8221;$HOME/.config/solana/id.json&#8221;

pnpm ts-node scripts/solana_accounts.ts</code></pre><p>You should see something like:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i9Uf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i9Uf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 424w, https://substackcdn.com/image/fetch/$s_!i9Uf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 848w, https://substackcdn.com/image/fetch/$s_!i9Uf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 1272w, https://substackcdn.com/image/fetch/$s_!i9Uf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i9Uf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png" width="1456" height="415" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:415,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i9Uf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 424w, https://substackcdn.com/image/fetch/$s_!i9Uf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 848w, https://substackcdn.com/image/fetch/$s_!i9Uf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 1272w, https://substackcdn.com/image/fetch/$s_!i9Uf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa1176004-5037-4b28-9020-1ac4878b6a75_1488x424.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JgYJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JgYJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 424w, https://substackcdn.com/image/fetch/$s_!JgYJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 848w, https://substackcdn.com/image/fetch/$s_!JgYJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 1272w, https://substackcdn.com/image/fetch/$s_!JgYJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JgYJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png" width="1456" height="716" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JgYJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 424w, https://substackcdn.com/image/fetch/$s_!JgYJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 848w, https://substackcdn.com/image/fetch/$s_!JgYJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 1272w, https://substackcdn.com/image/fetch/$s_!JgYJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff926693e-345d-4a2d-b7d6-da2456abc72e_1600x787.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://solscan.io/account/Ga1UVR2AoZAazWCSZQUg7ZdKkpNXxLCXju4eN5YRrKQJ?cluster=devnet">https://solscan.io/account/Ga1UVR2AoZAazWCSZQUg7ZdKkpNXxLCXju4eN5YRrKQJ?cluster=devnet</a></figcaption></figure></div><h3>Resources</h3><ul><li><p>Anchor code can be found <a href="https://github.com/bounty-wiz/Anchor-Solana-Accounts/tree/main">here</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Understanding Solana: Account Model - part 2]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/understanding-solana-account-model</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/understanding-solana-account-model</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Wed, 12 Nov 2025 15:27:12 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!aUku!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>After reading the <a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-architecture-account-model-and-transactions-part-1-1bffae449650">previous post</a> we have a knowledge about Solana architecture, in this post we will learn that in Solana, <strong>everything revolves around accounts</strong>.<br>Every piece of state from your tokens to your program data lives inside an account. Programs themselves are stateless, they only read and modify data that accounts expose to them. Once you understand that, Solana&#8217;s entire design starts to make sense.</p><p>In this post, we&#8217;ll slow down and unpack what &#8220;an account&#8221; really means.<br>We&#8217;ll go through its structure, ownership rules, and how Solana uses <em>Program-Derived Addresses (PDAs)</em> to create predictable and secure addresses without private keys. You&#8217;ll also learn how different types of accounts fit together, from system accounts managed by the runtime to program state accounts that store your app&#8217;s logic or user data.</p><ul><li><p><strong>What makes an account the basic unit of state on Solana</strong></p></li><li><p><strong>Account Types</strong></p></li><li><p><strong>What is Program derived address (PDA)</strong></p></li><li><p><strong>How Solana enforces ownership, rent, and access</strong></p></li></ul><p>In the next blog, we&#8217;ll explore what is <strong>Anchor </strong>and see how these concepts map to real code and how Anchor&#8217;s account macros simplify safety and boilerplate.</p></blockquote><div><hr></div><h3><strong>Previous part</strong></h3><ul><li><p><a href="https://andreyobruchkov1996.substack.com/p/understanding-solana-architecture-account-model-and-transactions-part-1-1bffae449650">Understanding Solana - Part 1</a></p></li></ul><h3><strong>What makes an account the basic unit of state on Solana</strong></h3><p>The most important thing to understand about Solana is that unlike most blockchains where &#8220;contracts&#8221; hold their own state internally, Solana separates <strong>code</strong> and <strong>data</strong> completely.<br>Programs (smart contracts) are pure logic, they don&#8217;t own persistent storage.<br>Instead, all persistent data lives in <strong>accounts</strong>, which are on-chain data containers managed by the runtime.</p><p>You can think of an account as a <strong>self-contained piece of state</strong> with:</p><pre><code>struct Account {
    // num of lamports in the account
    lamports:   uint64,
    // data held in this account
    data:       bytes,
    // PubKey that owns this account. If executable, the program that loads this account.
    owner:      Pubkey,
    // this account&#8217;s data contains a loaded program (and is now read-only)
    executable: bool,
    // the epoch at which this account will next owe rent
    rent_epoch: Epoch,
}</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aUku!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aUku!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 424w, https://substackcdn.com/image/fetch/$s_!aUku!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 848w, https://substackcdn.com/image/fetch/$s_!aUku!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 1272w, https://substackcdn.com/image/fetch/$s_!aUku!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aUku!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png" width="1456" height="371" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:371,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aUku!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 424w, https://substackcdn.com/image/fetch/$s_!aUku!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 848w, https://substackcdn.com/image/fetch/$s_!aUku!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 1272w, https://substackcdn.com/image/fetch/$s_!aUku!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16e01332-6c68-4fc4-9556-4556f6346dd6_1600x408.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">taken from <a href="https://solana.com/docs/core/accounts#account-address">https://solana.com/docs/core/accounts#account-address</a></figcaption></figure></div><p>Every instruction that runs on Solana explicitly lists which accounts it wants to read or write.<br>The runtime enforces access rules:</p><ul><li><p>Only the <strong>account&#8217;s owner program</strong> can modify its data.</p></li><li><p>Only the account&#8217;s <strong>signer</strong> (or a PDA acting as signer, PDA will be explained in next section) can authorize transfers or ownership changes.</p></li></ul><p>This strict separation achieves two things:</p><ol><li><p><strong>Parallelism: </strong>since programs operate only on the accounts listed in the transaction, Solana can execute unrelated transactions concurrently.</p></li><li><p><strong>Security and clarity: </strong>a program cannot accidentally modify global state; it must be given explicit permission to touch each account.</p></li></ol><p>In short, <strong>accounts are the atomic unit of state</strong> on Solana.<br>If Ethereum&#8217;s equivalent is a contract with internal storage slots, Solana&#8217;s model externalizes that: each &#8220;slot&#8221; becomes its own addressable account on-chain.</p><p>Everything you interact with: users, tokens, pools, NFTs, even the Solana runtime itself is built on top of these accounts.</p><h3><strong>What is Program derived address (PDA)</strong></h3><p>Solana doesn&#8217;t allow programs to hold private keys.<br>Yet, sometimes your program needs an address it can <strong>own and control deterministically,</strong> for example, a vault to store user deposits, or a metadata account tied to a specific mint.<br>That&#8217;s exactly what <strong>Program-Derived Addresses (PDAs)</strong> provide.</p><p>A <strong>PDA</strong> is a special kind of account address that is:</p><ul><li><p><strong>Deterministically derived</strong> from a set of <em>seeds</em> and a <em>program ID</em></p></li><li><p><strong>Guaranteed not to collide</strong> with any address that has a valid Ed25519 private key.</p></li></ul><p>Because PDAs don&#8217;t correspond to real keypairs, they cannot be signed using a private key.<br>Instead, the Solana runtime lets the owning program &#8220;sign&#8221; on behalf of that PDA during an instruction but <em>only</em> if the PDA was derived with the program&#8217;s own ID.</p><h4>How PDAs are generated</h4><p>A PDA is derived like that with the help of solana-sdk on rust:</p><pre><code>Pubkey::find_program_address(seeds: &amp;[&amp;[u8]], program_id: &amp;Pubkey)</code></pre><p>This function tries different &#8220;bump&#8221; values: It iterate bump values by starting at 255 and decrements by 1 until a valid PDA is found that is not on the Ed25519 curve (i.e., cannot have a private key).<br>That results a <strong>unique </strong>PDA is created for the combination of:</p><ul><li><p><code>program_id</code></p></li><li><p>and the <code>seeds</code> array.</p></li></ul><p>Example:</p><pre><code>let (vault_pda, bump) =
    Pubkey::find_program_address(&amp;[b&#8221;my_vault&#8221;, user.key().as_ref()], &amp;program_id);</code></pre><p>Now <code>vault_pda</code> will always be the same for this user and program no need to store it anywhere.</p><p>Overview:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cGn8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cGn8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 424w, https://substackcdn.com/image/fetch/$s_!cGn8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 848w, https://substackcdn.com/image/fetch/$s_!cGn8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 1272w, https://substackcdn.com/image/fetch/$s_!cGn8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cGn8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png" width="1456" height="985" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:985,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cGn8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 424w, https://substackcdn.com/image/fetch/$s_!cGn8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 848w, https://substackcdn.com/image/fetch/$s_!cGn8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 1272w, https://substackcdn.com/image/fetch/$s_!cGn8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F837a1dbb-488b-41dd-bfbb-2cb69bf2876d_1600x1082.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">taken from <a href="https://solana.com/docs/core/pda">https://solana.com/docs/core/pda</a></figcaption></figure></div><h4>How PDAs &#8220;sign&#8221;</h4><p>When your program makes a cross-program invocation (CPI) (Don&#8217;t worry it will be covered in later chapters, now we only want to understand the sign effect) that needs the PDA to act as a signer, for example, to transfer tokens from a PDA&#8217;s account <br>you use the function <code>invoke_signed()</code> and pass the <strong>same seeds and bump</strong> used to derive the PDA.</p><p>Example:</p><pre><code>invoke_signed(
    &amp;instruction,
    &amp;account_infos,
    &amp;[&amp;[b&#8221;my_vault&#8221;, user.key().as_ref(), &amp;[bump]]],
)?;</code></pre><p>The runtime checks that those seeds and bump match a valid PDA for your program ID. If they do, it lets your program &#8220;sign&#8221; as that PDA.<br>No private keys, no risk of exposure.</p><h4>Why PDAs matter</h4><p>PDAs are at the heart of how complex Solana programs structure state.<br>They let you:</p><ul><li><p>Create <strong>per-user or per-pool</strong> state accounts deterministically (like <code>[&#8221;profile&#8221;, user]</code> or <code>[&#8221;pool&#8221;, token_a, token_b]</code>).</p></li><li><p>Build <strong>vaults</strong> or <strong>treasuries</strong> controlled solely by your program.</p></li><li><p>Prevent arbitrary users from hijacking your storage addresses.</p></li><li><p>Avoid having to store mapping data on-chain (you can recompute addresses instead).</p></li></ul><p>They are what make Solana&#8217;s stateless programs practical, safe, and composable.</p><h3><strong>Account Types</strong></h3><p>There are two basic categories that accounts fall into:</p><ul><li><p><strong>Program accounts:</strong> Accounts that contain executable code</p></li><li><p><strong>Data</strong> <strong>accounts:</strong> Accounts that do not contain executable code, but holds information</p></li></ul><p>This separation means that a program&#8217;s executable code and its state are stored in separate accounts.</p><h4>Program accounts</h4><p>A <strong>program account</strong> stores the compiled bytecode that defines how a Solana program behaves.</p><p><strong>Key characteristics</strong></p><ul><li><p><strong>Executable flag:</strong> <code>executable = true</code></p></li><li><p><strong>Owner:</strong> always the <strong><a href="https://solana.com/docs/core/programs#loader-programs">BPF Loader</a></strong></p></li><li><p><strong>Data:</strong> the actual code of the program</p></li></ul><p>Program accounts do <strong>not</strong> hold user state or variables.<br>When your transaction calls a program, Solana loads this executable account into memory and executes the code inside it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uL4P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uL4P!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 424w, https://substackcdn.com/image/fetch/$s_!uL4P!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 848w, https://substackcdn.com/image/fetch/$s_!uL4P!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 1272w, https://substackcdn.com/image/fetch/$s_!uL4P!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uL4P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png" width="1456" height="561" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/063f1384-6cff-4039-9c19-2581c2190362_1600x617.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:561,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uL4P!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 424w, https://substackcdn.com/image/fetch/$s_!uL4P!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 848w, https://substackcdn.com/image/fetch/$s_!uL4P!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 1272w, https://substackcdn.com/image/fetch/$s_!uL4P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F063f1384-6cff-4039-9c19-2581c2190362_1600x617.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">taken from <a href="https://solana.com/docs/core/accounts#program-data-accounts">https://solana.com/docs/core/accounts#program-data-accounts</a></figcaption></figure></div><h4>Data accounts</h4><p>Data accounts store <strong>all persistent state</strong> for your application: user profiles, pools, vaults, mints, and so on.<br>They are <strong>owned by a program</strong>, and only that program can modify their contents.</p><p>Within this broad category, you&#8217;ll encounter several important <strong>sub-types</strong>:</p><p><strong>Program state account:</strong></p><p>Custom accounts that hold the on-chain data for your program.<br>They represent things like: liquidity pool, user&#8217;s staking position, configuration record and so on&#8230;</p><p><strong>Key properties</strong></p><ul><li><p><code>owner</code> = your program&#8217;s public key</p></li><li><p><code>executable = false</code></p></li><li><p>must hold enough lamports to be <strong>rent-exempt </strong>(will be covered in next section)</p></li><li><p>usually <strong>Program-Derived Addresses (PDAs)</strong> to ensure deterministic, secure ownership (Explained above)</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!edFh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!edFh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 424w, https://substackcdn.com/image/fetch/$s_!edFh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 848w, https://substackcdn.com/image/fetch/$s_!edFh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 1272w, https://substackcdn.com/image/fetch/$s_!edFh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!edFh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png" width="1456" height="917" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:917,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!edFh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 424w, https://substackcdn.com/image/fetch/$s_!edFh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 848w, https://substackcdn.com/image/fetch/$s_!edFh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 1272w, https://substackcdn.com/image/fetch/$s_!edFh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc93a762a-c2a1-403c-9af9-a390365e4b06_1600x1008.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">taken from <a href="https://solana.com/docs/core/accounts#program-data-accounts">https://solana.com/docs/core/accounts#program-data-accounts</a></figcaption></figure></div><p>Example</p><pre><code>Address:  pool_pda (derived from [&#8221;pool&#8221;, token_a, token_b])
Owner:    MyDex111111111111111111111111111111111111111
Data:     { token_a, token_b, reserves, bump }</code></pre><p><strong>System accounts:</strong></p><p>Accounts that are managed by the <strong><a href="https://solana.com/docs/core/programs#the-system-program">System Program</a>.</strong><br>These include:</p><ul><li><p>Wallet accounts that hold SOL and can sign transactions</p></li><li><p>Temporary storage or uninitialized accounts before they&#8217;re assigned to a program</p></li><li><p>Upgrade or admin authorities (often simple keypair accounts)</p></li></ul><p>System accounts are the backbone of Solana&#8217;s runtime. They can send SOL, pay rent, and be reassigned to another program as their owner.</p><p><strong>Sysvar accounts</strong></p><p><em>Read-only system-provided accounts</em> that expose internal blockchain data to programs.<br>Instead of embedding runtime data into every instruction, Solana provides them through these predefined accounts. More data can be found <a href="https://docs.anza.xyz/runtime/sysvars">here</a>.</p><h3><strong>How Solana enforces ownership, rent, and access</strong></h3><p>Now that we&#8217;ve seen the different types of accounts, it&#8217;s important to understand <strong>how Solana controls who can read, write, or move lamports</strong> and how it ensures the network stays economically stable through rent.</p><h4>Ownership</h4><p>Every account has an <code>owner</code> field a public key that identifies the <strong>program authorized to modify its data</strong>.</p><ul><li><p>Only the <strong>owning program</strong> can change an account&#8217;s <code>data</code> bytes.</p></li><li><p>The <strong>System Program</strong> can change an account&#8217;s owner (using <code>assign</code>) or lamport balance (using <code>transfer</code>).</p></li><li><p>User wallets are just <strong>system accounts</strong> whose owner is the System Program.</p></li><li><p>When your custom program creates an account, it sets itself as the owner, making it the only program that can write to that account&#8217;s data.</p></li></ul><p>Example:</p><pre><code>account.owner = &lt;your program id&gt;</code></pre><p>means that only your program can modify this account&#8217;s internal data field no other program can. This strict owner&#8211;writer rule prevents accidental or malicious modification of another program&#8217;s state.</p><h4>Access Control (Signers and Writable Flags)</h4><p>When a transaction executes, it must <strong>explicitly list all accounts</strong> the instruction will use.<br>For each account, the transaction specifies:</p><ul><li><p>Whether the account is <strong>read-only</strong> or <strong>writable</strong></p></li><li><p>Whether it is a <strong>signer</strong></p></li></ul><p>The runtime enforces these flags:</p><ul><li><p>Writable accounts are the only ones that can have their data or lamports modified.</p></li><li><p>Signer accounts are those that have authorized the transaction typically user wallets or PDAs through <code>invoke_signed</code>.</p></li><li><p>Programs cannot arbitrarily access any on-chain data. They only see what&#8217;s passed into the instruction&#8217;s context.</p></li></ul><p>This explicit access model enables <strong>parallel transaction execution</strong>, since Solana knows which accounts each instruction touches.</p><h4>Rent and Rent Exemption</h4><p>Every account must pay <strong>rent</strong>, which is a small cost to occupy on-chain storage. However, most programs make accounts <strong>rent-exempt</strong> by depositing enough lamports upfront.</p><p>The rent-exempt minimum depends on:</p><ul><li><p>the <strong>account&#8217;s data size</strong> (number of bytes), and</p></li><li><p>the <strong>current rent rate</strong> (available via the <code>SysvarRent</code> account).</p></li></ul><p>Key points:</p><ul><li><p>If an account&#8217;s lamports drop below the rent-exempt threshold, the runtime can reclaim it.</p></li><li><p>Rent-exempt accounts never lose lamports or get purged.</p></li><li><p>Programs typically calculate rent like this:</p></li></ul><pre><code>let rent = Rent::get()?;
let lamports = rent.minimum_balance(space);</code></pre><h3>Up Next</h3><p>Once you have this foundation, we&#8217;ll move from theory to practice.<br>In the next post, we&#8217;ll open up <strong>Anchor in Rust</strong>, create our first on-chain program, and see how all these ideas: ownership, seeds, PDAs, and account sizing come together in real code.</p><p>You&#8217;ll learn how Anchor&#8217;s macros automatically enforce the same rules we explored conceptually here, making Solana development both safer and more ergonomic.</p>]]></content:encoded></item><item><title><![CDATA[Proxies and Upgradability - Minimal Proxy (EIP-1167)]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability-minimal</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability-minimal</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:58:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!PJ1A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>Factories make it easy to deploy many contracts, but each deployment still costs gas for full bytecode.<br>What if you could deploy thousands of instances for just a fraction of that cost?</p><p>That&#8217;s the idea behind <strong>EIP-1167</strong>, the <em>Minimal Proxy</em> (or <em>clone</em>) pattern.<br>Instead of redeploying the entire logic each time, a minimal proxy holds only a few bytes of code that delegate every call to a single implementation contract.<br>Every instance behaves like a full contract but reuses the same logic: saving gas, storage, and bytecode size.</p><p>In this post, we&#8217;ll cover:</p><ul><li><p><strong>How EIP-1167 reduces deployment cost using a tiny bytecode stub</strong></p></li><li><p><strong>The difference between normal proxies and clones</strong></p></li><li><p><strong>A Foundry walkthrough for deploying and verifying minimal proxies on-chain</strong></p></li></ul><p>By the end, you&#8217;ll understand how different protocols scale massive deployments without redeploying logic again and again.</p></blockquote><div><hr></div><h3>Minimal proxy (<a href="https://eips.ethereum.org/EIPS/eip-1167">EIP-1167</a>)</h3><p>Not all proxies are about upgrades. Sometimes, the goal isn&#8217;t to keep one address stable forever&#8202;, &#8202;it&#8217;s to <strong>stamp out many identical contracts cheaply</strong>, each with its own state but sharing the same logic. That&#8217;s exactly what EIP-1167 &#8220;Minimal Proxy&#8221; contracts (aka <em>clones</em>) are designed for.</p><h4>What&#8217;s a Minimal Proxy?</h4><p>A clone is just ~45 bytes of runtime code that forwards every call to a fixed implementation using <code>DELEGATECALL</code>.</p><ul><li><p>The <strong>logic contract</strong> (the &#8220;master copy&#8221;) holds all the functions.</p></li><li><p>Each <strong>clone</strong> stores no logic, only data. Calls to the clone are executed in its storage, but using the master copy&#8217;s code.</p></li><li><p>The only difference between clones is <strong>their state</strong>, not their code.</p></li></ul><p>Because the implementation address is <em>baked into the clone&#8217;s bytecode</em>, the clone can&#8217;t be upgraded later. This makes them extremely small, gas-efficient, and predictable, but also fundamentally different from upgradeable proxies.</p><h4>How the Clone&#8217;s Bytecode Works</h4><p>The &#8220;magic&#8221; behind EIP-1167 is that all clones share the <strong>same tiny runtime code</strong>, with just one variable part: the embedded implementation address.</p><p>The canonical runtime looks like this (hex):</p><pre><code>363d3d373d3d3d363d73&lt;20-byte-impl&gt;5af43d82803e903d91602b57fd5bf3

// &lt;20-byte-impl&gt; can be: 0xbebebebebe.... (the shared logic address)</code></pre><p><strong>OPcode explanation</strong>:</p><p>Press enter or click to view image in full size</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PJ1A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PJ1A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 424w, https://substackcdn.com/image/fetch/$s_!PJ1A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 848w, https://substackcdn.com/image/fetch/$s_!PJ1A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 1272w, https://substackcdn.com/image/fetch/$s_!PJ1A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PJ1A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png" width="1400" height="748" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:748,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PJ1A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 424w, https://substackcdn.com/image/fetch/$s_!PJ1A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 848w, https://substackcdn.com/image/fetch/$s_!PJ1A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 1272w, https://substackcdn.com/image/fetch/$s_!PJ1A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4d5d7486-ebc4-4dcc-a61e-6326dff6875a_1400x748.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://eips.ethereum.org/EIPS/eip-1167">https://eips.ethereum.org/EIPS/eip-1167</a></figcaption></figure></div><blockquote><p><em><strong>Note:</strong> A real world example can be a <strong>dex pair </strong>where<strong> </strong>each liquidity pair is a clone with its own reserves, but all share the same pair logic.</em></p></blockquote><h4>Minimal example</h4><p><code>minimalProxy.sol</code></p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/* ---------------------------------- */
/* 1) Implementation (shared logic)   */
/* ---------------------------------- */
contract Counter {
    address public owner;
    uint256 public value;
    bool private _initialized;
    // Called via the clone after deployment
    function initialize(address _owner, uint256 start) external {
        require(!_initialized, &#8220;already initialized&#8221;);
        _initialized = true;
        owner = _owner;
        value = start;
    }
    function inc() external {
        require(msg.sender == owner, &#8220;not owner&#8221;);
        unchecked { value += 1; }
    }
    // Optional: prevent initializing the logic contract itself
    constructor() {
        _initialized = true;
    }
}
/* ---------------------------------- */
/* 2) Minimal Clone Factory           */
/* ---------------------------------- */
contract CounterCloneFactory {
    event CloneCreated(address indexed clone, address indexed owner, uint256 start, bytes32 salt);
    /* ---- Internal helpers: build creation code for the clone ---- */
    // Clone creation code = small prologue that returns the 45-byte runtime
    function _cloneCreationCode(address impl) internal pure returns (bytes memory code) {
        // creation: 3d602d80600a3d3981f3  -&gt; return(next 0x37 bytes)
        // runtime:  363d3d373d3d3d363d73 &lt;impl&gt; 5af43d82803e903d91602b57fd5bf3
        code = abi.encodePacked(
            hex&#8221;3d602d80600a3d3981f3&#8221;,
            hex&#8221;363d3d373d3d3d363d73&#8221;,
            impl,
            hex&#8221;5af43d82803e903d91602b57fd5bf3&#8221;
        );
    }
    /* ---- Deploy a clone with CREATE ---- */
    function createClone(address implementation, address owner_, uint256 start_)
        external
        returns (address clone)
    {
        bytes memory code = _cloneCreationCode(implementation);
        assembly {
            clone := create(0, add(code, 0x20), mload(code))
            if iszero(clone) { revert(0, 0) }
        }
        // Initialize immediately (constructors don&#8217;t run through clones)
        (bool ok, ) = clone.call(abi.encodeWithSignature(&#8221;initialize(address,uint256)&#8221;, owner_, start_));
        require(ok, &#8220;init failed&#8221;);
        emit CloneCreated(clone, owner_, start_, bytes32(0));
    }
    /* ---- Deploy a deterministic clone with CREATE2 ---- */
    function createCloneDeterministic(address implementation, address owner_, uint256 start_, bytes32 salt)
        external
        returns (address clone)
    {
        bytes memory code = _cloneCreationCode(implementation);
        assembly {
            clone := create2(0, add(code, 0x20), mload(code), salt)
            if iszero(clone) { revert(0, 0) }
        }
        (bool ok, ) = clone.call(abi.encodeWithSignature(&#8221;initialize(address,uint256)&#8221;, owner_, start_));
        require(ok, &#8220;init failed&#8221;);
        emit CloneCreated(clone, owner_, start_, salt);
    }
}</code></pre><p>Lets deploy it and run some on chain functions:</p><p><strong>Start the node:</strong></p><pre><code>anvil</code></pre><p><strong>Deploy the contract:</strong></p><pre><code>// Deployment of the shared logic
forge create src/minimalProxy.sol:Counter \
  --rpc-url localhost:8545 \
  --private-key &lt;YOUR-ANVIL-PK&gt; --broadcast

// Expected output:
// [&#10250;] Compiling...
// No files changed, compilation skipped
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
//Transaction hash: 0x43f37888e5c370cc1a398f1891cea860a80815fdf59716d4e2d8adb198b5edb8
// Deployment of the minimal proxy factory 
forge create src/minimalProxy.sol:CounterCloneFactory \
  --rpc-url localhost:8545 \
  --private-key &lt;YOUR-ANVIL-PK&gt; --broadcast
// Expected output:
// [&#10250;] Compiling...
// No files changed, compilation skipped
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0x0165878A594ca255338adfa4d48449f69242Eb8F
// Transaction hash: 0x6ddac050694baf16fc7bcfde90dcf0516bd1777b6a1b8ea23fd8019bc20391b2</code></pre><p><strong>Create Some clones:</strong></p><pre><code>// Deploy first instance
cast send 0x0165878A594ca255338adfa4d48449f69242Eb8F \
  &#8220;createClone(address,address,uint256)(address)&#8221; \
  0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 &lt;YOUR-ANVIL-PK-ADDR&gt; 100 \  
  --rpc-url localhost:8545 \
  --private-key &lt;YOUR-ANVIL-PK&gt;

// On success expected this log:
// logs [
// {&#8221;address&#8221;:&#8221;0x0165878a594ca255338adfa4d48449f69242eb8f&#8221;,
//    &#8220;topics&#8221;:[&#8221;0x6823f533242a9c540bd8ab230da11a7199723745abb9d484732bb57b4f34d4d1&#8221;,
//              &#8220;0x0000000000000000000000003b02ff1e626ed7a8fd6ec5299e2c54e1421b626b&#8221;,
//              &#8220;0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266&#8221;],
//              &#8220;data&#8221;:&#8221;0x00000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000&#8221;,
//              &#8220;blockHash&#8221;:&#8221;0xe7748e4f28f2492642e9f64e7252fd40e85079e5211523b7b5b35c82adceff69&#8221;,&#8221;blockNumber&#8221;:&#8221;0x8&#8221;,&#8221;blockTimestamp&#8221;:&#8221;0x68b92822&#8221;,&#8221;transactionHash&#8221;:&#8221;0xe48d780a8268235887019e74d62e09a7e09164aaa37bdea60a2c0aa97f81d00c&#8221;,
//              &#8220;transactionIndex&#8221;:&#8221;0x0&#8221;,
//              &#8220;logIndex&#8221;:&#8221;0x0&#8221;,
//              &#8220;removed&#8221;:false}]
// 
// From logs we can see that the address is: 0x3b02ff1e626ed7a8fd6ec5299e2c54e1421b626b
// Deploy second instance
cast send 0x0165878A594ca255338adfa4d48449f69242Eb8F \
  &#8220;createClone(address,address,uint256)(address)&#8221; \
  0x5FC8d32690cc91D4c39d9d3abcBD16989F875707 &lt;YOUR-ANVIL-PK-ADDR&gt; 100 \
  --rpc-url localhost:8545 \
  --private-key &lt;YOUR-ANVIL-PK&gt;
// On success expected this log:
// [{&#8221;address&#8221;:&#8221;0x0165878a594ca255338adfa4d48449f69242eb8f&#8221;,
//   &#8220;topics&#8221;:[&#8221;0x6823f533242a9c540bd8ab230da11a7199723745abb9d484732bb57b4f34d4d1&#8221;,
//   &#8220;0x000000000000000000000000ba12646cc07adbe43f8bd25d83fb628d29c8a762&#8221; ....
//
// From logs we can see that the address is: 0xba12646cc07adbe43f8bd25d83fb628d29c8a762</code></pre><p><strong>Lets do some reads and writes:</strong></p><pre><code>// Read the value of the first proxy
cast call 0x3b02ff1e626ed7a8fd6ec5299e2c54e1421b626b &#8220;value()(uint256)&#8221; --rpc-url localhost:8545
// Expected output: 100

// Read the value of the first proxy
cast call 0xba12646cc07adbe43f8bd25d83fb628d29c8a762 &#8220;value()(uint256)&#8221; --rpc-url localhost:8545
// Expected output: 100
// Write operation on first contract
cast send 0x3b02ff1e626ed7a8fd6ec5299e2c54e1421b626b &#8220;inc()&#8221; \
  --rpc-url http://localhost:8545 \
  --private-key &lt;YOUR-ANVIL-PK&gt;
// Read the value of the first proxy
cast call 0x3b02ff1e626ed7a8fd6ec5299e2c54e1421b626b &#8220;value()(uint256)&#8221; --rpc-url localhost:8545
// Expected output: 101
// Read the value of the first proxy
cast call 0xba12646cc07adbe43f8bd25d83fb628d29c8a762 &#8220;value()(uint256)&#8221; --rpc-url localhost:8545
// Expected output: 100</code></pre><blockquote><p><em><strong>Note: </strong>As we see each proxy holds its own storage while pointing to some shared logic. We used here the factory pattern combined with proxy.</em></p></blockquote><h4>Why Not Just Deploy Full Contracts in a Factory?</h4><p>The simple <code>CounterFactory</code> example works fine: each call to <code>createCounter</code> deploys a <em>full</em> <code>Counter</code> contract. But there&#8217;s a big tradeoff: <strong>gas costs. </strong>Every time you deploy a contract on EVM, you pay gas proportional to the <strong>size of the bytecode</strong> you&#8217;re putting on-chain.</p><ul><li><p>A normal <code>Counter</code> might have a few kilobytes of bytecode.</p></li><li><p>Deploying 10,000 counters = writing that same bytecode 10,000 times into EVM state.</p></li></ul><p>That&#8217;s expensive and wasteful: the code never changes, it&#8217;s just repeated.</p><h3>Summary</h3><p>Minimal proxies take the factory concept to its logical extreme: reuse everything except state.<br>Instead of redeploying full logic bytecode, each clone stores just a 45-byte delegatecall stub that forwards execution to a shared implementation.<br>This design cuts deployment costs dramatically while maintaining isolated state for every instance a perfect fit for protocols that need to spin up large numbers of identical contracts.</p><p>It&#8217;s the pattern behind many famous protocols, and it&#8217;s a foundational tool for any protocol aiming to scale efficiently on the EVM.<br>Factories create consistency, minimal proxies make it affordable.</p>]]></content:encoded></item><item><title><![CDATA[Factories - How Smart Contracts Deploy Other Contracts]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/factories-how-smart-contracts-deploy</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/factories-how-smart-contracts-deploy</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:56:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>By now, we&#8217;ve seen how contracts get deployed (CREATE and CREATE2 in this <a href="https://andreyobruchkov1996.substack.com/p/deployments-and-deterministic-addresses">post</a>) and how they can be upgraded through proxies like <strong>EIP-1967  (<a href="https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability">in this post</a>)</strong> and <strong>EIP-1822 (<a href="https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability-uups-proxy">in this post</a>)</strong>.<br>But most real-world systems don&#8217;t stop there they need to deploy <strong>many</strong> contracts, not just one.</p><p>That&#8217;s where <strong>factories</strong> come in.<br>A factory is a smart contract that knows how to <strong>deploy, initialize, and track</strong> other contracts on-chain. Instead of manually deploying every instance, you call the factory once and it handles everything: creation, configuration, and event emission for indexing.</p><p>This pattern powers everything from <strong>DEX pairs</strong> to <strong>vaults, wallets, and DAOs</strong> anywhere a protocol needs predictable, standardized deployments.</p><p>In this post, we&#8217;ll cover:</p><ul><li><p><strong>How factories use </strong><code>CREATE</code><strong> and </strong><code>CREATE2</code><strong> under the hood</strong></p></li><li><p><strong>Why deterministic deployments matter for discoverability and security</strong></p></li><li><p><strong>A full Foundry example of deploying and interacting with contracts via a factory</strong></p></li></ul><p>By the end, you&#8217;ll understand why nearly every modern protocol includes a factory and how to build your own to safely scale deployments across the EVM.</p></blockquote><div><hr></div><h3>Factories</h3><p>Up to now, we&#8217;ve looked at how contracts are deployed and how proxies help with upgrades. But there&#8217;s another pattern you&#8217;ll see everywhere in production <strong>EVM</strong> code: <strong>the factory.</strong></p><p>A factory is simply a contract that knows how to <strong>deploy other contracts</strong>. Instead of deploying each new contract by hand, you interact with a single factory contract that:</p><ul><li><p>Spins up new instances (<code>CREATE</code> or <code>CREATE2</code>)</p></li><li><p>Initializes them safely</p></li><li><p>Emits events so you can track them on-chain</p></li></ul><p>Think of it as an <strong>assembly line for contracts</strong>. You give the factory the parameters, it returns a fresh instance.</p><h3>Why Factories?</h3><p>Factories are used by almost every major protocol because they bring:</p><ul><li><p><strong>Standardization: </strong>every instance is deployed with the same code path.</p></li><li><p><strong>Discoverability: </strong>you can query factory events or mappings to find all deployed instances.</p></li><li><p><strong>Initialization safety: </strong>the factory usually calls the initializer right after deployment, so you don&#8217;t end up with half-baked contracts someone else can hijack.</p></li><li><p><strong>Determinism: </strong>with <code>CREATE2</code>, a factory can precompute an instance&#8217;s address before it even exists (great for &#8220;counterfactual&#8221; wallets or deterministic pool addresses).</p></li></ul><h3>Example:</h3><p><code>basicfactory.sol:</code></p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Counter {
    address public owner;
    uint256 public value;
    constructor(address _owner, uint256 start) {
        owner = _owner;
        value = start;
    }
    function inc() external {
        require(msg.sender == owner, &#8220;not owner&#8221;);
        value += 1;
    }
}

contract CounterFactory {
    event CounterCreated(address indexed counter, address indexed owner, uint256 start);

    function createCounter(address owner_, uint256 start_) external returns (address addr) {
        for (uint256 i = 0; i &lt; 5; i++) {
            Counter c = new Counter(owner_, start_);
            addr = address(c);
            emit CounterCreated(addr, owner_, start_);
        }
    }
}</code></pre><blockquote><p><strong>Note:</strong> <code>createCounter</code> here is not deployed with <code>CREATE2</code> but it could be easily achieved by getting the bytecode of <code>Counter</code> contract and executing <code>create2(...)</code></p></blockquote><p><strong>Setup the node:</strong></p><pre><code>anvil</code></pre><p><strong>Deploy the contract and call it</strong></p><pre><code>// Deploy the factory contract
forge create src/basicfactory.sol:CounterFactory \
  --rpc-url localhost:8545 \
  --private-key &lt;YOUR-ANVIL-PK&gt;

// Expected output:
// [&#10250;] Compiling...
// [&#10258;] Compiling 1 files with Solc 0.8.30
// [&#10274;] Solc 0.8.30 finished in 52.99ms
// Compiler run successful!
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0x610178dA211FEF7D417bC0e6FeD39F05609AD788
// Transaction hash: 0x5c25685d15aacde7128a648d48dcd8b975ffc6e20aac9605bce2f582125b6437
// Call the factory
cast send 0x610178dA211FEF7D417bC0e6FeD39F05609AD788 \
  &#8220;createCounter(address,uint256)(address)&#8221; \
  0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 42 \
  --rpc-url localhost:8545 --private-key &lt;YOUR-ANVIL-PK&gt;
// Logs expected in after this call with topics for each emited event
// and there you will find the contract addresses</code></pre><h3>Summary</h3><p>Factories turn contract creation into a repeatable process.<br>Instead of deploying each instance manually, you get a single trusted entry point that handles deployment, initialization, and tracking making large on-chain systems both safer and easier to manage.</p><p>They&#8217;re the backbone behind scalable architectures in DeFi, from liquidity pools to vaults and smart wallets.<br>Once you understand factories, you can start designing protocols that deploy and manage entire ecosystems of contracts automatically.</p><p>By the end, you&#8217;ll understand why nearly every modern protocol includes a factory&#8202;&#8212;&#8202;and how to build your own to safely scale deployments across the EVM.</p>]]></content:encoded></item><item><title><![CDATA[Proxies and Upgradability - UUPS proxy (EIP-1822)]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability-uups-proxy</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability-uups-proxy</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:52:30 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>In the previous post, we explored the <strong>Transparent Proxy (EIP-1967)</strong> pattern where the proxy holds both the forwarding logic and the upgrade control plane.<br>That design works, but it comes with baggage: an extra admin contract, more bytecode, and slightly higher gas overhead.</p><p><strong>UUPS (Universal Upgradeable Proxy Standard, EIP-1822)</strong> takes a leaner approach.<br>It keeps the proxy as a <strong>minimal forwarder</strong> and moves the upgrade logic into the <strong>implementation contract</strong> itself.<br>Instead of calling <code>upgradeTo</code> on the proxy, you call it on the implementation through the proxy.<br>This makes the proxy reusable, smaller, and easier to reason about, while each implementation defines its own upgrade rules.</p><p>In this post, you&#8217;ll learn:</p><ul><li><p><strong>How UUPS differs from the Transparent Proxy pattern</strong></p></li><li><p><strong>The role of </strong><code>proxiableUUID</code><strong> and the </strong><code>keccak256(&#8221;PROXIABLE&#8221;)</code><strong> storage slot</strong></p></li><li><p><strong>How to deploy and upgrade a UUPS proxy end-to-end with Foundry</strong></p></li></ul><p>By the end, you&#8217;ll understand how modern protocols (and OpenZeppelin&#8217;s <code>UUPSUpgradeable</code> contracts) achieve upgradability with less overhead and the trade-offs that come with that simplicity.</p></blockquote><div><hr></div><h3>UUPS proxy (<a href="https://eips.ethereum.org/EIPS/eip-1822">EIP-1822</a>)</h3><p><a href="https://medium.com/@andrey_obruchkov/understanding-contract-deployments-proxies-and-create2-part-1-696b0b11f8a5">Transparent proxies</a> are fine, but they have an extra baggage: you have to maintain both the proxy contract and the upgrade admin logic. <strong>UUPS (Universal Upgradeable Proxy Standard)</strong> flips that design.</p><p>UUPS proxies instead of making the proxy contract responsible for upgrades, UUPS pushes that responsibility into the implementation contract. The proxy itself is just a &#8220;dumb forwarder&#8221; that delegates all calls. The implementation contains a special function (usually called <code>proxiableUUID</code> or <code>upgradeTo</code>) that knows how to upgrade to a new version. This keeps the proxy very small and reusable, while each implementation can define its own rules for who&#8217;s allowed to upgrade.</p><p>the address of the Logic Contract is stored at the defined storage position <code>keccak256(&#8221;PROXIABLE&#8221;)=0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7</code>.</p><p>Key differences:</p><ul><li><p>The proxy is slimmer: no upgrade code inside it.</p></li><li><p>Each implementation contract must include an upgrade function (and usually OpenZeppelin&#8217;s <code>UUPSUpgradeable</code> mixin).</p></li><li><p>Storage layout still matters: if you change storage order between upgrades, you&#8217;ll corrupt state.</p></li></ul><h4>Example:</h4><p><code>UUPSLogicContract.sol</code></p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

// The proxy
contract Proxiable {
    // Code position in storage is keccak256(&#8221;PROXIABLE&#8221;) = &#8220;0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7&#8221;
    function updateCodeAddress(address newAddress) internal {
        require(
            bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(),
            &#8220;Not compatible&#8221;
        );
        assembly { // solium-disable-line
            sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress)
        }
    }
    function proxiableUUID() public pure returns (bytes32) {
        return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7;
    }
}
// Controls that only owner can do changes
contract Owned {
    address owner;
    function setOwner(address _owner) internal {
        owner = _owner;
    }
    modifier onlyOwner() {
        require(msg.sender == owner, &#8220;Only owner is allowed to perform this action&#8221;);
        _;
    }
}

contract LibraryLockDataLayout {
  bool public initialized = false;
}

// Locking mechaninsm.
contract LibraryLock is LibraryLockDataLayout {
    // Ensures no one can manipulate the Logic Contract once it is deployed.
    // PARITY WALLET HACK PREVENTION
    modifier delegatedOnly() {
        require(initialized == true, &#8220;The library is locked. No direct &#8216;call&#8217; is allowed&#8221;);
        _;
    }
    function initialize() internal {
        initialized = true;
    }
}

contract ERC20DataLayout is LibraryLockDataLayout {
  uint256 public totalSupply;
  mapping(address=&gt;uint256) public tokens;
}

contract MyToken is Owned, ERC20DataLayout, Proxiable, LibraryLock {
    function constructor1(uint256 _initialSupply) public {
        totalSupply = _initialSupply;
        tokens[msg.sender] = _initialSupply;
        initialize();
        setOwner(msg.sender);
    }
    function updateCode(address newCode) public onlyOwner delegatedOnly  {
        updateCodeAddress(newCode);
    }
    function transfer(address to, uint256 amount) public delegatedOnly {
        require(tokens[msg.sender] &gt;= amount, &#8220;Not enough funds for transfer&#8221;);
        tokens[to] += amount;
        tokens[msg.sender] -= amount;
    }
}</code></pre><blockquote><p><em><strong>Note:</strong> A lot of precautions added here, the main contract is MyToken that inherits Proxiable. other inherits keep the contract safe from different possible attacks.</em></p></blockquote><p><code>UUPSProxy.sol:</code></p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

/*
 * Very simplified UUPS pattern.
 * - Proxy stores state and delegates calls.
 * - Implementation holds upgrade logic.
 */
// ---------------- Proxy ----------------
contract UUPSProxy {
    // Code position in storage is keccak256(&#8221;PROXIABLE&#8221;) = &#8220;0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7&#8221;
    constructor(bytes memory constructData, address contractLogic) {
        // save the code address
        assembly {
            sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic)
        }
        // call the constructor
        (bool success,  ) = contractLogic.delegatecall(constructData);
        require(success, &#8220;Construction failed&#8221;);
    }
    // This fallback will actually call the logic contract
    // because every function with databytes will arrive here
    fallback() external payable {
        assembly {
            // load the logic contract address
            let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)
            calldatacopy(0x0, 0x0, calldatasize())
            // call the logic contract with the databytes
            let success := delegatecall(sub(gas(), 10000), contractLogic, 0x0, calldatasize(), 0, 0)
            let retSz := returndatasize()
            returndatacopy(0, 0, retSz)
            switch success
            case 0 {
                revert(0, retSz)
            }
            default {
                return(0, retSz)
            }
        }
    }
}</code></pre><p><strong>Lets spin the node:</strong></p><pre><code>anvil</code></pre><p><strong>and deploy the two contracts from a different terminal</strong></p><pre><code>// Deploy the contract logic
forge create src/UUPSLogicContract.sol:MyToken --rpc-url localhost:8545 --private-key &lt;YOUR-ANVIL-PRIVATE-KEY&gt; --broadcast

// expected output, something like:
// [&#10250;] Compiling...
// [&#10258;] Compiling 1 files with Solc 0.8.30
// [&#10246;] Solc 0.8.30 finished in 69.50ms
// Compiler run successful!
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
// Transaction hash: 0xfd7f81e7a54dba55a1a6399e465ed9ff67dc0d95a3f2fbbf85542fd7b0ffdf81
// Deploy the UUPS proxy
INIT_DATA=$(cast calldata &#8220;constructor1(uint256)&#8221; 1000000)
forge create src/UUPSProxy.sol:UUPSProxy --rpc-url localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --broadcast --constructor-args &#8220;$INIT_DATA&#8221; 0x5FbDB2315678afecb367f032d93F642f64180aa3
// expected output, something like:
// [&#10250;] Compiling...
// No files changed, compilation skipped
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
// Transaction hash: 0x60c04bf75c647a9c5bcd73155c8906675247ccf4f25fab20bf76e1b5a4ef129e</code></pre><p>Now lets call the auto-generated getter on MyToken contract via the proxy:</p><pre><code>// Note that 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 is the proxy address
cast call 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 &#8220;totalSupply()(uint256)&#8221; --rpc-url http://localhost:8545</code></pre><h3>Summary</h3><p>UUPS proxies strip upgradeability down to its simplest form.<br>The proxy only knows how to forward calls. The <strong>implementation</strong> handles upgrades through its own <code>updateCode</code> (or <code>upgradeTo</code>) function, secured by access control.</p><p>This pattern reduces bytecode size, deployment cost, and complexity but it also puts more responsibility on the implementation contract. A single mistake in upgrade logic can brick the system, which is why production frameworks (like OpenZeppelin&#8217;s <code>UUPSUpgradeable</code>) wrap it with guards and the ERC-1967 slot standard.</p><p>Understanding UUPS gives you a deeper sense of how real protocols balance <strong>immutability with flexibility</strong>, and how much power (and risk) lives behind a single <code>delegatecall</code>.</p>]]></content:encoded></item><item><title><![CDATA[Proxies and Upgradability - Transparent Proxy (EIP-1967)]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/proxies-and-upgradability</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:51:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>Once you understand how contracts get on-chain, the next big question is how they <em>stay useful</em> after deployment.<br>Ethereum contracts are immutable by design once the bytecode is there, it can&#8217;t be changed. That&#8217;s great for trust, but painful for reality: bugs happen, features evolve, and governance rules shift.</p><p>To solve that, developers use <strong>proxies, </strong>minimal contracts that never move, while their logic can be replaced underneath.<br>They keep the same address, preserve all user state, and route execution to a separate implementation contract using <code>delegatecall</code>.</p><p>In this post, we&#8217;ll break down the most common proxy pattern: <strong>the Transparent Proxy (EIP-1967)</strong> and see exactly how it works in practice:</p><ul><li><p><strong>How delegatecall lets logic live elsewhere while state stays local</strong></p></li><li><p><strong>Why EIP-1967 standardizes admin and implementation storage slots</strong></p></li><li><p><strong>How to deploy, upgrade, and inspect a proxy on-chain with Foundry</strong></p></li></ul><p>By the end, you&#8217;ll understand how protocols like Aave and OpenZeppelin upgrade contracts without breaking users and how to do the same safely.</p></blockquote><div><hr></div><h3>Proxies and Upgradability</h3><p>Smart contracts are, by design, immutable. Once deployed, their bytecode can&#8217;t be changed. That immutability is great for trust, but painful for real protocols: bugs happen, standards evolve, governance rules change. How do you &#8220;upgrade&#8221; logic without asking every user to migrate to a new address?</p><p>The answer is <strong>proxies. </strong>Contracts that never change their address but forward calls to an implementation that can be swapped.</p><h4>The Core Idea</h4><p>A proxy is a thin contract that:</p><ol><li><p>Receives a call.</p></li><li><p>Forwards it (via <code>delegatecall</code>) to another contract, called the <strong>implementation</strong>.</p></li><li><p>Returns the result to the user.</p></li></ol><p>Because <code>delegatecall</code> executes in the caller&#8217;s context, all storage is preserved in the proxy, while the logic lives in the implementation. Swap the implementation &#8594; you&#8217;ve &#8220;upgraded&#8221; the contract, but users keep interacting with the same address.</p><h3>Transparent Proxy (<a href="https://eips.ethereum.org/EIPS/eip-1967">EIP-1967</a>)</h3><p><strong>Problem:</strong> Contracts are immutable, but real systems need fixes and new features.<br><strong>Solution:</strong> A <strong>Transparent Proxy</strong> keeps one public address and forwards user calls to a swappable implementation. It uses fixed storage slots from <br><strong><a href="https://eips.ethereum.org/EIPS/eip-1967">EIP-1967</a></strong> so tools and audits know exactly where admin/implementation live.</p><h4><strong>Two faces of the same proxy</strong></h4><p><strong>User surface (normal callers)</strong></p><ul><li><p>Any caller <strong>that is not the admin</strong> hits the proxy&#8217;s <code>fallback</code>.</p></li><li><p>The proxy <code>delegatecall</code><strong>s</strong> to the current <strong>implementation</strong> and returns the result.</p></li><li><p>All <strong>state lives in the proxy</strong>; the implementation is just logic.</p></li></ul><p><strong>Admin surface (upgrade operator)</strong></p><ul><li><p>If the caller <strong>is the admin</strong>, the proxy <strong>does not delegate</strong>.</p></li><li><p>The admin can call explicit management functions like <code>upgradeTo</code>, <code>changeAdmin</code>, etc.</p></li><li><p>This prevents an admin from accidentally hitting app code through the fallback (function-selector clashes).</p></li></ul><p>That split is why it&#8217;s called <strong>transparent</strong>: to users the proxy behaves like the app and to the admin it exposes a tiny control plane.</p><p>The three canonical EIP-1967 slots (readable on any explorer/RPC)</p><ul><li><p><strong>Implementation slot(holds the address of the executed contract)</strong><br><code>0x360894A13BA1A3210667C828492DB98DCA3E2076CC3735A920A3CA505D382BBC</code></p></li><li><p><strong>Admin slot (holds the address of the proxy contract admin)</strong><br><code>0xB53127684A568B3173AE13B9F8A6016E243E63B6E8EE1178D6A717850B5D6103</code></p></li><li><p><strong>Beacon slot</strong> (for Beacon proxies. <strong>We will not cover it here</strong>)<br><code>0xA3F0AD74E5423AEBFD80D3EF4346578335A9A72AEAEE59FF6CB3582B35133D50</code></p></li></ul><blockquote><p><em><strong>Note: </strong>You can inspect them with </em><code>eth_getStorageAt</code><em> / </em><code>cast storage</code><em> and read the last 20 bytes as an address.</em></p></blockquote><h4><strong>Important operational gotcha (EOA vs. ProxyAdmin):</strong></h4><p>Do <strong>not</strong> set your everyday EOA as the proxy <strong>admin</strong> if you also need to use the dapp from that address.</p><ul><li><p><strong>Admins cannot hit fallback</strong> &#8594; they <strong>can&#8217;t use the app</strong> through the proxy.</p></li><li><p>Best practice: make the proxy admin a <strong>separate</strong> authority (usually an <strong>OpenZeppelin </strong><code>ProxyAdmin</code> contract controlled by a multisig/timelock). Your user EOAs remain normal users.</p></li></ul><p>Lets take a look at minimal Transparent Proxy implementation:</p><p>Contract implementation<code>StorageV1.sol</code><strong> :</strong></p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity 0.8.12;

contract StorageV1 {
    // IMPORTANT: storage lives in the proxy; layout must be stable across upgrades.
    uint256 public number;
    string public ownerName;
    bool private _initialized;
    // Will be initialized only once, you can use openzeppelin guard
    function initialize(uint256 n, string calldata who) external {
        require(!_initialized, &#8220;already initialized&#8221;);
        _initialized = true;
        number = n;
        ownerName = who;
    }
    function setNumber(uint256 n) external {
        number = n;
    }
}</code></pre><p>Other contract implementation <code>StorageV2.sol</code>:</p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

import &#8220;./StorageV1.sol&#8221;;
contract StorageV2 is StorageV1 {
    // new vars only at the end
    string public note;
    function setNote(string calldata s) external { note = s; }
    function double() external view returns (uint256) { return number * 2; }
}</code></pre><p>Transparent Proxy implementation<code>TransparentProxy1967.sol</code><strong> :</strong></p><pre><code>// SPDX-License-Identifier: MIT
pragma solidity 0.8.12;

/// @title Minimal Transparent Proxy (EIP-1967) &#8211; for education/demo
/// @notice Admin gets upgrade functions; non-admin callers are delegated to implementation.
///         Admin is blocked from fallback to avoid selector clashes.
contract TransparentProxy1967 {
    // EIP-1967 slots (implementation, admin)
    bytes32 private constant _IMPLEMENTATION_SLOT =
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    bytes32 private constant _ADMIN_SLOT =
        0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    event Upgraded(address indexed implementation);
    event AdminChanged(address previousAdmin, address newAdmin);
    // logic        - The address of the logic contract
    // initialAdmin - The admin of this proxy that can upgrade logic
    // data         - &#8220;constructor&#8221; for the logic contract
    constructor(address logic, address initialAdmin, bytes memory data) payable {
        require(_isContract(logic), &#8220;Proxy: logic not a contract&#8221;);
        require(initialAdmin != address(0), &#8220;Proxy: admin zero&#8221;);
        _setAddress(_ADMIN_SLOT, initialAdmin);
        _setAddress(_IMPLEMENTATION_SLOT, logic);
        // optional initializer call (acts like a constructor for the implementation)
        if (data.length &gt; 0) {
            (bool ok, bytes memory err) = logic.delegatecall(data);
            require(ok, string(err));
        }
    }
    // -------- Admin control plane --------
    modifier ifAdmin() {
        if (msg.sender == _admin()) {
            _;
        } else {
            _fallback();
        }
    }
    function admin() external ifAdmin returns (address) { return _admin(); }
    function implementation() external ifAdmin returns (address) { return _implementation(); }
    function changeAdmin(address newAdmin) external ifAdmin {
        require(newAdmin != address(0), &#8220;Proxy: admin zero&#8221;);
        emit AdminChanged(_admin(), newAdmin);
        _setAddress(_ADMIN_SLOT, newAdmin);
    }
    function upgradeTo(address newImplementation) external ifAdmin {
        _upgradeTo(newImplementation);
    }
    function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
        _upgradeTo(newImplementation);
        (bool ok, bytes memory err) = newImplementation.delegatecall(data);
        require(ok, string(err));
    }
    // -------- User surface (fallback/delegate) --------
    fallback() external payable { _fallback(); }
    receive() external payable { _fallback(); }
    function _fallback() internal {
        require(msg.sender != _admin(), &#8220;Transparent: admin cannot fallback&#8221;);
        _delegate(_implementation());
    }
    function _delegate(address impl) internal {
        assembly {
            // 0x00&#8211;0x3f: scratch space the compiler may use temporarily.
            // Using memory at 0x00 is safe in this proxy fallback
            // because the assembly block never returns to Solidity.
            calldatacopy(0, 0, calldatasize())
            let ok := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch ok
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
    // -------- Slot helpers &amp; guards --------
    function _admin() internal view returns (address a) { a = _getAddress(_ADMIN_SLOT); }
    function _implementation() internal view returns (address a) { a = _getAddress(_IMPLEMENTATION_SLOT); }
    function _upgradeTo(address newImpl) internal {
        require(_isContract(newImpl), &#8220;Proxy: new impl not a contract&#8221;);
        _setAddress(_IMPLEMENTATION_SLOT, newImpl);
        emit Upgraded(newImpl);
    }
    function _getAddress(bytes32 slot) internal view returns (address a) {
        assembly { a := sload(slot) }
    }
    function _setAddress(bytes32 slot, address a) internal {
        assembly { sstore(slot, a) }
    }
    function _isContract(address a) internal view returns (bool) {
        uint256 size; assembly { size := extcodesize(a) }
        return size &gt; 0;
    }
}</code></pre><blockquote><p><em><strong>Note: </strong>Notice how the real contract execution happens in the </em><code>fallback</code><em> function.</em></p></blockquote><p>Take a look that we dont have a constructor in the logic contract. but why?</p><h4>Why a normal constructor doesn&#8217;t work with proxies</h4><ul><li><p><strong>Different storage context:</strong> <code>delegatecall</code> (as learned in one of the <a href="https://medium.com/@andrey_obruchkov/what-every-blockchain-developer-should-know-about-evm-internals-part-2-eab0f4fae3de">previous blog posts</a>) executes the logic code <em>in the caller&#8217;s storage</em> (the proxy). The implementation&#8217;s constructor ran earlier in <em>its own</em> context.</p></li><li><p><strong>Constructors can&#8217;t be re-run:</strong> Once the implement(logic contract) is deployed, its constructor is gone. You can&#8217;t &#8220;constructor-via-delegatecall&#8221; later.</p></li><li><p><strong>You&#8217;ll see zeros via the proxy:</strong> Your constructor may have set <code>owner</code>/<code>number</code> on the logic address, but reading via the proxy will return defaults until you initialize the proxy&#8217;s storage.</p></li><li><p><strong>Security best practice:</strong> In upgradeable patterns we also <strong>lock</strong> the logic contract by disabling initializers so nobody can later &#8220;initialize&#8221; the implementation contract itself.</p></li></ul><p><strong>Lets dive into the fun part:</strong></p><p>lets the local chain with the anvil as we did previously in different terminal</p><pre><code>anvil</code></pre><p>Logic contract deployment:</p><pre><code>forge create src/StorageV1.sol:StorageV1 --rpc-url http://localhost:8545 --private-key &lt;PRIVATE-KEY-FROM-ANVIL&gt; --broadcast

// Output will be:
// [&#10250;] Compiling...
// No files changed, compilation skipped
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
// Transaction hash: 0x35b7d8d461c60c3759ab08a733c2f66e5fd8e22656ca5e158b37eecec605a80e</code></pre><p>Transparent proxy deployment:</p><pre><code>// Previuosly deployed contract
IMPL=0x5FbDB2315678afecb367f032d93F642f64180aa3
// ADMIN is the second address given by anvil, you can use any other admin
// for the sake of simplicity we will use this address
ADMIN=0x70997970C51812dc3A010C7d01b50e0d17dc79C8

# Build FULL calldata (selector + args)
# &#8220;constructor&#8221; of the logic contract
INIT_DATA=$(cast calldata &#8220;initialize(uint256,string)&#8221; 42 &#8220;some-owner&#8221;)
// --private-key - in this case is the private key of the ADMIN we provided previously
// but any other deployer could be used
forge create src/TransparentProxy1967.sol:TransparentProxy1967 \
  --rpc-url http://127.0.0.1:8545 \
  --private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \
  --broadcast \
  --constructor-args $IMPL $ADMIN &#8220;$INIT_DATA&#8221;
// Output should look like that:
// [&#10250;] Compiling...
// No files changed, compilation skipped
// Deployer: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
// Deployed to: 0x8464135c8F25Da09e49BC8782676a84730C318bC
// Transaction hash: 0x389ad3d17e7d8fe850c9325247cd3bd6d9c7bcd59757a73246656d56fb9425c3</code></pre><p>Lets use the transparent proxy contarct as Users:</p><pre><code># read via proxy (uses delegatecall)
cast call 0x8464135c8F25Da09e49BC8782676a84730C318bC &#8220;number()(uint256)&#8221; --rpc-url http://127.0.0.1:8545
cast call 0x8464135c8F25Da09e49BC8782676a84730C318bC &#8220;ownerName()(string)&#8221; --rpc-url http://127.0.0.1:8545

// Output should look like:
// 42
// &#8220;some-owner&#8221;
# write via proxy
cast send 0x8464135c8F25Da09e49BC8782676a84730C318bC &#8220;setNumber(uint256)&#8221; 77 --private-key &lt;YOUR-PRIVATE-KEY&gt; --rpc-url http://127.0.0.1:8545
# Check that it worked
cast call 0x8464135c8F25Da09e49BC8782676a84730C318bC &#8220;number()(uint256)&#8221; --rpc-url http://127.0.0.1:8545
// Expected output: 77</code></pre><p>Lets prove that address can&#8217;t fallback (use logic contract):</p><pre><code># this reverts: &#8220;Transparent: admin cannot fallback&#8221;
cast call 0x8464135c8F25Da09e49BC8782676a84730C318bC &#8220;number()(uint256)&#8221; --from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --rpc-url http://127.0.0.1:8545

// Expected output:
// server returned an error response: error code 3: execution reverted: Transparent: admin cannot fallback...</code></pre><p>Lets inspects transparent proxy slots:</p><pre><code># admin slot (last 20 bytes)
cast storage 0x8464135c8F25Da09e49BC8782676a84730C318bC 0xB53127684A568B3173AE13B9F8A6016E243E63B6E8EE1178D6A717850B5D6103 --rpc-url http://127.0.0.1:8545

# logic contract address slot (last 20 bytes)
cast storage 0x8464135c8F25Da09e49BC8782676a84730C318bC 0x360894A13BA1A3210667C828492DB98DCA3E2076CC3735A920A3CA505D382BBC --rpc-url http://127.0.0.1:8545
// Expected outputs:
// 0x00000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c8
// 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3</code></pre><p>Lets <strong>Upgrade to a new implementation</strong> <code>StorageV2</code> with added functions, same storage prefix)</p><p>Lets the deploy the second contract:</p><pre><code>forge create src/StorageV2.sol:StorageV2 --rpc-url  http://127.0.0.1:8545 --private-key &lt;YOUR-PRIVATE-KEY&gt; --broadcast

// Output should be something like
// [&#10250;] Compiling...
// No files changed, compilation skipped
// Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
// Deployed to: 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
// Transaction hash: 0x80fd23c7145fff6853e17439b1e4d1332b41526a411dfaa842a4f985d55f936c</code></pre><p>Point the transparent proxy to the newly created contract:</p><pre><code>cast send 0x8464135c8F25Da09e49BC8782676a84730C318bC &#8220;upgradeTo(address)&#8221; 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 --from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --rpc-url http://127.0.0.1:8545 --private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d</code></pre><p>now you can re-run the storage and see that the address of the logic contract address is changed.</p><h4>Takeaways</h4><ul><li><p><strong>Transparent Proxy: </strong>&#8220;one address, swappable logic&#8221; with an explicit <strong>admin plane</strong>.</p></li><li><p><strong>Admins cannot use the app through the proxy</strong>; that&#8217;s by design. Keep admin separate (ideally a <code>ProxyAdmin</code> contract controlled by a multisig/timelock).</p></li><li><p>Slots are standardized by <strong>EIP-1967</strong>, which makes inspection and audits predictable.</p></li><li><p>Use <strong>initializers</strong> in logic contracts; constructors don&#8217;t run through proxies.</p></li></ul><h3>Summary</h3><p>Proxies let contracts keep one public address while swapping logic behind the scenes. A Transparent proxy (EIP-1967) routes user calls to an implementation via <code>delegatecall</code>, keeps all state in the proxy, and exposes a separate admin plane for upgrades. You show the three standard slots (implementation/admin/beacon), why constructors don&#8217;t work (use <code>initialize</code>), and a full Foundry walkthrough: deploy V1, deploy proxy with init calldata, read/write via proxy, inspect slots, then upgrade to V2 and confirm state continuity.</p>]]></content:encoded></item><item><title><![CDATA[Deployments & Deterministic Addresses (CREATE vs CREATE2)]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/deployments-and-deterministic-addresses</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/deployments-and-deterministic-addresses</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:48:45 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EFFU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>Most of what confuses people about on-chain systems isn&#8217;t the Solidity itself it&#8217;s how contracts actually <em>get</em> on-chain.<br>What happens in a deployment transaction?<br>How does Ethereum decide where a contract will live?<br>And how can some teams know an address <em>before</em> they even deploy?</p><p>This post breaks down that lifecycle step by step.<br>We&#8217;ll start with first principles: what a deployment transaction really is (<code>to = null</code>, <strong>init code &#8594; runtime code</strong>) and how the EVM derives a contract&#8217;s address using the <strong>CREATE</strong> opcode.<br>Then we&#8217;ll extend that to <strong>CREATE2</strong>, the trick that makes addresses deterministic and lets protocols pre-compute pools, wallets, and factory outputs with mathematical precision.</p><p>By the end, you&#8217;ll be able to:</p><ul><li><p><strong>Compute a contract&#8217;s address manually from a sender and nonce</strong></p></li><li><p><strong>Predict addresses in advance with CREATE2</strong></p></li><li><p><strong>Understand what&#8217;s really stored on-chain (and why constructors disappear after deployment)</strong></p></li></ul></blockquote><div><hr></div><h3>How Contract Deployment Works</h3><p>When you deploy a smart contract, you&#8217;re really just sending a special kind of transaction. Unlike a normal transfer where <code>to</code> is an existing address, a <strong>deployment transaction</strong> sets <code>to = null</code> and uses the transaction&#8217;s calldata to carry the contract&#8217;s <strong>init code</strong>.</p><p>That init code has two jobs:</p><ol><li><p><strong>Run once at deployment: </strong>it executes any constructor logic, sets up initial storage, and processes constructor arguments.</p></li><li><p><strong>Return the runtime code: </strong>the actual bytecode that will live at the contract&#8217;s address permanently.</p></li></ol><p>In other words:</p><ul><li><p><strong>Init code</strong> = &#8220;bootloader.&#8221;</p></li><li><p><strong>Runtime code</strong> = &#8220;the operating system&#8221; that stays on-chain.</p></li></ul><h4>Constructor Arguments</h4><p>When you pass parameters to a constructor in Solidity, they get ABI-encoded and appended to the init code. They&#8217;re consumed during deployment and don&#8217;t remain in the contract bytecode. This is why you don&#8217;t see them in the runtime code when you query <code>eth_getCode</code>.</p><h4>Where Does the Contract Live</h4><p>The EVM deterministically computes a contract&#8217;s address at the moment of deployment:</p><p>With <strong>CREATE (default)</strong>:</p><pre><code>address = keccak256(rlp(sender_address, sender_nonce))[12:]</code></pre><p>Meaning: take the deploying account&#8217;s address + its nonce, RLP-encode them, hash, and use the last 20 bytes.</p><h4>Size Limits</h4><p>There&#8217;s also a ceiling: contracts can&#8217;t be bigger than <strong>24 KB of bytecode</strong> (<a href="https://eips.ethereum.org/EIPS/eip-170">EIP-170</a>).</p><h4>Example</h4><p>As we can see the contract code is not relevant for address computation for <a href="https://www.evm.codes/?fork=cancun#f0">CREATE opcode</a>. Lets use manual computation for calculating contract address after deployment.</p><p>Lets Run Anvil (as we learned <a href="https://medium.com/@andrey_obruchkov/what-every-blockchain-developer-should-know-about-evm-internals-part-3-b6813d964592">here</a>)</p><pre><code>anvil</code></pre><h4><strong>Manual Computation</strong></h4><p>Lets select one of the anvils default addresses and private-keys and check the nonce:</p><p>we can check the nonce with the native EVM call of <code>eth_getTransactionCount</code> or use <code>cast</code> by forge.</p><pre><code>cast nonce &lt;YOUR-ADDRESS&gt; --rpc-url http://localhost:8545</code></pre><p>This will show you the nonce of the address. Now lets use the calculation formula from above. Lets do the RLP encoding. You can take the code for RLP encoding from one of the previous <a href="https://medium.com/@andrey_obruchkov/understanding-ethereum-transactions-and-messages-from-state-changes-to-off-chain-messages-part-1-54130865e71e">blogs</a> or use some online tool. i will be using online tool for clarity.</p><p>Press enter or click to view image in full size</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EFFU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EFFU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 424w, https://substackcdn.com/image/fetch/$s_!EFFU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 848w, https://substackcdn.com/image/fetch/$s_!EFFU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 1272w, https://substackcdn.com/image/fetch/$s_!EFFU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EFFU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png" width="1400" height="544" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:544,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EFFU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 424w, https://substackcdn.com/image/fetch/$s_!EFFU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 848w, https://substackcdn.com/image/fetch/$s_!EFFU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 1272w, https://substackcdn.com/image/fetch/$s_!EFFU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe2c3af6d-2004-49bf-9271-39b07f19cfea_1400x544.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>rlp(sender_address, sender_nonce)</p><blockquote><p><em><strong>Note:</strong> My nonce here was 1 in the response, input your nonce in hex. if it is 0 type 0x</em></p></blockquote><p>Now lets calculate a keccak256 for the next step, ill be using an online tool again:</p><p>Press enter or click to view image in full size</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JRxS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JRxS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 424w, https://substackcdn.com/image/fetch/$s_!JRxS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 848w, https://substackcdn.com/image/fetch/$s_!JRxS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 1272w, https://substackcdn.com/image/fetch/$s_!JRxS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JRxS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png" width="1400" height="882" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:882,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!JRxS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 424w, https://substackcdn.com/image/fetch/$s_!JRxS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 848w, https://substackcdn.com/image/fetch/$s_!JRxS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 1272w, https://substackcdn.com/image/fetch/$s_!JRxS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa4b01dc1-cc6a-4e4d-8637-2d1fe32331b4_1400x882.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>keccak256(rlp(sender_address, sender_nonce))[12:]</p><p>The input here is the output of the previous image and we <strong>take the last 20 bytes</strong> of the hash.</p><p>Lets validate the address with the help of forge, for that we will run the next line:</p><pre><code>// &lt;YOUR-ADDRESS&gt; - can be address from anvil default addresses
cast compute-address &lt;YOUR-ADDRESS&gt; --nonce 1
Computed Address: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512</code></pre><p>We see that the address is the same.</p><h3>Deterministic Deployments with CREATE2</h3><p>Normal contract deployments (<code>CREATE</code>) tie the new address to the deployer&#8217;s account <strong>and its nonce</strong>. That means addresses are sequential and you can&#8217;t know them in advance unless you track nonces.</p><p><strong><a href="https://eips.ethereum.org/EIPS/eip-1014">EIP-1014</a> (CREATE2)</strong> introduced a way to deploy contracts at an address that&#8217;s predictable <em>before the transaction is even mined</em>. This is why people say CREATE2 gives you &#8220;deterministic deployments.&#8221;</p><p>With <strong>CREATE2</strong>:</p><pre><code>address = keccak256( 0xff ++ sender ++ salt ++ keccak256(init_code) )[12:]</code></pre><ul><li><p><code>0xff</code> &#8594; a constant marker byte to avoid collisions with normal CREATE.</p></li><li><p><code>sender</code> &#8594; the deployer&#8217;s address.</p></li><li><p><code>salt</code> &#8594; a 32-byte value you pick.</p></li><li><p><code>init_code</code> &#8594; the same &#8220;bootloader&#8221; we saw earlier (constructor logic + runtime code).</p></li></ul><h4>Example</h4><p>lets use the next contract:</p><pre><code>pragma solidity ^0.8.12;

contract Storage {
    struct my_storage_struct {
        uint256 number;
        string owner;
    }
    my_storage_struct my_storage;

    function store(my_storage_struct calldata new_storage) public {
        if (new_storage.number &gt; 100) {
            revert(&#8221;Number too large&#8221;);
        }
        my_storage = new_storage;
    }
    function retrieve() public view returns (my_storage_struct memory){
        return my_storage;
    }
}</code></pre><p>To determine the contract init code we can use forge cli:</p><pre><code>forge inspect Storage bytecode</code></pre><p>after that we can use one online tool or forge to calculate the keccak256:</p><pre><code>cast keccak $(forge inspect Storage bytecode)</code></pre><p>The output should look like:</p><pre><code>0x9e7ceb5009cf19fc3a77cbead52c79f881b81800108a8931565aa92f9f1f5b64</code></pre><p>Now lets put everything in the formula for the keccak256 input:</p><p>Press enter or click to view image in full size</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BbOZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BbOZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 424w, https://substackcdn.com/image/fetch/$s_!BbOZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 848w, https://substackcdn.com/image/fetch/$s_!BbOZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 1272w, https://substackcdn.com/image/fetch/$s_!BbOZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BbOZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png" width="1400" height="795" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:795,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BbOZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 424w, https://substackcdn.com/image/fetch/$s_!BbOZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 848w, https://substackcdn.com/image/fetch/$s_!BbOZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 1272w, https://substackcdn.com/image/fetch/$s_!BbOZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F068d9b60-3fe5-4431-aa58-c66cbf54ad87_1400x795.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I chose a random salt here of</p><pre><code>0x0000000000000000000000000000000000000000000000000000000000000042</code></pre><p>Lets validate it with forge:</p><pre><code>// &lt;YOUR-ADDRESS&gt; - can be address from anvil default addresses
cast compute-address &lt;YOUR-ADDRESS&gt;\
  --salt 0x0000000000000000000000000000000000000000000000000000000000000042 \
  --init-code-hash 0x9e7ceb5009cf19fc3a77cbead52c79f881b81800108a8931565aa92f9f1f5b64

// Computed Address: 0x93eFaEdEe330e749D9AF79424398204EC04F89F1</code></pre><p>As you can see we got the same address.</p><h4>Why it matters</h4><p>developers guarantee contract addresses <strong>before</strong> <strong>deployment</strong>, enabling things like pre-funded wallets, deterministic DEX pools, and verifiable system contracts.</p><p>ChatGPT said:</p><h3>Summary</h3><p>Contract deployments aren&#8217;t magic they&#8217;re math.<br>A <code>to = null</code> transaction runs <strong>init code</strong> once to produce the on-chain <strong>runtime code</strong>.<br>With <strong>CREATE</strong>, the address is <code>keccak256(rlp(sender, nonce))[12:]</code>; with <strong>CREATE2</strong>, it&#8217;s <code>keccak256(0xff ++ sender ++ salt ++ keccak256(init_code))[12:]</code>.</p><p>CREATE2 makes addresses predictable before deployment perfect for deterministic pools, wallets, and factories.</p>]]></content:encoded></item><item><title><![CDATA[Batching Calls Efficiently with Multicall and RPC batch]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/batching-calls-efficiently-with-multicall</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/batching-calls-efficiently-with-multicall</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:46:50 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>If your app makes dozens of <code>eth_call</code>s for balances, symbols, or reserves, you&#8217;re wasting time and rate-limit budget.<br>Multicall fixes that. It&#8217;s a simple on-chain contract that executes multiple <code>staticcall</code>s in one go, returning all results from a single block snapshot.<br>In this post, you&#8217;ll see how to pack multiple reads into one <code>eth_call</code>, run them through Multicall3 on Polygon Amoy, and decode the results in Go. We&#8217;ll also compare it to JSON-RPC batching and explain why only Multicall guarantees consistent state across all results.</p></blockquote><div><hr></div><h3>Batching Calls with Multicall</h3><p>When your app needs <strong>lots of view calls: </strong>balances, allowances, symbols, pool reserves, doing them one&#8209;by&#8209;one means:</p><ul><li><p>extra <strong>latency</strong> (N round&#8209;trips),</p></li><li><p>higher <strong>provider bill</strong> (N requests),</p></li><li><p>more <strong>rate&#8209;limit</strong> pain (bursts get throttled),</p></li><li><p>and <strong>inconsistent snapshots</strong> (results from slightly different blocks).</p></li></ul><p><strong>Multicall</strong> fixes all of that. It&#8217;s a tiny contract that executes many <code>staticcall</code>s in one go and returns all results so you make <strong>one</strong> <code>eth_call</code>, get <strong>N</strong> answers, <strong>all at the same block</strong>.</p><h3>Why Multicall is a must have</h3><ul><li><p><strong>Fewer RPCs</strong> &#8594; fewer rate&#8209;limit hits + lower cost.</p></li><li><p><strong>Lower latency</strong> &#8594; one round&#8209;trip instead of many.</p></li><li><p><strong>Consistent state</strong> &#8594; all reads at the <strong>same block</strong> (no &#8220;half-old&#8221; data).</p></li></ul><h3>How its done</h3><p>You ABI&#8209;encode each read (e.g., <code>balanceOf(user)</code>), pack them into an array &#8594; call Multicall&#8217;s <code>aggregate/tryAggregate</code> via <strong>one</strong> <code>eth_call</code> &#8594; decode each return blob.</p><blockquote><p><em><strong>Note:</strong> you can find Multicall deployments <a href="https://www.multicall3.com/">here</a></em></p></blockquote><p><strong>Example:</strong></p><pre><code>package main

import (
   &#8220;context&#8221;
   &#8220;fmt&#8221;
   &#8220;log&#8221;
   &#8220;math/big&#8221;
   &#8220;strings&#8221;
   &#8220;github.com/ethereum/go-ethereum&#8221;
   &#8220;github.com/ethereum/go-ethereum/accounts/abi&#8221;
   &#8220;github.com/ethereum/go-ethereum/common&#8221;
   &#8220;github.com/ethereum/go-ethereum/ethclient&#8221;
)

const multicallABI = `[  {    &#8220;inputs&#8221;:[      {&#8221;internalType&#8221;:&#8221;bool&#8221;,&#8221;name&#8221;:&#8221;requireSuccess&#8221;,&#8221;type&#8221;:&#8221;bool&#8221;},      {        &#8220;components&#8221;:[          {&#8221;internalType&#8221;:&#8221;address&#8221;,&#8221;name&#8221;:&#8221;target&#8221;,&#8221;type&#8221;:&#8221;address&#8221;},          {&#8221;internalType&#8221;:&#8221;bytes&#8221;,&#8221;name&#8221;:&#8221;callData&#8221;,&#8221;type&#8221;:&#8221;bytes&#8221;}        ],
        &#8220;internalType&#8221;:&#8221;struct Call[]&#8221;,
        &#8220;name&#8221;:&#8221;calls&#8221;,
        &#8220;type&#8221;:&#8221;tuple[]&#8221;
      }
    ],
    &#8220;name&#8221;:&#8221;tryAggregate&#8221;,
    &#8220;outputs&#8221;:[
      {
        &#8220;components&#8221;:[
          {&#8221;internalType&#8221;:&#8221;bool&#8221;,&#8221;name&#8221;:&#8221;success&#8221;,&#8221;type&#8221;:&#8221;bool&#8221;},
          {&#8221;internalType&#8221;:&#8221;bytes&#8221;,&#8221;name&#8221;:&#8221;returnData&#8221;,&#8221;type&#8221;:&#8221;bytes&#8221;}
        ],
        &#8220;internalType&#8221;:&#8221;struct Result[]&#8221;,
        &#8220;name&#8221;:&#8221;returnData&#8221;,
        &#8220;type&#8221;:&#8221;tuple[]&#8221;
      }
    ],
    &#8220;stateMutability&#8221;:&#8221;nonpayable&#8221;,
    &#8220;type&#8221;:&#8221;function&#8221;
  }
]`

const erc20ABI = `[  {&#8221;name&#8221;:&#8221;balanceOf&#8221;,&#8221;type&#8221;:&#8221;function&#8221;,&#8221;stateMutability&#8221;:&#8221;view&#8221;,&#8221;inputs&#8221;:[{&#8221;name&#8221;:&#8221;owner&#8221;,&#8221;type&#8221;:&#8221;address&#8221;}],&#8221;outputs&#8221;:[{&#8221;type&#8221;:&#8221;uint256&#8221;}]},
  {&#8221;name&#8221;:&#8221;symbol&#8221;,&#8221;type&#8221;:&#8221;function&#8221;,&#8221;stateMutability&#8221;:&#8221;view&#8221;,&#8221;inputs&#8221;:[],&#8221;outputs&#8221;:[{&#8221;type&#8221;:&#8221;string&#8221;}]},
  {&#8221;name&#8221;:&#8221;decimals&#8221;,&#8221;type&#8221;:&#8221;function&#8221;,&#8221;stateMutability&#8221;:&#8221;view&#8221;,&#8221;inputs&#8221;:[],&#8221;outputs&#8221;:[{&#8221;type&#8221;:&#8221;uint8&#8221;}]}
]`

var (
   RPC            = &#8220;https://polygon-amoy.drpc.org&#8221;
   MULTICALL_ADDR = common.HexToAddress(&#8221;0xcA11bde05977b3631167028862bE2a173976CA11&#8221;) // Multicall3
   TOKEN_ADDR     = common.HexToAddress(&#8221;0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904&#8221;) // LINK on Amoy
   USER           = common.HexToAddress(&#8221;0x7F8b1ca29F95274E06367b60fC4a539E4910FD0c&#8221;)
)

func main() {
   ctx := context.Background()
   client, err := ethclient.Dial(RPC)
   if err != nil {
      log.Fatalf(&#8221;dial rpc: %v&#8221;, err)
   }

   mabi, err := abi.JSON(strings.NewReader(multicallABI))
   if err != nil {
      log.Fatalf(&#8221;parse multicall abi: %v&#8221;, err)
   }
   eabi, err := abi.JSON(strings.NewReader(erc20ABI))
   if err != nil {
      log.Fatalf(&#8221;parse erc20 abi: %v&#8221;, err)
   }

   // Build calldata for ERC20 reads
   balData, err := eabi.Pack(&#8221;balanceOf&#8221;, USER)
   if err != nil {
      log.Fatalf(&#8221;pack balanceOf: %v&#8221;, err)
   }

   symData, err := eabi.Pack(&#8221;symbol&#8221;)
   if err != nil {
      log.Fatalf(&#8221;pack symbol: %v&#8221;, err)
   }

   decData, err := eabi.Pack(&#8221;decimals&#8221;)
   if err != nil {
      log.Fatalf(&#8221;pack decimals: %v&#8221;, err)
   }

   type Call struct {
    Target   common.Address
    CallData []byte
   }

   calls := []Call{
      {Target: TOKEN_ADDR, CallData: balData},
      {Target: TOKEN_ADDR, CallData: symData},
      {Target: TOKEN_ADDR, CallData: decData},
   }

   input, err := mabi.Pack(&#8221;tryAggregate&#8221;, false, calls)
   if err != nil {
      log.Fatalf(&#8221;pack tryAggregate: %v&#8221;, err)
   }

   // Single eth_call
   msg := ethereum.CallMsg{To: &amp;MULTICALL_ADDR, Data: input}
   out, err := client.CallContract(ctx, msg, nil)
   if err != nil {
      log.Fatalf(&#8221;CallContract (eth_call): %v&#8221;, err)
   }

   // Decode results
   var results []struct {
      Success    bool
      ReturnData []byte
   }

   if err := mabi.UnpackIntoInterface(&amp;results, &#8220;tryAggregate&#8221;, out); err != nil {
      log.Fatalf(&#8221;unpack tryAggregate: %v&#8221;, err)
   }
   if len(results) != 3 {
      log.Fatalf(&#8221;unexpected results len: %d&#8221;, len(results))
   }

   var (
      balance  *big.Int
      symbol   string
      decimals uint8
   )

   // 0: balanceOf
   if results[0].Success {
      vals, err := eabi.Unpack(&#8221;balanceOf&#8221;, results[0].ReturnData)
      if err != nil {
       log.Fatalf(&#8221;unpack balanceOf: %v&#8221;, err)
      }
      balance = vals[0].(*big.Int)
   } else {
      log.Printf(&#8221;balanceOf failed&#8221;)
   }

   // 1: symbol
   if results[1].Success {
      vals, err := eabi.Unpack(&#8221;symbol&#8221;, results[1].ReturnData)
      if err != nil {
         log.Fatalf(&#8221;unpack symbol: %v&#8221;, err)
      }
      symbol = vals[0].(string)
   } else {
        log.Printf(&#8221;symbol failed&#8221;)
   }

   // 2: decimals
   if results[2].Success {
      vals, err := eabi.Unpack(&#8221;decimals&#8221;, results[2].ReturnData)
      if err != nil {
         log.Fatalf(&#8221;unpack decimals: %v&#8221;, err)
      }
      decimals = vals[0].(uint8)
   } else {
      log.Printf(&#8221;decimals failed&#8221;)
   }

   fmt.Printf(&#8221;Symbol: %s, Decimals: %d\n&#8221;, symbol, decimals)

   if balance != nil {
      fmt.Printf(&#8221;Balance(%s): %s\n&#8221;, TOKEN_ADDR.Hex(), balance.String())
   }
}</code></pre><p>The result will be something like:</p><pre><code>Symbol: LINK, Decimals: 18
Balance(0x0Fd9e8d3aF1aaee056EB9e802c3A762a667b1904): 38000000000000000000</code></pre><h3>JSON-RPC Batching</h3><p>Some providers support sending an array of <code>eth_*</code> requests in one HTTP call. This can be handy for things like fetching balances, block headers, or transaction receipts. But for <code>eth_call</code> it&#8217;s usually not recommended. The node still executes each call separately, and you lose the guarantee that results come from the exact same block. If you care about consistent snapshots and rate-limit savings, <strong>Multicall on-chain is the better option.</strong></p><blockquote><p><em><strong>Note:</strong> JSON-RPC batching helps reduce HTTP requests, but only Multicall guarantees a consistent snapshot across all calls at the same block.</em></p></blockquote><h3>Summary</h3><p>Multicall is one of those rare utilities that quietly powers almost every dashboard, analytics app, and on-chain data service in the ecosystem. It gives you consistency, lower latency, and fewer RPC headaches all in a single <code>eth_call</code>.</p><p>While JSON-RPC batching can reduce HTTP requests, it can&#8217;t guarantee that all results come from the same block. Multicall does. By executing reads on-chain in one static context, you get atomic, synchronized snapshots of state a must for accurate data pipelines or price displays.</p><p>Together with tracing and event streaming, Multicall rounds out the toolkit for anyone building real-time, efficient Ethereum integrations.</p>]]></content:encoded></item><item><title><![CDATA[Catching ETH (Native) Transfers Without Events]]></title><description><![CDATA[I&#8217;m currently open to collaborations and development projects across blockchain, smart contracts, and full-stack systems, feel free to connect if you&#8217;re building something interesting.]]></description><link>https://andreyobruchkov1996.substack.com/p/catching-eth-native-transfers-without</link><guid isPermaLink="false">https://andreyobruchkov1996.substack.com/p/catching-eth-native-transfers-without</guid><dc:creator><![CDATA[0xByteBeetle]]></dc:creator><pubDate>Sat, 08 Nov 2025 18:45:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!xMog!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cc86116-985e-4a16-859f-f9cd4957fbba_608x608.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div><hr></div><blockquote><p>Events (as explained <a href="https://andreyobruchkov1996.substack.com/p/understanding-events-the-evms-built">here</a>) tell a lot but not everything.<br>A contract can move native ETH without emitting a single log. Bridge payouts, refunds, self-destructs they can all happen silently.<br>In this post, you&#8217;ll learn two reliable ways to detect native ETH transfers even when no events are emitted:</p><p>Reading top-level transactions directly.</p><p>Tracing internal calls with <code>trace_replayTransaction</code>.<br>We&#8217;ll walk through the JSON-RPC calls and show how to recursively parse the call tree to find every movement of value inside a transaction.</p></blockquote><div><hr></div><h3>Catching ETH Transfers Without Events</h3><p><strong>Events are great, but they&#8217;re not the whole story.</strong><br>A contract can send <strong>native ETH</strong> with a simple <code>CALL{value: ...}</code> and emit <strong>no logs at all</strong>. Unless the contract <em>chooses</em> to <code>emit</code>, that movement won&#8217;t show up in <code>eth_getLogs</code>. So if you only listen to events, you&#8217;ll miss:</p><ul><li><p>Payouts from routers/bridges/treasuries to users</p></li><li><p>Refunds via <code>CALL{value}</code></p></li><li><p>Self&#8209;destruct payouts (<code>SELFDESTRUCT</code> sending the contract&#8217;s balance)</p></li><li><p>Any internal ETH moves hidden inside a larger transaction</p></li></ul><p>There are two reliable ways to see ETH moving:</p><h3>Top&#8209;level ETH transfers (easy): look at the transaction itself</h3><p>If an EOA sends ETH directly to <code>to</code>, it&#8217;s visible without logs:</p><ul><li><p>Read <code>tx.to</code> and <code>tx.value</code> (wei).</p></li><li><p>If the tx <strong>succeeds</strong>, that amount of ETH moved from <code>from</code> &#8594; <code>to</code>.</p></li></ul><p>Caveats:</p><ul><li><p>If the tx <strong>reverts</strong>, nothing moved.</p></li><li><p>If <code>to</code> is a contract and it makes <em>further</em> payments, those are <strong>internal</strong> (see next).</p></li></ul><h3>Internal ETH transfers (the real deal): trace the execution</h3><p>To see ETH moved <strong>inside</strong> a transaction (contract &#8594; user), you must parse the <strong>call tree</strong>. Use node tracing APIs (as we learned in the <a href="https://medium.com/@andrey_obruchkov/ethereum-node-types-explained-and-why-they-can-make-or-break-your-debugging-fc8d89b724cc">previous post</a>).</p><p>You&#8217;re looking for <strong>calls with non&#8209;zero </strong><code>value</code><strong> :</strong></p><pre><code>curl -s -X POST &lt;YOUR-BLOCKCHAIN-NODE-URL&gt;\
  -H &#8220;Content-Type: application/json&#8221; \
  --data &#8216;{
    &#8220;jsonrpc&#8221;:&#8221;2.0&#8221;,
    &#8220;id&#8221;:1,
    &#8220;method&#8221;:&#8221;trace_replayTransaction&#8221;,
    &#8220;params&#8221;:[
      &#8220;&lt;tx-hash&gt;&#8221;,
      [&#8221;trace&#8221;]
    ]
  }&#8217; \
| jq -r &#8216;.result.trace[]
| select(.action.value!=&#8221;0x0&#8221;)
| {type, from:.action.from, to:.action.to, value:.action.value}&#8217;</code></pre><p>The output will look like this:</p><pre><code>{
  &#8220;type&#8221;: &#8220;call&#8221;,
  &#8220;from&#8221;: &#8220;&lt;addr1&gt;&#8221;,
  &#8220;to&#8221;: &#8220;&lt;addr2&gt;&#8221;,
  &#8220;value&#8221;: &#8220;0x22e92f1cfbaaacd5d&#8221;
}
{
  &#8220;type&#8221;: &#8220;call&#8221;,
  &#8220;from&#8221;: &#8220;&lt;addr3&gt;&#8221;,
  &#8220;to&#8221;: &#8220;&lt;addr4&gt;&#8221;,
  &#8220;value&#8221;: &#8220;0x22e92f1cfbaaacd5d&#8221;
}</code></pre><ul><li><p>Walk the tree depth&#8209;first; <strong>collect any object where </strong><code>value != 0</code>.</p></li></ul><p>Also you can handle:</p><ul><li><p><code>&#8220;type&#8221;: &#8220;SELFDESTRUCT&#8221;</code> &#8594; has a <code>to</code> and <code>value</code> payout.</p></li><li><p><code>&#8220;type&#8221;: &#8220;DELEGATECALL&#8221;</code> &#8594; <strong>value is always 0</strong> (it forwards context), so skip unless you track side&#8209;effects(you can see inner call after delegation).</p></li></ul><blockquote><p><em><strong>Note: </strong>You can just parse every &#8220;node&#8221; recursievly and see if value != 0.</em></p></blockquote><h3>Small pseudo-code example:</h3><pre><code>function collectEthTransfers(node):
  if node.value &gt; 0:
    record(from=node.from, to=node.to, value=node.value, type=node.type)
  for child in node.calls:
    collectEthTransfers(child)</code></pre><h3>Summary</h3><p>ETH can move even when no <code>Transfer</code> event exists through low-level calls, refunds, or self-destructs that never emit logs. By combining top-level transaction reads with execution traces from <code>trace_replayTransaction</code>, you can uncover every hidden movement of value inside a transaction.</p><p>This tracing approach is the foundation for real analytics, accounting, and bridge monitoring tools anywhere you need a full picture of how value actually flowed.</p>]]></content:encoded></item></channel></rss>