<?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"><channel><title><![CDATA[Fullstack Web3 Blog]]></title><description><![CDATA[I blend my passion for Solidity with a deep understanding of smart contract development. It's not just about writing code; it's about creating solutions that ar]]></description><link>https://blog.fullstackwebthree.com</link><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 15:52:50 GMT</lastBuildDate><atom:link href="https://blog.fullstackwebthree.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Bankr's Migration to Doppler Protocol: A Technical Deep Dive]]></title><description><![CDATA[What Happened
Bankr has migrated their token launcher from Clanker to Doppler Protocol, the same professional-grade infrastructure used by Zora, Paragraph, and Noice. This isn't a fork or a copy: Bank]]></description><link>https://blog.fullstackwebthree.com/bankr-s-migration-to-doppler-protocol-a-technical-deep-dive</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/bankr-s-migration-to-doppler-protocol-a-technical-deep-dive</guid><category><![CDATA[Tokenomics]]></category><category><![CDATA[protocols]]></category><category><![CDATA[Blockchain]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Tue, 24 Feb 2026 20:29:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/627413578af49c436c43fbb5/f0de62bd-7d37-4479-a5ed-21f86d176f25.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>What Happened</h2>
<p>Bankr has migrated their token launcher from Clanker to <strong>Doppler Protocol</strong>, the same professional-grade infrastructure used by Zora, Paragraph, and Noice. This isn't a fork or a copy: Bankr is using the <strong>canonical Doppler contracts</strong> on Base, deployed and maintained by Whetstone Research.</p>
<p>This article breaks down the technical architecture, explains the liquidity mechanics, and clarifies what this means if you're trading these tokens.</p>
<blockquote>
<p><strong>Note:</strong> Bankr's initial Feb 10 deployment used <code>ScheduledMulticurveInitializer</code>. Within three days, they switched to <code>DecayMulticurveInitializer</code> with a significantly different curve structure. The article documents both setups. Skip to <a href="#current-setup-feb-13-decaymulticurveinitializer">Current Setup</a> if you only care about how it works now.</p>
</blockquote>
<hr />
<h2>The Core Infrastructure</h2>
<h3>The Airlock Pattern</h3>
<p>Doppler uses a modular "Airlock" architecture where a central orchestrator contract delegates to specialized components:</p>
<pre><code class="language-plaintext">Airlock (0x660eAaEdEBc968f8f3694354FA8EC0b4c5Ba8D12)
    ├── TokenFactory80 → Deploys DERC20 tokens
    │
    ├── Pool Initializer (one of):
    │     ├─ ScheduledMulticurveInitializer (0xA36715d..., Feb 10)
    │     │    └── Hook: binary start-time gate
    │     │
    │     └─ DecayMulticurveInitializer (0xd59ce43..., Feb 13+) ← CURRENT
    │          └── Hook: 80% → 1.2% fee decay over 10s
    │
    ├── NoOpGovernanceFactory → Immutable, no admin controls
    ├── NoOpMigrator → Required param (always reverts)
    └── StreamableFeesLockerV2 → Locks positions, streams fees
</code></pre>
<p><strong>Key takeaway:</strong> Once deployed, these tokens are fully immutable. No admin can change parameters, pause trading, or rug the liquidity.</p>
<hr />
<h2>Original Liquidity Design: Scheduled Multicurve (Feb 10)</h2>
<blockquote>
<p><strong>Historical:</strong> This section describes Bankr's initial setup which was replaced on Feb 13. See <a href="#current-setup-feb-13-decaymulticurveinitializer">Current Setup</a> for the active configuration.</p>
</blockquote>
<p>Bankr's initial deployment used Doppler's <strong>Uniswap V4 Scheduled Multicurve</strong> with four overlapping curve definitions subdivided into ~21 segments.</p>
<h3>What This Means Technically</h3>
<p>Instead of a single liquidity position or full-range V3 LP, Bankr tokens launch with:</p>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Component</p></th><th><p>Specification</p></th></tr><tr><td><p><strong>DEX Version</strong></p></td><td><p>Uniswap V4</p></td></tr><tr><td><p><strong>Fee Tier</strong></p></td><td><p>1.2% (12,000 pips)</p></td></tr><tr><td><p><strong>Tick Spacing</strong></p></td><td><p>200</p></td></tr><tr><td><p><strong>Curve Structure</strong></p></td><td><p>4 curves → ~21 segments</p></td></tr><tr><td><p><strong>Liquidity Lock</strong></p></td><td><p>StreamableFeesLockerV2 (time-locked)</p></td></tr><tr><td><p><strong>Trading Gate</strong></p></td><td><p><code>startingTime</code> timestamp on hook</p></td></tr></tbody></table>

<h3>The Four Curves (Example: BlueWhale deployment)</h3>
<table style="min-width:125px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Curve</p></th><th><p>Tick Range</p></th><th><p>Segments</p></th><th><p>ETH/Segment</p></th><th><p>Purpose</p></th></tr><tr><td><p>1</p></td><td><p>[-230,200 → -219,200]</p></td><td><p>10</p></td><td><p>0.2 ETH</p></td><td><p>Core range — widest coverage</p></td></tr><tr><td><p>2</p></td><td><p>[-222,000 → -215,200]</p></td><td><p>5</p></td><td><p>0.25 ETH</p></td><td><p>Mid concentration</p></td></tr><tr><td><p>3</p></td><td><p>[-216,200 → -210,000]</p></td><td><p>5</p></td><td><p>0.35 ETH</p></td><td><p>Tightest concentration</p></td></tr><tr><td><p>4</p></td><td><p>[-210,800 → +887,200]</p></td><td><p>1</p></td><td><p>0.2 ETH</p></td><td><p>Safety net (thin)</p></td></tr></tbody></table>

<p><strong>Total deployed:</strong> ~4.55 ETH across 21 positions</p>
<h3>Market Cap Depth Map (WETH @ ~$2,700)</h3>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Zone</p></th><th><p>Mcap Range</p></th><th><p>Depth Characteristic</p></th></tr><tr><td><p>\(27K → \)61K</p></td><td><p>Curve 1 only</p></td><td><p>Thin, easy to push through</p></td></tr><tr><td><p><strong>\(61K → \)82K</strong></p></td><td><p><strong>Curves 1+2 overlap</strong></p></td><td><p><strong>~2x depth</strong></p></td></tr><tr><td><p>\(82K → \)108K</p></td><td><p>Curve 2 only</p></td><td><p>Moderate</p></td></tr><tr><td><p><strong>\(108K → \)122K</strong></p></td><td><p><strong>Curves 2+3 overlap</strong></p></td><td><p><strong>~2x depth</strong></p></td></tr><tr><td><p>\(122K → \)189K</p></td><td><p>Curve 3 only</p></td><td><p>Moderate</p></td></tr><tr><td><p><strong>\(189K → \)205K</strong></p></td><td><p><strong>Curves 3+4 overlap</strong></p></td><td><p><strong>~2x depth</strong></p></td></tr><tr><td><p>$205K+</p></td><td><p>Curve 4 only</p></td><td><p><strong>Extremely thin — cliff edge</strong></p></td></tr></tbody></table>

<hr />
<h2>Critical Trading Implications</h2>
<h3>1. Designed for Volatility, Not Stability</h3>
<p>The liquidity structure is optimized for <strong>fast, violent price action</strong> in a narrow mcap band:</p>
<ul>
<li><p><strong>Entire concentrated range:</strong> \(27K → \)205K (7.5x price journey)</p>
</li>
<li><p><strong>Beyond $205K:</strong> Near-zero liquidity, price can moon or crater on minimal volume</p>
</li>
<li><p><strong>All segments activate at once:</strong> The "scheduled" hook is a binary gate (blocked → fully live), not gradual activation</p>
</li>
</ul>
<blockquote>
<p><strong>Bottom line:</strong> These tokens are engineered for micro-cap speculation, not gradual price discovery.</p>
</blockquote>
<h3>2. No External Liquidity</h3>
<p>The Doppler hook <strong>blocks all third-party liquidity additions</strong>. What you see at launch is what you get:</p>
<ul>
<li><p>No retail LPs can add depth to stabilize price</p>
</li>
<li><p>No whale can come in and provide "support"</p>
</li>
<li><p>The curve structure is immutable, no adjustments possible</p>
</li>
</ul>
<h3>3. Locked but Not Burned</h3>
<p>Liquidity positions are locked in the <code>UniswapV4MulticurveInitializer</code>, not burned. This means:</p>
<ul>
<li><p>Fees stream to beneficiaries forever.</p>
</li>
<li><p>Migration is effectively disabled (<code>NoOpMigrator</code> always reverts)</p>
</li>
<li><p>The initializer holds the liquidity position, but the fees are distributed to beneficiaries configured by the user, plus 5% for Doppler.</p>
</li>
</ul>
<hr />
<h2>The "Scheduled" Hook Explained (Feb 10)</h2>
<blockquote>
<p><strong>Historical:</strong> Bankr now uses <code>DecayMulticurveInitializerHook</code> instead. See <a href="#current-setup-feb-13-decaymulticurveinitializer">Current Setup</a>.</p>
</blockquote>
<p>Despite the name, "Scheduled" doesn't mean gradual liquidity activation:</p>
<ol>
<li><p>At creation, a <code>startingTime</code> timestamp is set</p>
</li>
<li><p>Before that time: all swaps revert with <code>CannotSwapBeforeStartingTime()</code></p>
</li>
<li><p>After that time: <strong>all 21 segments are immediately tradeable</strong></p>
</li>
</ol>
<p>There's no phased rollout, no time-based dampening. It's a binary switch from "no trading" to "full volatility."</p>
<hr />
<h2>DERC20: The Token Standard</h2>
<p>Doppler tokens are <strong>DERC20</strong>, an extended ERC20 with additional built-in functionality.</p>
<pre><code class="language-solidity">contract DERC20 is ERC20, ERC20Votes, ERC20Permit, Ownable {
    uint256 public immutable vestingStart;
    uint256 public immutable vestingDuration;
    uint256 public immutable vestedTotalAmount;
    address public pool;
    bool public isPoolUnlocked;
    uint256 public yearlyMintRate; // Max 2%
    string public tokenURI;
}
</code></pre>
<p><strong>Built-in features:</strong></p>
<ul>
<li><p><strong>Vesting:</strong> Up to 80% of supply can be time-locked</p>
</li>
<li><p><strong>Governance:</strong> ERC20Votes for DAO functionality (though irrelevant for Bankr)</p>
</li>
<li><p><strong>Inflation controls:</strong> Max 2% yearly mint rate</p>
</li>
<li><p><strong>Permit2:</strong> Gasless approvals.</p>
</li>
<li><p><strong>Pool locking:</strong> Liquidity locked until conditions met (in our case, forever)</p>
</li>
</ul>
<hr />
<h2>Current Setup (Feb 13): DecayMulticurveInitializer</h2>
<p>Bankr switched to a newer Doppler contract within three days of launch. The following reflects the <strong>current active configuration</strong>.</p>
<h3>Current Contracts</h3>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Contract</p></th><th><p>Address</p></th></tr><tr><td><p>DecayMulticurveInitializer</p></td><td><p><code>0xd59ce43e53d69f190e15d9822fb4540dccc91178</code></p></td></tr><tr><td><p>DecayMulticurveInitializerHook</p></td><td><p><code>0xbB7784A4d481184283Ed89619A3e3ed143e1Adc0</code></p></td></tr></tbody></table>

<p><strong>Source:</strong> <a href="https://github.com/whetstoneresearch/doppler/blob/46bad16d5524661fac04a0e3cd0ea81f7648ecc7/src/initializers/DecayMulticurveInitializer.sol#L36">DecayMulticurveInitializer.sol</a> / <a href="https://github.com/whetstoneresearch/doppler/blob/46bad16d5524661fac04a0e3cd0ea81f7648ecc7/src/initializers/DecayMulticurveInitializerHook.sol">DecayMulticurveInitializerHook.sol</a></p>
<h3>What Changed: Decaying Fee Instead of Start-Time Gate</h3>
<p>The old "Scheduled" hook blocked all swaps until <code>startingTime</code>, then opened the floodgates. The new "Decay" hook takes a different approach: <strong>swaps are always allowed</strong>, but the fee starts extremely high and linearly decays to the terminal rate.</p>
<p>The hook struct (from source, single storage slot):</p>
<pre><code class="language-solidity">struct FeeSchedule {
    uint32 startingTime;
    uint24 startFee;    // Fee at start
    uint24 endFee;      // Terminal fee
    uint24 lastFee;     // Last applied (monotonically decreasing)
    uint32 durationSeconds;
}
</code></pre>
<p><strong>Flow Caster deployment values (verified on-chain):</strong></p>
<table style="min-width:50px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Parameter</p></th><th><p>Value</p></th></tr><tr><td><p>startFee</p></td><td><p>800,000 = <strong>80%</strong></p></td></tr><tr><td><p>endFee</p></td><td><p>12,000 = <strong>1.2%</strong></p></td></tr><tr><td><p>durationSeconds</p></td><td><p><strong>10 seconds</strong></p></td></tr></tbody></table>

<p>The fee decays linearly: <code>currentFee = startFee - (startFee - endFee) * elapsed / durationSeconds</code>. At second 0 it's 80%, at second 5 it's ~41%, at second 10 it locks at 1.2%.</p>
<p>This is an anti-snipe mechanism. Bots that buy in the first seconds get taxed at up to 80%. Wait 10 seconds and you pay a normal 1.2%.</p>
<h3>Current Curve Structure</h3>
<p>The Feb 10 setup used 4 overlapping curves with ~21 segments in a narrow \(27K–\)205K range. The current setup is much simpler:</p>
<table style="min-width:100px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Curve</p></th><th><p>Tick Range</p></th><th><p>Share</p></th><th><p>Mcap Range</p></th></tr><tr><td><p>0 (concentrated)</p></td><td><p>[-230,000 → -120,000]</p></td><td><p>99%</p></td><td><p><strong>~\(27.7K → ~\)1.66B</strong></p></td></tr><tr><td><p>1 (safety net)</p></td><td><p>[-120,000 → +887,200]</p></td><td><p>1%</p></td><td><p><strong>~$1.66B → ∞</strong></p></td></tr></tbody></table>

<p>The concentrated range now extends to <strong>\(1.66B</strong>, roughly 60x wider than the previous \)205K ceiling. No overlapping curves, no stacked "speed bump" zones. One large position holds 99% of supply.</p>
<h3>Revised Comparison</h3>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><th><p>Aspect</p></th><th><p>Bankr in Clanker (V4, current)</p></th><th><p>Bankr in Doppler (Decay, current)</p></th></tr><tr><td><p><strong>DEX</strong></p></td><td><p>Uniswap V4</p></td><td><p>Uniswap V4</p></td></tr><tr><td><p><strong>Tick Range</strong></p></td><td><p>[-230,400 → -120,000]</p></td><td><p>[-230,000 → -120,000]</p></td></tr><tr><td><p><strong>Mcap Range</strong></p></td><td><p>~\(26.7K → ~\)1.66B</p></td><td><p>~\(27.7K → ~\)1.66B</p></td></tr><tr><td><p><strong>Fee</strong></p></td><td><p>1.2% (static, can add a volatility hook that rises it up to 5% depending on market conditions)</p></td><td><p>80% → 1.2% (10s linear decay)</p></td></tr><tr><td><p><strong>Anti-Snipe</strong></p></td><td><p>Dedicated MEV module</p></td><td><p>Fee decay (80% for first 10s)</p></td></tr><tr><td><p><strong>Curves</strong></p></td><td><p>1 (single concentrated range)</p></td><td><p>2 (99%/1% split)</p></td></tr><tr><td><p><strong>Tick Spacing</strong></p></td><td><p>200</p></td><td><p>200</p></td></tr><tr><td><p><strong>External LPs</strong></p></td><td><p>Allowed</p></td><td><p>Blocked by hook</p></td></tr></tbody></table>

<p>The tick ranges are nearly identical, both platforms now target the same ~\(27K → ~\)1.66B market cap corridor.</p>
<h3>Current Trading Implications</h3>
<ul>
<li><p>The "cliff edge at \(205K" no longer applies to new Bankr deployments, the concentrated range now extends to ~\)1.66B</p>
</li>
<li><p>Anti-snipe via fee decay is aggressive: buying in the first 10 seconds costs up to 80%</p>
</li>
<li><p>The safety-net tail (1% of supply beyond $1.66B) means there's <em>some</em> liquidity above the concentrated range. Clanker allows adding liquidity on arbitrary ranges after token deployment.</p>
</li>
</ul>
<hr />
<h2>Summary</h2>
<p>Bankr's migration to Doppler represents a shift in infrastructure philosophy.</p>
<p>Both Clanker and Doppler are professional-grade systems, Clanker is backed by Farcaster/Neynar, has processed billions in volume, and its v4 now offers similar features including custom curves (up to 7 positions), dynamic fees, and MEV protection.</p>
<p>The key differences are economic: Clanker protocol fees are significantly higher than Doppler's. Doppler also offers richer token primitives (DERC20 with built-in governance, vesting, and inflation controls) and more granular curve design flexibility through its multicurve architecture.</p>
<p>The current setup concentrates 99% of liquidity in a ~\(27K–\)1.66B range with a 1% safety net above. Combined with aggressive anti-snipe fees (80% decaying to 1.2% over 10 seconds) and no external LP access, the design prioritizes fee capture from active trading while providing reasonable depth acraoss a wide mcap range.</p>
<p>Trade accordingly.</p>
<hr />
<h2>References</h2>
<ul>
<li><p>Doppler App: <a href="https://app.doppler.lol/">https://app.doppler.lol/</a></p>
</li>
<li><p>Doppler Docs: <a href="https://docs.doppler.lol/">https://docs.doppler.lol/</a></p>
</li>
<li><p>Doppler GitHub: <a href="https://github.com/whetstoneresearch/doppler">https://github.com/whetstoneresearch/doppler</a></p>
</li>
<li><p>Bankr Announcement: <a href="https://x.com/i/status/2021103152246775936">https://x.com/i/status/2021103152246775936</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to read from a smart contract using wagmi v3]]></title><description><![CDATA[Note: This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions ...]]></description><link>https://blog.fullstackwebthree.com/how-to-read-from-a-smart-contract-using-wagmi-v3</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/how-to-read-from-a-smart-contract-using-wagmi-v3</guid><category><![CDATA[#wagmi]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Tue, 10 Feb 2026 16:00:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770144819665/66335186-85b3-4c24-af63-a9109dcbe7b6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>Note:</strong> This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions and improve type safety.</p>
</blockquote>
<p>If you have ever done Web3 frontend, then you must have heard about or used the <a target="_blank" href="https://wagmi.sh/">wagmi</a> library. Wagmi allows devs to make all kind of blockchain interactions on the frontend in a manner that feels very native to React, with a set of 40+ React hooks. From getting info about the connected wallet to smart contract interaction passing through fetching blockchain state data.</p>
<p>As you know, I'm a big fan of the 80/20 rule. For me that means that even if most of the functionality that wagmi has to offer is interesting, you will probably use around 20 percent of it on a regular basis. That leaves us with...8 hooks?</p>
<p>I'm not going to cajole this series of articles to fit that number but the plan is to talk about what I use most frequently at work. I'm also going to focus on doubts and questions that frequently come up so you can speed run your smart contract interaction tasks.</p>
<p>Talking about frequently asked questions, another of the reasons for this article is that the wagmi library continues to evolve with breaking changes. After the significant changes from v1 to v2, wagmi v3 introduces a more streamlined API that better aligns with TanStack Query patterns. The main changes in v3 include:</p>
<ul>
<li><p>Hook renames: <code>useAccount</code> -&gt; <code>useConnection</code> (better reflects the connection model)</p>
</li>
<li><p>Mutation function standardization: hooks now return a mutation object with <code>.mutate()</code> and <code>.mutateAsync()</code> methods instead of direct functions</p>
</li>
<li><p>TypeScript requirement bumped to 5.7.3+</p>
</li>
<li><p>Connector dependencies are now optional peer dependencies (install only the connectors you use)</p>
</li>
</ul>
<p>You can see the full v3 migration guide <a target="_blank" href="https://wagmi.sh/react/guides/migrate-from-v2-to-v3">here</a></p>
<h2 id="heading-first-a-bit-of-setup">First, a bit of setup</h2>
<p>I will assume that you are using a React-based framework (I'm using Next.js) with Typescript.</p>
<p><strong>Note:</strong> If you plan to use wallet connectors (WalletConnect, Coinbase, MetaMask, etc.), you now need to install their peer dependencies manually in v3.</p>
<ol>
<li>Install wagmi v3 and dependencies</li>
</ol>
<pre><code class="lang-bash">npm install wagmi@3 viem@2 @tanstack/react-query
</code></pre>
<ol start="2">
<li><p>Create a custom Provider. We need to set up two providers:</p>
<ol>
<li><p>The <code>WagmiProvider</code> provides blockchain context to your app</p>
</li>
<li><p>The <code>QueryClientProvider</code> from TanStack Query manages data fetching and caching</p>
</li>
</ol>
</li>
</ol>
<p>Don't forget to add <code>use client</code> if you are using Next.js with the App Router.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> { WagmiProvider, createConfig, http } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { mainnet, sepolia } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>;
<span class="hljs-keyword">import</span> { QueryClient, QueryClientProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;

<span class="hljs-comment">// Create wagmi config</span>
<span class="hljs-keyword">const</span> config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
          <span class="hljs-comment">// RPC URL for each chain</span>
    [mainnet.id]: http(
      process.env.NEXT_PUBLIC_ETH_MAINNET_RPC,
    ),
    [sepolia.id]: http(
      process.env.NEXT_PUBLIC_ETH_SEPOLIA_RPC,
    ),
    ),
  },
});

<span class="hljs-comment">// Create TanStack Query client</span>
<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Web3Provider = <span class="hljs-function">(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;WagmiProvider config={config}&gt;
      &lt;QueryClientProvider client={queryClient}&gt;
        {children}
      &lt;/QueryClientProvider&gt;
    &lt;/WagmiProvider&gt;
  );
};
</code></pre>
<p>Finally, add your custom provider to your root <code>layout.tsx</code> file in your Next project.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Web3Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Web3Provider"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{children}: Readonly&lt;{children:React.ReactNode;}&gt;</span>) </span>{
      <span class="hljs-keyword">return</span> (
          &lt;html&gt;
              &lt;body&gt;
                  &lt;Web3Provider&gt;
                      {children}
            &lt;/Web3Provider&gt;
              &lt;/body&gt;
        &lt;/html&gt;
      );
};
</code></pre>
<h4 id="heading-wallet-ui-note-for-v3">Wallet UI note for v3</h4>
<p>wagmi v3 refactored connectors into optional peer dependencies. This reduces bundle size and lets you control wallet SDK versions, but it also means many wallet UI libraries (like <a target="_blank" href="https://www.rainbowkit.com/">Rainbowkit</a> and <a target="_blank" href="https://docs.family.co/connectkit#connectkit">Connectkit</a>) still targeting wagmi v2 need time to update. The result is that v3 can look "unsupported" until you manually install connectors and pass them into <code>createConfig</code>.</p>
<p>If you only need injected wallets (MetaMask, Coinbase Wallet extension, etc.), a small custom button built with wagmi hooks is enough to unblock you while the ecosystem catches up.</p>
<h2 id="heading-read-from-a-single-smart-contract">Read from a single smart contract</h2>
<p>Lets start reading data from a smart contract. You can access data from any function marked as <code>view</code> on a smart contract, and you don't even need that your user's wallet is connected to your application.</p>
<p>Oftentimes you will use these functions to check token ownership or balance, this is how it looks like.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RandomComponent</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { address, isConnected } = useConnection();
  <span class="hljs-keyword">const</span> {
    data: balance,
    isLoading: isLoadingBalance,
    isError: errorBalance,
    isSuccess: successBalance,
  } = useReadContract({
    <span class="hljs-comment">// APE coin</span>
    address: <span class="hljs-string">"0x4d224452801ACEd8B2F0aebE155379bb5D594381"</span>,
    abi: erc20Abi,
    functionName: <span class="hljs-string">"balanceOf"</span>,
    args: [address],
    chainId: <span class="hljs-number">1</span>,
  })
  <span class="hljs-comment">//...Rest of the component ...</span>
}
</code></pre>
<p>Take into account:</p>
<ul>
<li><p>If you are using <code>balance</code> or whatever <code>data</code> you have directly in your component, be sure to handle the <code>undefined</code> case well, as on the first render it will almost 100% return undefined.</p>
</li>
<li><p>wagmi uses Tanstack Query, so the retries are baked in. You don't need to add <code>balance</code> to a <code>useEffect</code> dependency array just to fetch the data.</p>
</li>
<li><p>If your output is a number, it will probably be a BigInt, handle with <code>formatEther</code> to get something readable.</p>
</li>
</ul>
<p>I added <code>chainId</code> because, in my case, my app is configured to be able to read from both Sepolia and ETH Mainnet. Normally, wagmi hooks will fetch the <code>config</code> that you set up in the Provider, but if you need to read data from a different chain in just one specific component, you can define this config and only apply it to that contract call in the hook.</p>
<p>Example on how to read from the USDT contract on Arbitrum, just for this component. We first define the custom <code>config</code> for the component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { http, createConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>
<span class="hljs-keyword">import</span> { arbitrum } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = createConfig({
  chains: [arbitrum],
  transports: {
    [arbitrum.id]: http(),
  },
})
</code></pre>
<p>Then we just use this config on the hook</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RandomComponent</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { address, isConnected } = useConnection();
  <span class="hljs-keyword">const</span> { data: balance } = useReadContract({
    config,
    address: <span class="hljs-string">"0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"</span>,
    abi: erc20Abi,
    functionName: <span class="hljs-string">"balanceOf"</span>,
    args: [address],
    chainId: <span class="hljs-number">42161</span>,
  })
  <span class="hljs-comment">//...Rest of the component ...</span>
}
</code></pre>
<p><strong>What happens if I need to wait for another piece of data as input for the read function?</strong></p>
<p>Remember, if the hook has a dependency on the <code>args</code> array, just handle the <code>undefined</code> case.</p>
<h3 id="heading-auto-refresh-after-writes">Auto-refresh after writes</h3>
<p>If a read depends on a write you just performed, you can refetch once the write confirms. This keeps the read UI fresh without polling.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> writeContract = useWriteContract();
<span class="hljs-keyword">const</span> { data: receipt, isSuccess: isConfirmed } = useWaitForTransactionReceipt({
  hash: writeContract.data <span class="hljs-keyword">as</span> <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span>,
});

<span class="hljs-keyword">const</span> { refetch: refetchBalance } = useReadContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: <span class="hljs-string">"balanceOf"</span>,
  args: [address],
});

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!isConfirmed || !receipt) <span class="hljs-keyword">return</span>;
  refetchBalance();
}, [isConfirmed, receipt, refetchBalance]);
</code></pre>
<h2 id="heading-read-from-multiple-smart-contracts">Read from multiple smart contracts</h2>
<p>You can also use wagmi's hooks to perform batch reads of different contracts, multiple functions inside the same contract or even multiple reads of the same smart contract with different input parameters.</p>
<p>To achieve this, we will use the <a target="_blank" href="https://wagmi.sh/react/api/hooks/useReadContracts#usereadcontracts">useReadContracts</a> hook, it works almost the same, but it takes an array of contract configs under the <code>contracts</code> key</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { data: balanceTest } = useReadContracts({
  contracts: [{
    address: <span class="hljs-string">"0x4d224452801ACEd8B2F0aebE155379bb5D594381"</span>,
    abi: erc20Abi,
    functionName: <span class="hljs-string">"balanceOf"</span>,
    args: [testAddress],
    chainId: <span class="hljs-number">1</span>,
  },
  {
    address: <span class="hljs-string">"0x6B175474E89094C44Da98b954EedeAC495271d0F"</span>,
    abi: erc20Abi,
    functionName: <span class="hljs-string">"balanceOf"</span>,
    args: [testAddress],
    chainId: <span class="hljs-number">1</span>,
  }],
})
</code></pre>
<p>In this case, the response will be an array of objects, "[{result: 0n, status: success}]"</p>
<p>Of course, if you hold the list of contracts in a separate variable, you can use <code>map</code> to iterate over the whole list. This often looks cleaner and the code for the component ends up less cluttered. In this case you need to do a bit more of explicit type casting.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { data: balanceTest } = useReadContracts({
  contracts: contractConfigs.map(<span class="hljs-function"><span class="hljs-params">contract</span> =&gt;</span> ({
    address: contract.address <span class="hljs-keyword">as</span> <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span>,
    abi: contract.abi <span class="hljs-keyword">as</span> Abi,
    functionName: contract.functionName,
    args: contract.args,
    chainId: contract.chainId,
  })),
})
</code></pre>
<h3 id="heading-example-repo">Example repo</h3>
<p>I set up a repo with examples for this series of articles on my <a target="_blank" href="https://github.com/IpastorSan/wagmi-v3-example">Github</a>. Feel free to explore it and run it locally!</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://wagmi.sh/">https://wagmi.sh/</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/guides/migrate-from-v2-to-v3">https://wagmi.sh/react/guides/migrate-from-v2-to-v3</a> (v3 Migration Guide)</p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/query/v5/docs/framework/react/overview">https://tanstack.com/query/v5/docs/framework/react/overview</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/api/hooks/useReadContract#usage">https://wagmi.sh/react/api/hooks/useReadContract#usage</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to listen for and parse smart contract events using wagmi v3]]></title><description><![CDATA[Note: This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions ...]]></description><link>https://blog.fullstackwebthree.com/how-to-listen-for-and-parse-smart-contract-events-using-wagmi-v3</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/how-to-listen-for-and-parse-smart-contract-events-using-wagmi-v3</guid><category><![CDATA[#wagmi]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Tue, 03 Feb 2026 18:47:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770144263932/b6aae7a3-dd9a-47d9-9579-4e52e6b944fd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>Note:</strong> This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions and improve type safety.</p>
</blockquote>
<p>Sometimes, you need to listen for an event emitted by a smart contract in your frontend. Often after making a contract call so the UI can advance to the next step of the process. So, how do we do this?</p>
<h2 id="heading-wagmi">wagmi</h2>
<p>If you have ever done Web3 frontend, then you must have heard about or used the <a target="_blank" href="https://wagmi.sh/">wagmi</a> library. Wagmi allows devs to make all kind of blockchain interactions on the frontend in a manner that feels very native to React, with a set of 40+ React hooks. From getting info about the connected wallet to smart contract interaction passing through fetching blockchain state data.</p>
<p>As you know, I'm a big fan of the 80/20 rule. For me that means that even if most of the functionality that wagmi has to offer is interesting, you will probably use around 20 percent of it on a regular basis. That leaves us with...8 hooks?</p>
<p>I'm not going to cajole this series of articles to fit that number but the plan is to talk about what I use most frequently at work. I'm also going to focus on doubts and questions that frequently come up so you can speed run your smart contract interaction tasks.</p>
<p>Talking about frequently asked questions, another of the reasons for this article is that the wagmi library continues to evolve with breaking changes. After the significant changes from v1 to v2, wagmi v3 introduces a more streamlined API that better aligns with TanStack Query patterns. The main changes in v3 include:</p>
<ul>
<li><p>Hook renames: <code>useAccount</code> -&gt; <code>useConnection</code> (better reflects the connection model)</p>
</li>
<li><p>Mutation function standardization: hooks now return a mutation object with <code>.mutate()</code> and <code>.mutateAsync()</code> methods instead of direct functions</p>
</li>
<li><p>TypeScript requirement bumped to 5.7.3+</p>
</li>
<li><p>Connector dependencies are now optional peer dependencies (install only the connectors you use)</p>
</li>
</ul>
<p>You can see the full v3 migration guide <a target="_blank" href="https://wagmi.sh/react/guides/migrate-from-v2-to-v3">here</a></p>
<h2 id="heading-first-a-bit-of-setup">First, a bit of setup</h2>
<p>I will assume that you are using a React-based framework (I'm using Next.js) with Typescript.</p>
<p><strong>Note:</strong> If you plan to use wallet connectors (WalletConnect, Coinbase, MetaMask, etc.), you now need to install their peer dependencies manually in v3.</p>
<ol>
<li>Install wagmi v3 and dependencies</li>
</ol>
<pre><code class="lang-bash">npm install wagmi@3 viem@2 @tanstack/react-query
</code></pre>
<ol start="2">
<li><p>Create a custom Provider. We need to set up two providers:</p>
<ol>
<li><p>The <code>WagmiProvider</code> provides blockchain context to your app</p>
</li>
<li><p>The <code>QueryClientProvider</code> from TanStack Query manages data fetching and caching</p>
</li>
</ol>
</li>
</ol>
<p>Don't forget to add <code>use client</code> if you are using Next.js with the App Router.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> { WagmiProvider, createConfig, http } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { mainnet, sepolia } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>;
<span class="hljs-keyword">import</span> { QueryClient, QueryClientProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;

<span class="hljs-comment">// Create wagmi config</span>
<span class="hljs-keyword">const</span> config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
       <span class="hljs-comment">// RPC URL for each chain</span>
    [mainnet.id]: http(
      process.env.NEXT_PUBLIC_ETH_MAINNET_RPC,
    ),
    [sepolia.id]: http(
      process.env.NEXT_PUBLIC_ETH_SEPOLIA_RPC,
    ),
    ),
  },
});

<span class="hljs-comment">// Create TanStack Query client</span>
<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Web3Provider = <span class="hljs-function">(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;WagmiProvider config={config}&gt;
      &lt;QueryClientProvider client={queryClient}&gt;
        {children}
      &lt;/QueryClientProvider&gt;
    &lt;/WagmiProvider&gt;
  );
};
</code></pre>
<p>Finally, add your custom provider to your root <code>layout.tsx</code> file in your Next project.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Web3Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Web3Provider"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{children}: Readonly&lt;{children:React.ReactNode;}&gt;</span>) </span>{
      <span class="hljs-keyword">return</span> (
          &lt;html&gt;
              &lt;body&gt;
                  &lt;Web3Provider&gt;
                      {children}
            &lt;/Web3Provider&gt;
              &lt;/body&gt;
        &lt;/html&gt;
      );
};
</code></pre>
<h4 id="heading-wallet-ui-note-for-v3">Wallet UI note for v3</h4>
<p>wagmi v3 made connectors optional peer dependencies. This gives you more control over wallet SDKs but requires you to install and wire them manually. Wallet UI kits (like <a target="_blank" href="https://www.rainbowkit.com/">Rainbowkit</a> and <a target="_blank" href="https://docs.family.co/connectkit#connectkit">Connectkit</a>) are still adapting to this change, which is why v3 adoption looks slower in the ecosystem.</p>
<p>For injected-only flows, a small custom connect button using wagmi hooks is often the simplest, most reliable option.</p>
<h2 id="heading-listen-and-parse-smart-contract-events">Listen and parse smart contract events</h2>
<p>The hook that you will use for creating a smart contract event listener on your frontend is <code>useWatchContractEvent</code>.</p>
<pre><code class="lang-typescript">useWatchContractEvent({
  address: process.env.SMART_CONTRACT_ADDRESS,
  abi: ERC20Abi.abi,
  eventName: <span class="hljs-string">"Minted"</span>,
  onLogs(logs) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Contract event log:"</span>, logs)
  },
});
</code></pre>
<p>This hook does not have a return value (well, <code>void</code>) so all the information received through it has to be handled through the <code>onLogs</code> callback.</p>
<p>Logging the contract event logs like we do in this example will render the whole event object, with things like <code>eventName</code> and <code>args</code> that might be useful to extract information that you may need in your frontend, like fetching the tokenID of the new NFT that has been minted (transferred from the address zero) to your user or the amount of ERC20 that went from one EOA to another.</p>
<h3 id="heading-manually-decoding-event-logs">Manually decoding event logs</h3>
<p>If, for any reason, you need to decode the raw event data received from your smart contract, you can use the lower level viem's <code>decodeEventLog</code> and use it on your <code>useWatchContractEvent</code> hook <code>onLogs</code> callback. You will need to pass it your contract's abi, the <code>data</code> and <code>topics</code> fields that you received in the contract event object.</p>
<pre><code class="lang-typescript">useWatchContractEvent({
  address: process.env.SMART_CONTRACT_ADDRESS,
  abi: ERC721Abi.abi,
  eventName: <span class="hljs-string">"Minted"</span>,
  onLogs(logs) {
    <span class="hljs-keyword">const</span> decodedLogs: <span class="hljs-built_in">any</span> = decodeEventLog({
      abi: ERC721Abi.abi,
      data: logs[<span class="hljs-number">0</span>].data,
      topics: logs[<span class="hljs-number">0</span>].topics,
    });
    <span class="hljs-keyword">const</span> tokenIdString = decodedLogs?.args?.tokenId.toString();
    setTokenId(tokenIdString);
  },
});
</code></pre>
<p>As you see, I'm using the callback to update a React state variable that will be used elsewhere in my component. You can use this hook together or instead of <code>useWaitForTransactionReceipt</code> to wait for the "response" from the smart contract when making a contract call. Both of these hooks include in their responses logs corresponding to the events emitted. The difference is that <code>useWaitForTransactionReceipt</code> is more targeted, as it only deals with one specific transaction hash, while <code>useWatchContractEvent</code> will deal with any events emitted by the contract, no matter who initiated that transaction.</p>
<p>This is why you want to be mindful when using this hook, as you could accidentally get data from a different user than the one currently using the UI.</p>
<h3 id="heading-auto-refresh-after-events">Auto-refresh after events</h3>
<p>If your UI depends on read hooks (balances, ownership, supply), you can refetch them when a relevant event arrives. This keeps the UI in sync without manual refreshes.</p>
<pre><code class="lang-typescript">useWatchContractEvent({
  address: tokenAddress,
  abi: erc20Abi,
  eventName: <span class="hljs-string">"Transfer"</span>,
  onLogs() {
    refetchBalance();
  },
});
</code></pre>
<h3 id="heading-example-repo">Example repo</h3>
<p>I set up a repo with examples for this series of articles on my <a target="_blank" href="https://github.com/IpastorSan/wagmi-v3-example">Github</a>. Feel free to explore it and run it locally!</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://wagmi.sh/">https://wagmi.sh/</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/guides/migrate-from-v2-to-v3">https://wagmi.sh/react/guides/migrate-from-v2-to-v3</a> (v3 Migration Guide)</p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/query/v5/docs/framework/react/overview">https://tanstack.com/query/v5/docs/framework/react/overview</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/api/hooks/useWatchContractEvent">https://wagmi.sh/react/api/hooks/useWatchContractEvent</a></p>
</li>
<li><p><a target="_blank" href="https://viem.sh/docs/contract/decodeEventLog.html">https://viem.sh/docs/contract/decodeEventLog.html</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to call a smart contract using wagmi's v3 useWriteContract hook]]></title><description><![CDATA[Note: This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions ...]]></description><link>https://blog.fullstackwebthree.com/how-to-call-a-smart-contract-using-wagmis-usewritecontract-hook</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/how-to-call-a-smart-contract-using-wagmis-usewritecontract-hook</guid><category><![CDATA[frontend]]></category><category><![CDATA[#wagmi]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[Smart Contracts]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Fri, 02 Jan 2026 13:53:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767362294858/17c04ecd-7b4b-4445-b206-c392111c8544.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>Note:</strong> This article has been updated from the original version to reflect the breaking changes and new patterns introduced in wagmi v3. The core concepts remain the same, but the API syntax has been modernized to align with TanStack Query conventions and improve type safety.</p>
</blockquote>
<p>How do you call a smart contract function that performs a change of state (write) on the blockchain? And how could we be sure that the write transaction went through correctly?</p>
<h2 id="heading-wagmi">wagmi</h2>
<p>If you have ever done Web3 frontend, then you must have heard about or used the <a target="_blank" href="https://wagmi.sh/">wagmi</a> library. Wagmi allows devs to make all kind of blockchain interactions on the frontend in a manner that feels very native to React, with a set of 40+ React hooks. From getting info about the connected wallet to smart contract interaction passing through fetching blockchain state data.</p>
<p>As you know, I'm a big fan of the 80/20 rule. For me that means that even if most of the functionality that wagmi has to offer is interesting, you will probably use around 20 percent of it on a regular basis. That leaves us with...8 hooks?</p>
<p>I'm not going to cajole this series of articles to fit that number but the plan is to talk about what I use most frequently at work. I'm also going to focus on doubts and questions that frequently come up so you can speed run your smart contract interaction tasks.</p>
<p>Talking about frequently asked questions, another of the reasons for this article is that the wagmi library continues to evolve with breaking changes. After the significant changes from v1 to v2, wagmi v3 introduces a more streamlined API that better aligns with TanStack Query patterns. The main changes in v3 include:</p>
<ul>
<li><p>Hook renames: <code>useAccount</code> -&gt; <code>useConnection</code> (better reflects the connection model)</p>
</li>
<li><p>Mutation function standardization: hooks now return a mutation object with <code>.mutate()</code> and <code>.mutateAsync()</code> methods instead of direct functions</p>
</li>
<li><p>TypeScript requirement bumped to 5.7.3+</p>
</li>
<li><p>Connector dependencies are now optional peer dependencies (install only the connectors you use)</p>
</li>
</ul>
<p>You can see the full v3 migration guide <a target="_blank" href="https://wagmi.sh/react/guides/migrate-from-v2-to-v3">here</a></p>
<h2 id="heading-first-a-bit-of-setup">First, a bit of setup</h2>
<p>I will assume that you are using a React-based framework (I'm using Next.js) with Typescript.</p>
<p><strong>Note:</strong> If you plan to use wallet connectors (WalletConnect, Coinbase, MetaMask, etc.), you now need to install their peer dependencies manually in v3.</p>
<ol>
<li>Install wagmi v3 and dependencies</li>
</ol>
<pre><code class="lang-bash">npm install wagmi@3 viem@2 @tanstack/react-query
</code></pre>
<ol start="2">
<li><p>Create a custom Provider. We need to set up two providers:</p>
<ol>
<li><p>The <code>WagmiProvider</code> provides blockchain context to your app</p>
</li>
<li><p>The <code>QueryClientProvider</code> from TanStack Query manages data fetching and caching</p>
</li>
</ol>
</li>
</ol>
<p>Don't forget to add <code>use client</code> if you are using Next.js with the App Router.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> { WagmiProvider, createConfig, http } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi"</span>;
<span class="hljs-keyword">import</span> { mainnet, sepolia } <span class="hljs-keyword">from</span> <span class="hljs-string">"wagmi/chains"</span>;
<span class="hljs-keyword">import</span> { QueryClient, QueryClientProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;

<span class="hljs-comment">// Create wagmi config</span>
<span class="hljs-keyword">const</span> config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
    <span class="hljs-comment">// RPC URL for each chain</span>
    [mainnet.id]: http(
      process.env.NEXT_PUBLIC_ETH_MAINNET_RPC,
    ),
    [sepolia.id]: http(
      process.env.NEXT_PUBLIC_ETH_SEPOLIA_RPC,
    ),
  },
});

<span class="hljs-comment">// Create TanStack Query client</span>
<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Web3Provider = <span class="hljs-function">(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;WagmiProvider config={config}&gt;
      &lt;QueryClientProvider client={queryClient}&gt;
        {children}
      &lt;/QueryClientProvider&gt;
    &lt;/WagmiProvider&gt;
  );
};
</code></pre>
<p>Finally, add your custom provider to your root <code>layout.tsx</code> file in your Next project.</p>
<pre><code class="lang-typescript"><span class="hljs-string">"use client"</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Web3Provider } <span class="hljs-keyword">from</span> <span class="hljs-string">"./Web3Provider"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{children}: Readonly&lt;{children:React.ReactNode;}&gt;</span>) </span>{
      <span class="hljs-keyword">return</span> (
          &lt;html&gt;
              &lt;body&gt;
                  &lt;Web3Provider&gt;
                      {children}
                &lt;/Web3Provider&gt;
              &lt;/body&gt;
        &lt;/html&gt;
      );
};
</code></pre>
<h4 id="heading-wallet-ui-note-for-v3">Wallet UI note for v3</h4>
<p>wagmi v3 moved wallet connector SDKs into optional peer dependencies. That means wagmi no longer bundles wallet SDKs by default, and connector UI libraries (like <a target="_blank" href="https://www.rainbowkit.com/">Rainbowkit</a> and <a target="_blank" href="https://docs.family.co/connectkit#connectkit">Connectkit</a>) need to catch up to the new model. Some popular kits still target wagmi v2, so it can feel like v3 has "no wallet support" until you wire connectors yourself.</p>
<p>Practical path right now:</p>
<ul>
<li><p>install only the connector packages you need (injected, WalletConnect, Coinbase, etc.)</p>
</li>
<li><p>pass them into <code>createConfig</code></p>
</li>
<li><p>or build a small custom connect button with wagmi hooks while the ecosystem updates</p>
</li>
</ul>
<p>If you are using injected wallets only, you can ship a tiny custom button with <code>useConnect</code>, <code>useConnection</code>, and <code>useDisconnect</code> and skip third-party UI kits.</p>
<h2 id="heading-call-a-smart-contract">Call a smart contract</h2>
<p>To call a smart contract using wagmi v3, you will use <code>useWriteContract</code>. The hook returns a mutation object following TanStack Query conventions.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> writeContract = useWriteContract()
</code></pre>
<p><strong>Key changes in v3:</strong> The mutation object exposes several useful properties and methods:</p>
<ul>
<li><p><a target="_blank" href="http://writeContract.data"><code>writeContract.data</code></a> - Returns <code>undefined</code> until you call the contract. Once called, it contains the transaction hash, useful for feeding into <code>useWaitForTransactionReceipt</code>.</p>
</li>
<li><p><code>writeContract.mutate()</code> - Function to execute the contract call. Pass contract interaction params as an object. Use this in <code>onClick</code> handlers or custom functions.</p>
</li>
<li><p><code>writeContract.mutateAsync()</code> - Promise-based version if you need to await the result.</p>
</li>
<li><p><code>writeContract.isPending</code>, <code>writeContract.isSuccess</code>, <code>writeContract.isError</code> - Status flags for tracking the mutation state.</p>
</li>
</ul>
<pre><code class="lang-typescript">    <span class="hljs-keyword">const</span> handleMint = <span class="hljs-function">(<span class="hljs-params">address: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Minting...'</span>);
        writeContract.mutate({
            address: process.env.CONTRACT_ADDRESS <span class="hljs-keyword">as</span> Address,
            abi: erc20Abi,
            functionName: <span class="hljs-string">"mint"</span>,
            args: [address],
        })
    };
</code></pre>
<p>You can also manually change the config for this hook, instead of letting it pick up from the global config. If you do this, you probably will also want to use a hook like <code>useSwitchChain</code> to drive the user to connect to your target chain.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> config = createConfig({
    chains: [arbitrum]
    transports: {
        [arbitrum.id]: http()
    },
})

<span class="hljs-keyword">const</span> writeContract = useWriteContract({config})
</code></pre>
<h3 id="heading-how-to-handle-next-steps">How to handle next steps</h3>
<p>If you only want to check if the tx was sent successfully, you can use the mutation object's status flags (<code>isPending</code>, <code>isSuccess</code>, <code>isError</code>) or pass callbacks to the <code>.mutate()</code> function like <code>onSuccess</code>, <code>onError</code>, or <code>onSettled</code>.</p>
<p>But some times you need to use the tx confirmation, the function return values or even an event emitted by the contract.</p>
<p>This is when <code>useWaitForTransactionReceipt</code> and <code>useWatchContractEvent</code> come in handy. I talk about the latter one on the <a target="_blank" href="https://ignaciopastorsanchez.com/blog/how-to-listen-for-and-parse-smart-contract-events-using-wagmi-v3">next article</a> but lets see now how you would wait for the tx receipt event.</p>
<h2 id="heading-wait-for-the-tx-receipt-with-wagmi">Wait for the tx receipt with wagmi</h2>
<p>After calling a smart contract, you normally wait for some confirmation that your transaction went through and then you make some action.</p>
<p>This is a key element of blockchain UX, your users want to be sure that they did everything right and the action that they intended to make is completed.</p>
<p>This is when <code>useWaitForTransactionReceipt</code> comes into play. Remember the <a target="_blank" href="http://writeContract.data"><code>writeContract.data</code></a> property? That contains the tx hash of our contract call, and that's what we use as input for this hook.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> writeContract = useWriteContract();
<span class="hljs-keyword">const</span> { data:receipt, isLoading: isConfirming, isSuccess: isConfirmed } =
      useWaitForTransactionReceipt({hash: writeContract.data <span class="hljs-keyword">as</span> <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span>});
</code></pre>
<p>The <code>receipt</code> object is a complex one, with lots of data about your transaction. Info about gas usage, transaction type and a <code>logs</code> object which you can decode to get data about events emitted by the transaction.</p>
<p>The logs field includes a <code>data</code> and a <code>topics</code> field. They are hex-encoded, but you can transform them to a human readable format using viem's lower level <code>decodeEventLogs</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> decodedLogs:<span class="hljs-built_in">any</span> = decodeEventLog({
            abi: ERC20Abi,
            data: logs[<span class="hljs-number">0</span>].data,
            topics: logs[<span class="hljs-number">0</span>].topics,
          });
</code></pre>
<h3 id="heading-auto-refresh-after-confirmation">Auto-refresh after confirmation</h3>
<p>A simple pattern is to refetch any related reads once the transaction is confirmed. This keeps the UI in sync without manual refreshes.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> writeContract = useWriteContract();
<span class="hljs-keyword">const</span> { data: receipt, isSuccess: isConfirmed } = useWaitForTransactionReceipt({
  hash: writeContract.data <span class="hljs-keyword">as</span> <span class="hljs-string">`0x<span class="hljs-subst">${<span class="hljs-built_in">string</span>}</span>`</span>,
});

<span class="hljs-keyword">const</span> { refetch: refetchBalance } = useReadContract({
  address: tokenAddress,
  abi: erc20Abi,
  functionName: <span class="hljs-string">"balanceOf"</span>,
  args: [address],
});

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!isConfirmed || !receipt) <span class="hljs-keyword">return</span>;
  refetchBalance();
}, [isConfirmed, receipt, refetchBalance]);
</code></pre>
<h3 id="heading-example-repo">Example repo</h3>
<p>I set up a repo with examples for this series of articles on my <a target="_blank" href="https://github.com/IpastorSan/wagmi-v3-example">Github</a>. Feel free to explore it and run it locally!</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://wagmi.sh/">https://wagmi.sh/</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/guides/migrate-from-v2-to-v3">https://wagmi.sh/react/guides/migrate-from-v2-to-v3</a> (v3 Migration Guide)</p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/query/v5/docs/framework/react/overview">https://tanstack.com/query/v5/docs/framework/react/overview</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/api/hooks/useWriteContract">https://wagmi.sh/react/api/hooks/useWriteContract</a></p>
</li>
<li><p><a target="_blank" href="https://wagmi.sh/react/api/hooks/useWaitForTransactionReceipt#usewaitfortransactionreceipt">https://wagmi.sh/react/api/hooks/useWaitForTransactionReceipt#usewaitfortransactionreceipt</a></p>
</li>
<li><p><a target="_blank" href="https://viem.sh/docs/contract/decodeEventLog.html">https://viem.sh/docs/contract/decodeEventLog.html</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Is it possible to create upgradeable Minimal Proxy Clone contracts?]]></title><description><![CDATA[How did we get here?
The conversation goes somewhat like this:- PM: Can we create multiple copies of the same contract without redeploying the same one over and over? We can save a lot of gas costs in that way.You, savvy Solidity developer who did hi...]]></description><link>https://blog.fullstackwebthree.com/upgradeable-beacon-proxy</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/upgradeable-beacon-proxy</guid><category><![CDATA[Solidity]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[Smart Contracts]]></category><category><![CDATA[upgradable smart contracts]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Fri, 22 Dec 2023 09:43:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1703238039013/15b996cc-2bd4-4bbb-85cc-ccb5b17da2f7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-how-did-we-get-here">How did we get here?</h2>
<p>The conversation goes somewhat like this:<br />- <strong>PM:</strong> Can we create multiple copies of the same contract without redeploying the same one over and over? We can save a lot of gas costs in that way.<br />You, savvy Solidity developer who did his homework, answer confidently:<br />- <strong>0x9:</strong> Sure, there is <a target="_blank" href="https://eips.ethereum.org/EIPS/eip-1167">EIP1167</a>, the Minimal Proxy Contract (Minimal Clones), that allows us to deploy proxies pointing to an implementation address with minimal cost.<br />- <strong>PM:</strong> That sounds perfect, but if we are using a Proxy, then that means that those Clone contracts will still be upgradeable right?<br /><strong>Wrong,</strong> your soul sinks into an abysm of despair.<br />- <strong>0x9:</strong> Erm...not really, in Minimal Proxy contract the implementation address is hardcoded into the bytecode, so you can't just change it without breaking the standard. Plus, even if you could, you would have to go over every Clone changing the address (and I would rather not do that).<br />- <strong>PM:</strong> I see, then please get me the best option to implement an Upgradeable Factory pattern while keeping the ability to upgrade all the copies at the same time.</p>
<p>That is more or less how it goes. You are left alone in your home office to speak with your rubber duck 🦆 about how you can figure this out.</p>
<h2 id="heading-how-to-create-multiple-proxies-pointing-to-the-same-implementation">How to create multiple proxies pointing to the same implementation?</h2>
<p>This one is the easiest part (but spoiler, <strong>it won't be enough</strong>)You can create multiple copies of a Universal Upgradeable Proxy Standard (UUPS) or a Transparent Proxy. Since we have been told to get the most gas-efficient solution possible we will go with UUPS, since Transparent Proxies are more expensive to deploy and operate.</p>
<p>In UUPS the upgrade logic is handled by the implementation contract. This means that the contract holding the logic must include an <code>upgradeTo(newImplementation address)</code> that will update the implementation address in storage, therefore changing where the Proxy directs its <code>delegatecall()</code> function.</p>
<h3 id="heading-before-we-continue-project-setup"><strong>Before we continue: project setup</strong></h3>
<p>I am assuming that you know how to set up a Solidity project so I will skip all of those steps. I normally use Foundry so if you want to follow along you can either go to the article's <a target="_blank" href="https://github.com/IpastorSan/beacon-proxy-smart-contracts"><strong>repo</strong></a> or set up a new project through my <a target="_blank" href="https://github.com/IpastorSan/foundry-template"><strong>Foundry template</strong></a><strong>.</strong></p>
<h3 id="heading-how-do-we-create-multiple-copies-of-the-contract"><strong>How do we create multiple copies of the contract?</strong></h3>
<ol>
<li><p><strong>Implementation contract</strong></p>
<p> Once we deploy our implementation contract, we deploy an ERC1967 Proxy that points to this implementation. Since our that contract inherits from <a target="_blank" href="https://docs.openzeppelin.com/contracts/5.x/api/proxy#UUPSUpgradeable">UUPSUpgradeable</a>, the <code>upgrade()</code> logic resides there. We are going to implement 2 versions of an NFT contract, V1 only has a <code>mint()</code>function while V2 also includes a <code>burn()</code>function. Most of the initial parameters for a standard NFT contract are defined within the <code>initialize()</code> function.</p>
</li>
</ol>
<pre><code class="lang-solidity"><span class="hljs-comment">//SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin-contracts-upgradeable/contracts/token/ERC721/ERC721Upgradeable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../interfaces/Inft.sol"</span>;


<span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">MintPriceNotPaid</span>(<span class="hljs-params"></span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">SupplyExceeded</span>(<span class="hljs-params"></span>)</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">NFTV1UUPS</span> <span class="hljs-keyword">is</span> <span class="hljs-title">INFT</span>, <span class="hljs-title">ERC721Upgradeable</span>, <span class="hljs-title">OwnableUpgradeable</span>, <span class="hljs-title">UUPSUpgradeable</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> maxSupply;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> price;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> tokenId;
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> baseTokenURI;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
        _disableInitializers();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initialize</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> symbol,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI,
        <span class="hljs-keyword">uint256</span> initialMaxSupply, 
        <span class="hljs-keyword">uint256</span> initialPrice,
        <span class="hljs-keyword">address</span> owner
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">initializer</span> </span>{

        __ERC721_init_unchained(name, symbol);
        __Ownable_init_unchained();

        baseTokenURI <span class="hljs-operator">=</span> baseURI;
        maxSupply <span class="hljs-operator">=</span> initialMaxSupply;
        price <span class="hljs-operator">=</span> initialPrice;
        transferOwnership(owner);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">uint256</span> currentId <span class="hljs-operator">=</span> tokenId;

        <span class="hljs-keyword">if</span>(currentId <span class="hljs-operator">+</span> amount <span class="hljs-operator">&gt;</span> maxSupply) <span class="hljs-keyword">revert</span> SupplyExceeded();
        <span class="hljs-keyword">if</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> price <span class="hljs-operator">*</span> amount) <span class="hljs-keyword">revert</span> MintPriceNotPaid();

        tokenId <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i<span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&lt;</span> amount; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
            _mint(to, currentId <span class="hljs-operator">+</span> i);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
     <span class="hljs-keyword">uint256</span> balance <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
     <span class="hljs-built_in">require</span>(balance <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No ether left to withdraw"</span>);

     (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(owner()).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: balance}(<span class="hljs-string">""</span>);

     <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"Transfer failed."</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_authorizeUpgrade</span>(<span class="hljs-params"><span class="hljs-keyword">address</span></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title">onlyOwner</span> </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
       <span class="hljs-keyword">return</span> baseTokenURI;
    }
}
</code></pre>
<ol>
<li><p><strong>Factory contract</strong></p>
<p> Now to generate multiple "copies" of the implementation contract, we can create a Factory contract that will generate a new ERC1967 Proxy each time we call our <code>createNft()</code> function. We are using the CREATE2 opcode to generate the contract addresses deterministically.</p>
<p> Each time we create a new contract we increment an id counter. This counter will allow us to:</p>
<ol>
<li><p>Know how many copies of our contract exist.</p>
</li>
<li><p>Fetch the address of any of those copies according to their id.</p>
</li>
<li><p>Use it as the <code>salt</code>value that lets us create the contract with a deterministic address.</p>
</li>
</ol>
</li>
</ol>
<p>    If you add the <code>{salt:bytes32(ID)}</code>param when you create a new contract from another contract, the compiler will interpret that you intend to use CREATE2 instead of CREATE.</p>
<p>    Also, note that the best practice is to include the ABI-encoded call to the <code>initialize</code> function in the <code>_data</code> argument within the constructor call to the ERC1967 Proxy as per the <a target="_blank" href="https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable">Openzeppelin docs</a> to avoid leaving the Proxy uninitialized. However, we are doing it in a separate call within the same function. Otherwise, we would need to know the initial deployment arguments when trying to reconstruct the newly deployed contract address.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">//SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../interfaces/Inft.sol"</span>;

<span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">NonExistingProxy</span>(<span class="hljs-params"></span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">ZeroAddress</span>(<span class="hljs-params"></span>)</span>;
<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">UUPSProxyFactory</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">private</span> _proxyId;
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">private</span> _implementation;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span> (<span class="hljs-params"><span class="hljs-keyword">address</span> implementation</span>) </span>{
        _implementation <span class="hljs-operator">=</span> implementation;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createNewProxy</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, 
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> symbol, 
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI, 
        <span class="hljs-keyword">uint256</span> maxSupply, 
        <span class="hljs-keyword">uint256</span> price
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
        ERC1967Proxy proxy <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> ERC1967Proxy{<span class="hljs-built_in">salt</span>: <span class="hljs-keyword">bytes32</span>(_proxyId)}(_implementation, <span class="hljs-string">""</span>);
        INFT inft <span class="hljs-operator">=</span> INFT(<span class="hljs-keyword">address</span>(proxy));
        inft.initialize(name, symbol, baseURI, maxSupply, price, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        _proxyId<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(proxy);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setImplementation</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> implementation</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">if</span>(implementation <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) <span class="hljs-keyword">revert</span> ZeroAddress();
        _implementation <span class="hljs-operator">=</span> implementation;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getProxyAddress</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> proxyId</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
        <span class="hljs-keyword">if</span> (proxyId <span class="hljs-operator">&gt;</span> _proxyId) <span class="hljs-keyword">revert</span> NonExistingProxy();

        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> bytecode <span class="hljs-operator">=</span> <span class="hljs-keyword">type</span>(ERC1967Proxy).<span class="hljs-built_in">creationCode</span>;
        bytecode <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(bytecode, <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(_implementation,<span class="hljs-string">""</span>));
        <span class="hljs-keyword">bytes32</span> hash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(
            <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xff</span>),
                <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>),
                proxyId,
                <span class="hljs-built_in">keccak256</span>(bytecode)
            )
        );

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(<span class="hljs-keyword">uint160</span>(<span class="hljs-keyword">uint256</span>(hash)));
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCurrentProxyId</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> _proxyId;
    }
}
</code></pre>
<h3 id="heading-why-this-is-not-enough-simultaneous-upgrades"><strong>Why this is not enough? Simultaneous Upgrades</strong></h3>
<p>I am sure by now you were pulling your hair out. You got it wrong! That's not how it works! Well yeah, my internet frens but this is a walkthrough on the thought process of how to solve a given problem, as well as a code tutorial.</p>
<p>Why our current solution won't work? Because, one of the key requirements that we were given was to be able to <strong>simultaneously upgrade</strong> all the proxy copies of our contract. Neither UUPS nor Transparent proxies can do that because the address of the implementation contract lives on the storage of each instance of the proxy (even though the logic on how to upgrade it lives in different places depending on the standard).</p>
<p>So how can we update the implementation address without going through each one of the proxies that our factory contract has generated? We use a <strong>Beacon</strong> <strong>Proxy</strong>.</p>
<h2 id="heading-beacon-proxy">Beacon proxy</h2>
<p>In the Beacon proxy pattern, the implementation address is stored in a special contract, called precisely a Beacon, and every proxy checks against it what is the current implementation address before performing the <code>delegateCall</code> .</p>
<p>This makes it more costly to deploy than UUPS, as we will be deploying 3 contracts initially (implementation, Beacon, and proxy factory) but it checks all the boxes of our requirements as we only need to change the implementation address stored in the Beacon and then all of our proxy copies will begin forwarding their calls to the new logic contract. Note that this extra call also makes it a bit more expensive to use than UUPS, as we need more gas to go through the whole process.</p>
<p>This is what it looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1703237673049/8fe619ea-a569-4625-a697-c286f42b5a41.png" alt class="image--center mx-auto" /></p>
<p>On the implementation side, NFTV1 doesn't inherit from <code>UUPSUpgradeable</code> anymore.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">//SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin-contracts-upgradeable/contracts/token/ERC721/ERC721Upgradeable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./interfaces/Inft.sol"</span>;


<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">NFTV1</span> <span class="hljs-keyword">is</span> <span class="hljs-title">INFT</span>, <span class="hljs-title">ERC721Upgradeable</span>, <span class="hljs-title">OwnableUpgradeable</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> maxSupply;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> price;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> tokenId;
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> baseTokenURI;

    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">MintPriceNotPaid</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">error</span> <span class="hljs-title">SupplyExceeded</span>(<span class="hljs-params"></span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
        _disableInitializers();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initialize</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> symbol,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI,
        <span class="hljs-keyword">uint256</span> initialMaxSupply, 
        <span class="hljs-keyword">uint256</span> initialPrice,
        <span class="hljs-keyword">address</span> owner
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">initializer</span> </span>{

        __ERC721_init_unchained(name, symbol);
        __Ownable_init_unchained();

        baseTokenURI <span class="hljs-operator">=</span> baseURI;
        maxSupply <span class="hljs-operator">=</span> initialMaxSupply;
        price <span class="hljs-operator">=</span> initialPrice;
        transferOwnership(owner);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">uint256</span> currentId <span class="hljs-operator">=</span> tokenId;

        <span class="hljs-keyword">if</span>(currentId <span class="hljs-operator">+</span> amount <span class="hljs-operator">&gt;</span> maxSupply) <span class="hljs-keyword">revert</span> SupplyExceeded();
        <span class="hljs-keyword">if</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> price <span class="hljs-operator">*</span> amount) <span class="hljs-keyword">revert</span> MintPriceNotPaid();

        tokenId <span class="hljs-operator">+</span><span class="hljs-operator">=</span> amount;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint256</span> i<span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&lt;</span> amount; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
            _mint(to, currentId <span class="hljs-operator">+</span> i);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
       <span class="hljs-keyword">return</span> baseTokenURI;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
     <span class="hljs-keyword">uint256</span> balance <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
     <span class="hljs-built_in">require</span>(balance <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No ether left to withdraw"</span>);

     (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(owner()).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: balance}(<span class="hljs-string">""</span>);

     <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"Transfer failed."</span>);
    }
}
</code></pre>
<p>On the Factory contract, we are not deploying ERC1967 Proxies anymore, but Beacon Proxies.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">//SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./interfaces/Inft.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BeaconProxyFactory</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">private</span> _proxyId;
    <span class="hljs-keyword">address</span> <span class="hljs-keyword">private</span> _beacon;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span> (<span class="hljs-params"><span class="hljs-keyword">address</span> beacon</span>) </span>{
        _beacon <span class="hljs-operator">=</span> beacon;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createNewProxy</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, 
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> symbol, 
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI, 
        <span class="hljs-keyword">uint256</span> maxSupply, 
        <span class="hljs-keyword">uint256</span> price
    </span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
        BeaconProxy proxy <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> BeaconProxy{<span class="hljs-built_in">salt</span>: <span class="hljs-keyword">bytes32</span>(_proxyId)}(_beacon, <span class="hljs-string">""</span>);
        INFT inft <span class="hljs-operator">=</span> INFT(<span class="hljs-keyword">address</span>(proxy));
        inft.initialize(name, symbol, baseURI, maxSupply, price, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>);
        _proxyId<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(proxy);
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getProxyAddress</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> proxyId</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> bytecode <span class="hljs-operator">=</span> <span class="hljs-keyword">type</span>(BeaconProxy).<span class="hljs-built_in">creationCode</span>;
        bytecode <span class="hljs-operator">=</span> <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(bytecode, <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encode</span>(_beacon,<span class="hljs-string">""</span>));
        <span class="hljs-keyword">bytes32</span> hash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(
            <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                <span class="hljs-keyword">bytes1</span>(<span class="hljs-number">0xff</span>),
                <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>),
                proxyId,
                <span class="hljs-built_in">keccak256</span>(bytecode)
            )
        );

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">address</span>(<span class="hljs-keyword">uint160</span>(<span class="hljs-keyword">uint256</span>(hash)));
    }
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCurrentProxyId</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> _proxyId;
    }
}
</code></pre>
<p>Don't get confused by the contract's naming. If you are using Openzeppelin's contracts, Beacon Proxy is the proxy that looks up the implementation address in the Beacon contract, and that Beacon contract is called <code>UpgradeableBeacon</code> . You can see it clearly in the deployment script.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: UNLICENSED</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.20;</span>

<span class="hljs-keyword">import</span> {<span class="hljs-title">Script</span>, <span class="hljs-title">console2</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"forge-std/Script.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/NFTV1Implementation.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/BeaconProxyFactory.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"</span>;


<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Deploy</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Script</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        vm.startBroadcast();

        NFTV1 implementationV1 <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> NFTV1();
        console2.log(<span class="hljs-string">"Deployed NFTV1 Implementation at address: %s"</span>, <span class="hljs-keyword">address</span>(implementationV1));

        UpgradeableBeacon beacon <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> UpgradeableBeacon(<span class="hljs-keyword">address</span>(implementationV1));

        BeaconProxyFactory beaconProxyFactory <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> BeaconProxyFactory(<span class="hljs-keyword">address</span>(beacon));

        vm.stopBroadcast();
    }
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As we mentioned before, if we deploy a new version of our implementation, like our NFTV2 which includes a <code>burn()</code>functionality, we can just change the current address stored in <code>UpgradeableBeacon</code> and all the deployed proxies will acquire the new functionality. Be mindful of security on your upgrades as you can also potentially include a bug in all the copies of your contract at once.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://docs.openzeppelin.com/contracts/5.x/api/proxy#BeaconProxy">https://docs.openzeppelin.com/contracts/5.x/api/proxy#BeaconProxy</a></p>
</li>
<li><p><a target="_blank" href="https://blog.openzeppelin.com/deep-dive-into-the-minimal-proxy-contract">https://blog.openzeppelin.com/deep-dive-into-the-minimal-proxy-contract</a></p>
</li>
<li><p><a target="_blank" href="https://blog.logrocket.com/using-uups-proxy-pattern-upgrade-smart-contracts/#what-is-a-uups-proxy-pattern">https://blog.logrocket.com/using-uups-proxy-pattern-upgrade-smart-contracts/#what-is-a-uups-proxy-pattern</a></p>
</li>
<li><p><a target="_blank" href="https://dev.to/nvn/proxy-patterns-for-upgradeability-of-solidity-contracts-transparent-vs-uups-proxies-3ig2">https://dev.to/nvn/proxy-patterns-for-upgradeability-of-solidity-contracts-transparent-vs-uups-proxies-3ig2</a></p>
</li>
<li><p><a target="_blank" href="https://defihacklabs.substack.com/p/secure-proxy-models-understanding">https://defihacklabs.substack.com/p/secure-proxy-models-understanding</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Create your first Subgraph for your smart contract with The Graph]]></title><description><![CDATA[What is the Graph and why do you care?
The Graph is a protocol for indexing and querying blockchain data. It enables you to use your smart contract's events to create and update entities that represent the internal state of such a smart contract.
Why...]]></description><link>https://blog.fullstackwebthree.com/create-your-first-subgraph-for-your-smart-contract-with-the-graph</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/create-your-first-subgraph-for-your-smart-contract-with-the-graph</guid><category><![CDATA[Web3]]></category><category><![CDATA[NFT]]></category><category><![CDATA[subgraph]]></category><category><![CDATA[#thegraph]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Thu, 05 Oct 2023 16:52:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1693935283178/c6bd3bdd-c1bb-4557-9602-2ade9eca623b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-the-graph-and-why-do-you-care">What is the Graph and why do you care?</h2>
<p>The Graph is a protocol for indexing and querying blockchain data. It enables you to use your smart contract's events to create and update entities that represent the internal state of such a smart contract.</p>
<p>Why is this useful? Imagine a smart contract that requires an intensive lookup of information. An NFT marketplace is a good example. You have potentially thousands of listings going on at the same time, assuming that you need a listing ID to buy an item from another user. How do you identify a specific listing that you are interested in? How do you find it? From a front-end point of view, how do you get all listings, and filter them by seller, or by any other criteria?</p>
<p>If you are familiar with Solidity, you know you can do some of these things, but most of them are not very practical or are directly impossible when you reach a certain scale.</p>
<p>Let's see: if you use mappings to index a Listing struct, you need to know beforehand the key that identifies a given listing so you can track it in your frontend. Even if you do some composite key (mapping-ception) and use, say, the user address and the token ID, how do you get all the listings for a given user? You can't traverse a mapping and trying all the existing possibilities is not practical.</p>
<p><strong><em>"I will use an array then",</em></strong> fine but then you will eventually run out of gas just reading your arrays if your marketplace is even marginally successful.</p>
<p>With The Graph, you can create a Listing entity that gets updated anytime your users interact with any given listing. You can create, update, and delete any particular Listing instance, just telling the Graph the effect that your smart contract's events should have over that entity. Using GraphQL you can get any of those Listing entities, filter them to display the appropriate data, or even fetch info that you can use as input for a new smart contract function call.</p>
<p>"I could do that with a regular database, just storing anything that goes through my front end" I hear you saying. You are right, but there are some <strong>advantages to using this approach</strong>:</p>
<ul>
<li><p><strong>You don't depend on your front end</strong>. You already know that not all transactions need to/will go through your front end as anyone can interact directly with your smart contracts.</p>
</li>
<li><p><strong>Less surface area for mistakes.</strong> All the Subgraph information comes from the events emitted in the smart contract. We could say that the data comes from a primary source.</p>
</li>
<li><p><strong>Decentralization.</strong> This is still a work in progress. If you deploy your Subgraph to the <a target="_blank" href="https://thegraph.com/docs/en/network/overview/">Graph network</a> (or even to the <a target="_blank" href="https://thegraph.com/docs/en/deploying/hosted-service/">Hosted Service</a>) your users don't need to trust that you won't be manipulating the data in your favor. They still need to trust that you won't stop paying the Network fees so the data remains available, but that's a different problem. If you don't want to go over the trouble of deploying to the Graph network, there are other services that offer more centralized solutions.</p>
</li>
</ul>
<h2 id="heading-ok-im-convinced-how-do-i-start">Ok, I'm convinced. How do I start?</h2>
<p>We will deploy our first Subgraph to the <a target="_blank" href="https://thegraph.com/docs/en/cookbook/quick-start/#1-create-a-subgraph-on-subgraph-studio">Subgraph Studio</a> for testing.</p>
<p>You will need a wallet to connect to Subgraph Studio and an already deployed contract to bootstrap your subgraph. It's probably best to design your subgraph while on testnet, as you will likely want to change your smart contracts events while creating the subgraph.</p>
<h3 id="heading-set-up-your-graph-studiohosted-service-account">Set up your Graph Studio/Hosted Service Account</h3>
<p>You will need to connect your wallet (<a target="_blank" href="https://thegraph.com/studio/">Graph Studio</a>) or your Github Account (<a target="_blank" href="https://thegraph.com/hosted-service">Hosted Service</a>) and then create a subgraph through the UI. This is important as you will need that name to be the same in your local setup so you can later deploy it.</p>
<h3 id="heading-graph-cli">Graph CLI</h3>
<p>Then, you will need to install the Graph CLI.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># NPM</span>
$ npm install -g @graphprotocol/graph-cli

<span class="hljs-comment"># Yarn</span>
$ yarn global add @graphprotocol/graph-cli
</code></pre>
<h3 id="heading-subgraph-bootstrapping">Subgraph bootstrapping</h3>
<p>Using <code>graph init</code> you will start the process of bootstrapping your subgraph.</p>
<ul>
<li><p>Choose your <strong>protocol</strong>. No matter the specific chain your contracts are deployed into, if you are on an EVM chain, choose Ethereum.</p>
</li>
<li><p><strong>Produc</strong>t to initialize. <code>subgraph-studio</code> in our case.</p>
</li>
<li><p><strong>Subgraph slug</strong>. You should have set this when creating a new subgraph in the Studio 2 steps ago.</p>
</li>
<li><p><strong>Directory</strong> to create subgraph in (self-explanatory).</p>
</li>
<li><p>Ethereum <strong>network</strong>. Here is where you choose the network where your contract is deployed.</p>
</li>
<li><p><strong>Contract Address</strong>. If you have more than one contract address, just choose one. You can add the rest later.</p>
</li>
<li><p><strong>Fetch ABI and start block</strong>. If your contract is verified on Etherscan, the CLI should be able to fetch the abi file and start block from there. Otherwise, you will need to provide a directory where the CLI can fetch the abi from.</p>
</li>
<li><p><strong>Contract Name</strong>. Same as your contract name. <strong>Please don't skip this</strong>, or else all your relevant files and handlers will be named as Contract and that's annoying.</p>
</li>
<li><p><strong>Index contract events</strong> as entities? This is an example of a <strong>Good/Bad Default</strong>. We will talk more about this later. Choose YES for now.</p>
</li>
<li><p>After some automatic code generation, you will be asked if you want to <strong>add another contract</strong>. Do so if you need to but if it fails, don't fret, you can use <code>graph add &lt;CONTRACT_ADDRESS&gt;</code> later to add new contracts.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693982214487/66192dc3-732e-49f0-9b66-1a8752b32f33.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<h3 id="heading-code-away-design-your-subgraph">Code away! Design your subgraph</h3>
<p>For our example marketplace subgraph, we will assume that we will only deal with 2 entities: ItemSold and Listing. Item Sold will help us keep track of all successful sales, while we will use Listing to keep track of all marketplace listings during all of their lifecycle.</p>
<p>Remember when we bootstrapped the project, we said yes to indexing all contracts as entities? This is useful to get a feeling of how these entities look according to your events, data types, etc. This is the moment to actually design a data structure (the entities) that makes sense for your application. In our case, if we take Listing, it makes much more sense to just create one entity and modify it according to any changes that a specific instance might have, rather than just keep a record of all the events that happen, disconnected from each other.</p>
<p><strong>schema.graphql</strong></p>
<p>This is the file where you declare your entities, the ones that you want to track and query in your application. Any change to these entities will come from emitted events in your smart contracts.</p>
<p>Eliminate all the events-turned-entities and just leave ItemsSold plus a new Listing entity that gathers all the useful information of a given entity (all the info contained in the <code>Listing</code> struct in <code>ISimpleMarketplace.sol</code>). I also leave the blockNumber, timestamp, and tx hash.</p>
<p>Every entity needs to have a unique ID. By default, the event handlers create one based on the tx hash. If you only want to record all events then you can leave it like that, but if you want to modify an entity state based on the events that affect it (like a Listing having a new price) then you need to have an ID that you can reference in all the events that affect a given entity, and use it as an entry point to modify it.</p>
<p>This is when subgraph design and smart contract development should (ideally) work together. For example, each listing has a unique ID, a counter in the smart contract, and this ID is emitted in all the events that affect a listing.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">//@dev from IBasicMarketplace.sol</span>
<span class="hljs-comment">// Emitted when a valid listing is created</span>
<span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">ListingCreated</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> listingId, <span class="hljs-keyword">address</span> seller, <span class="hljs-keyword">address</span> nft, <span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">uint256</span> price, <span class="hljs-keyword">bool</span> exist</span>)</span>;
<span class="hljs-comment">// Emitted when a valid listing is updated by the Seller</span>
<span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">ListingUpdated</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> listingId, <span class="hljs-keyword">address</span> seller,  <span class="hljs-keyword">address</span> nft, <span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">uint256</span> price</span>)</span>;
<span class="hljs-comment">// Emitted when a listing is cancelled </span>
<span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">ListingCancelled</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> listingId</span>)</span>;
</code></pre>
<p>I will be using this listingId, which is emitted in every event as an ID for the Listing entity. This is how I know that I am modifying the correct entity instance for each change in every listing estate. This is not seen clearly in the GraphQL schema but will be clear in the mapping file when we deal with the event handlers.</p>
<pre><code class="lang-graphql"><span class="hljs-keyword">type</span> ItemSold <span class="hljs-meta">@entity</span>(<span class="hljs-symbol">immutable:</span> <span class="hljs-variable">true</span>) {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">listingId:</span> BigInt! <span class="hljs-comment"># uint256</span>
  <span class="hljs-symbol">nft:</span> Bytes! <span class="hljs-comment"># address</span>
  <span class="hljs-symbol">seller:</span> Bytes! <span class="hljs-comment"># address</span>
  <span class="hljs-symbol">buyer:</span> Bytes! <span class="hljs-comment"># address</span>
  <span class="hljs-symbol">price:</span> BigInt! <span class="hljs-comment"># uint256</span>
  <span class="hljs-symbol">blockNumber:</span> BigInt!
  <span class="hljs-symbol">blockTimestamp:</span> BigInt!
  <span class="hljs-symbol">transactionHash:</span> Bytes!
}

<span class="hljs-keyword">type</span> Listing <span class="hljs-meta">@entity</span> {
  <span class="hljs-symbol">id:</span> ID!
  <span class="hljs-symbol">listingId:</span> BigInt! <span class="hljs-comment"># uint256</span>
  <span class="hljs-symbol">seller:</span> Bytes! <span class="hljs-comment"># address</span>
  <span class="hljs-symbol">nft:</span> Bytes! <span class="hljs-comment"># address</span>
  <span class="hljs-symbol">tokenId:</span> BigInt! <span class="hljs-comment"># uint256</span>
  <span class="hljs-symbol">price:</span> BigInt! <span class="hljs-comment"># uint256</span>
  <span class="hljs-symbol">exist:</span> Boolean! <span class="hljs-comment"># bool</span>
  <span class="hljs-symbol">blockNumber:</span> BigInt!
  <span class="hljs-symbol">blockTimestamp:</span> BigInt!
  <span class="hljs-symbol">transactionHash:</span> Bytes!
}
</code></pre>
<p><strong>your-contracts.ts (Mapping file)</strong></p>
<p>In our case <code>simple-marketplace.ts</code> often referred to in the docs as the mapping file, which I found to be a confusing term at first but it actually makes sense.</p>
<p>This is the file that <em>maps</em> the events emitted by your smart contract to the entities that you declared in your schema. <strong>It is the backend business logic that handles the changes in your database.</strong></p>
<p>The subgraph is constantly listening for events from our contract. Each time an event hits the subgraph, the appropriate event handler gets triggered. Here you can see clearly why it is important to design our smart contracts with subgraph design in mind, as we use the listingId as a handle to get the correct listing every time we receive an event. At any given point, each listing instance reflects the current state of the listing within our smart contract.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {
  ListingCancelled <span class="hljs-keyword">as</span> ListingCancelledEvent,
  ListingCreated <span class="hljs-keyword">as</span> ListingCreatedEvent,
  ListingUpdated <span class="hljs-keyword">as</span> ListingUpdatedEvent,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"../generated/SimpleMarketplace/SimpleMarketplace"</span>
<span class="hljs-keyword">import</span> {
  Listing
} <span class="hljs-keyword">from</span> <span class="hljs-string">"../generated/schema"</span>
<span class="hljs-keyword">import</span> { store } <span class="hljs-keyword">from</span> <span class="hljs-string">'@graphprotocol/graph-ts'</span>

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleListingCancelled</span>(<span class="hljs-params">event: ListingCancelledEvent</span>): <span class="hljs-title">void</span> </span>{
  <span class="hljs-keyword">let</span> entity: Listing | <span class="hljs-literal">null</span>
  entity = Listing.load(event.params.listingId.toString())

  <span class="hljs-keyword">if</span> (entity!== <span class="hljs-literal">null</span>){
    store.remove(<span class="hljs-string">"Listing"</span>, event.params.listingId.toString())
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleListingCreated</span>(<span class="hljs-params">event: ListingCreatedEvent</span>): <span class="hljs-title">void</span> </span>{
  <span class="hljs-keyword">let</span> entity: Listing | <span class="hljs-literal">null</span>
  entity = <span class="hljs-keyword">new</span> Listing(event.params.listingId.toString())

  entity.listingId = event.params.listingId
  entity.seller = event.params.seller
  entity.nft = event.params.nft
  entity.tokenId = event.params.tokenId
  entity.price = event.params.price
  entity.exist = event.params.exist

  entity.blockNumber = event.block.number
  entity.blockTimestamp = event.block.timestamp
  entity.transactionHash = event.transaction.hash

  entity.save()
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleListingUpdated</span>(<span class="hljs-params">event: ListingUpdatedEvent</span>): <span class="hljs-title">void</span> </span>{
  <span class="hljs-keyword">let</span> entity: Listing | <span class="hljs-literal">null</span>
  entity = Listing.load(event.params.listingId.toString())

  <span class="hljs-keyword">if</span>(entity !== <span class="hljs-literal">null</span>){
    entity.listingId = event.params.listingId
    entity.seller = event.params.seller
    entity.nft = event.params.nft
    entity.tokenId = event.params.tokenId
    entity.price = event.params.price

    entity.blockNumber = event.block.number
    entity.blockTimestamp = event.block.timestamp
    entity.transactionHash = event.transaction.hash

    entity.save()
  }
}
</code></pre>
<p><strong>subgraph.yaml</strong></p>
<p>This file controls de deployment of the subgraph. Here we declare what is the contract's deployment address, what is the starting block to begin the indexing, network, tracked events, handlers... everything!</p>
<p>We will keep just 2 entities, ItemSold and Listing. We delete all the other entities. Regarding event handlers, we keep all of those already declared (we modified their original behavior in the mapping file) except for OwnershipTransferred which is inherited from Ownable and has no interest for us.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">specVersion:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.5</span>
<span class="hljs-attr">schema:</span>
  <span class="hljs-attr">file:</span> <span class="hljs-string">./schema.graphql</span>
<span class="hljs-attr">dataSources:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">kind:</span> <span class="hljs-string">ethereum</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">SimpleMarketplace</span>
    <span class="hljs-attr">network:</span> <span class="hljs-string">sepolia</span>
    <span class="hljs-attr">source:</span>
      <span class="hljs-attr">address:</span> <span class="hljs-string">"0xf91C1bfb2dbAcbfBD39a171eF0Cd3E3b47893099"</span>
      <span class="hljs-attr">abi:</span> <span class="hljs-string">SimpleMarketplace</span>
      <span class="hljs-attr">startBlock:</span> <span class="hljs-number">4233781</span>
    <span class="hljs-attr">mapping:</span>
      <span class="hljs-attr">kind:</span> <span class="hljs-string">ethereum/events</span>
      <span class="hljs-attr">apiVersion:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.7</span>
      <span class="hljs-attr">language:</span> <span class="hljs-string">wasm/assemblyscript</span>
      <span class="hljs-attr">entities:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">ItemSold</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">Listing</span>
      <span class="hljs-attr">abis:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">SimpleMarketplace</span>
          <span class="hljs-attr">file:</span> <span class="hljs-string">./abis/SimpleMarketplace.json</span>
      <span class="hljs-attr">eventHandlers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">event:</span> <span class="hljs-string">ItemSold(uint256,address,address,address,uint256)</span>
          <span class="hljs-attr">handler:</span> <span class="hljs-string">handleItemSold</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">event:</span> <span class="hljs-string">ListingCancelled(uint256)</span>
          <span class="hljs-attr">handler:</span> <span class="hljs-string">handleListingCancelled</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">event:</span> <span class="hljs-string">ListingCreated(uint256,address,address,uint256,uint256,bool)</span>
          <span class="hljs-attr">handler:</span> <span class="hljs-string">handleListingCreated</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">event:</span> <span class="hljs-string">ListingUpdated(uint256,address,address,uint256,uint256)</span>
          <span class="hljs-attr">handler:</span> <span class="hljs-string">handleListingUpdated</span>
      <span class="hljs-attr">file:</span> <span class="hljs-string">./src/simple-marketplace.ts</span>
</code></pre>
<h3 id="heading-build-and-deploy-your-subgraph">Build and deploy your subgraph</h3>
<p>Once you finish with the previous step, how do you deploy your subgraph to Studio?</p>
<p>From your root directory, <strong>first, compile the project</strong> running:</p>
<pre><code class="lang-bash">graph codegen &amp;&amp; graph build
</code></pre>
<p>You will surely find some compilation errors and will need to make adjustments to the code. (Assemblyscript is quite strict, plus not all JS features are implemented, like object spreading).</p>
<p>Once these difficulties are overcome, <strong>run graph auth with the deploy key</strong> that you will find in the Subgraph Studio UI, on your newly created subgraph.</p>
<pre><code class="lang-bash">graph auth --studio &lt;DEPLOY_KEY&gt;
</code></pre>
<p>Then finally, <strong>deploy</strong> to your subgraph name.</p>
<pre><code class="lang-bash">graph deploy --studio &lt;SUBGRAPH_NAME&gt;
</code></pre>
<p>You will be asked to provide a version label. <strong>You can change your code or metadata and redeploy</strong> as many times as you want before publishing to the decentralized network.</p>
<p>Thats it! You successfully deployed your first subgraph.</p>
<h3 id="heading-next-steps-publish-to-the-graph-decentralized-network">Next steps: publish to the Graph Decentralized Network</h3>
<p>Publishing to the Decentralized network is as easy as pushing "Publish" on your subgraph Studio UI.</p>
<p>You can publish to Ethereum Mainnet, Goerli, Arbitrum One, or Arbitrum Goerli, but that (supposedly) does not have an effect on your indexing as long as the subgraph points to one of the <a target="_blank" href="https://thegraph.com/docs/en/developing/supported-networks/">supported networks</a>.</p>
<p>It is recommended that you curate your own subgraph upon deployment, so Indexers and other Curators can pick it up and start indexing it as well. It is recommended to use at least 10K GRT to begin with, which at current prices is close to 740 USD. For testing purposes querying to Subgraph Studio is more than enough.</p>
<p>Indexing and Curation are topics in themselves, and it's probably worth writing another article on them. So for now I'm leaving you the docs on <a target="_blank" href="https://thegraph.com/docs/en/network/curating/">Curators</a> and <a target="_blank" href="https://thegraph.com/docs/en/network/indexing/">Indexers</a>.</p>
<h2 id="heading-see-also">See also</h2>
<p><a target="_blank" href="https://github.com/IpastorSan/the-graph-example">Link to the full example repository on Github</a></p>
<p>The Graph docs</p>
<ul>
<li><p><a target="_blank" href="https://thegraph.com/docs/en/about/">https://thegraph.com/docs/en/about/</a></p>
</li>
<li><p><a target="_blank" href="https://thegraph.com/docs/en/developing/creating-a-subgraph/">https://thegraph.com/docs/en/developing/creating-a-subgraph/</a></p>
</li>
<li><p><a target="_blank" href="https://thegraph.com/docs/en/deploying/subgraph-studio/">https://thegraph.com/docs/en/deploying/subgraph-studio/</a></p>
</li>
</ul>
<p>Centralized services to deploy subgraphs (not sponsored)</p>
<ul>
<li><p><a target="_blank" href="https://chainstack.com/subgraphs/">https://chainstack.com/subgraphs/</a></p>
</li>
<li><p><a target="_blank" href="https://chainstack.com/how-to-build-a-subgraph-in-5-minutes/">https://chainstack.com/how-to-build-a-subgraph-in-5-minutes/</a></p>
</li>
<li><p><a target="_blank" href="https://alchemy.com/blog/alchemy-acquires-satsuma">https://alchemy.com/blog/alchemy-acquires-satsuma</a></p>
</li>
</ul>
<p><a target="_blank" href="https://api.studio.thegraph.com/query/52096/marketplace-example/version/latest">Link to query the example subgraph</a></p>
<p><a target="_blank" href="https://sepolia.etherscan.io/address/0xd4DEca77F965C3c42FB278C2Cb0C51dF8551C049">Link to the deployed smart contract on Sepolia</a></p>
]]></content:encoded></item><item><title><![CDATA[How to implement EIP-2981 Royalties in your NFT smart contract]]></title><description><![CDATA[Intro to EIP-2981. The Royalties Standard.
The EIP-2981 aims to standardize the way that marketplaces and other intermediaries pay royalties to the original creator of a given non-fungible token. 
Soon after the NFT explosion in popularity (Thank you...]]></description><link>https://blog.fullstackwebthree.com/how-to-implement-eip-2981-royalties-in-your-nft-smart-contract</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/how-to-implement-eip-2981-royalties-in-your-nft-smart-contract</guid><category><![CDATA[NFT]]></category><category><![CDATA[Smart Contracts]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[eip-2981]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Tue, 12 Jul 2022 14:39:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1693935078827/2fc4a0d9-b046-4221-820a-4001d256c396.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro-to-eip-2981-the-royalties-standard">Intro to EIP-2981. The Royalties Standard.</h2>
<p>The EIP-2981 aims to standardize the way that marketplaces and other intermediaries pay royalties to the original creator of a given non-fungible token. </p>
<p>Soon after the NFT explosion in popularity (Thank you, <a target="_blank" href="https://www.theverge.com/2021/3/11/22325054/beeple-christies-nft-sale-cost-everydays-69-million">Beeple</a>?) it became evident that blockchain technology could enable creators and artists to receive a share of the sale price of their creative production every time it changed hands. This kind of royalty payment required ownership and exchange tracking that was basically impossible in a market as opaque as the art market or as free as the Internet. </p>
<p>The problem was, that each marketplace implemented this royalty payment differently and they were not compatible across them. This made it impossible again to track the royalty amount to be paid and the receiver of those funds. The implementation of this standard solves this problem and allows for the "ongoing funding of artists and other NFT creators"<a target="_blank" href="https://eips.ethereum.org/EIPS/eip-2981">(EIP-2981)</a>.</p>
<h3 id="heading-key-characteristics">Key characteristics</h3>
<p>Royalties are just a piece of information, that live the smart contract and say "Hey, this is the creator of this collection or token, and he would like to be paid x% of the price paid by his artwork)</p>
<ul>
<li>It is non-enforceable by design. It is recognized that not all NFT transfers are part of a sale, so it doesn't make sense to create a standard that would pay creators for every time an NFT changes wallet. Marketplaces as an industry are expected to implement it.</li>
<li>It is agnostic to the unit of exchange. Royalties are calculated as a percentage of the sale price, independently of the token used for payment. Therefore royalties can be paid in ETH or any ERC-20.</li>
</ul>
<p>I was about to call this section the "NFT Royalties Standard" but then I realized that it would be inaccurate. Although originally designed with non-fungible tokens in mind, the standard is intentionally designed to be as minimal as possible. It is compatible with ERC1155 and ERC721 but it isn't exclusive to them and it could be used to set royalties payments for any asset class. </p>
<h2 id="heading-implementing-eip-2981">Implementing EIP-2981</h2>
<p>Before diving into the full implementation, we need to be aware of some limitations of the standard and different ways of assigning the royalties.</p>
<p>Currently, the standard only allows for ONE address as the payee of royalties. If any given NFT, collection, etc... has a number of creators or royalties that want to be shared among different team members, then the best course of action is to set a split payment contract as the recipient of the royalties and then periodically send each participant's share of the balance to their wallets. For quick implementation, check out OpenZeppelin´s <a target="_blank" href="https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol">Payment Splitter contract</a>.</p>
<p>In this article, we will set a single royalties recipient for all the tokens in an NFT contract (contract-wide royalties). However, the standard allows for more granularity so you could set a different royalty recipient and amount for each Token ID in a given NFT collection. I will show you how in the bonus section.</p>
<h3 id="heading-which-marketplaces-currently-support-eip-2981">Which marketplaces currently support EIP-2981?</h3>
<p>So far, I have not easily found a list of marketplaces that implement this standard. So, I went to the docs of the major ones and checked for myself so you don't have to. I am only including the ones where I could find explicitly in their docs that they support EIP-2981. </p>
<ul>
<li>Mintable. One of the original actors involved in EIP-2981. See <a target="_blank" href="https://docs.mintable.app/ethereum-version/basics/how-to-start-creating-items-on-the-blockchain/types-of-items-gasless-printable-normal#fees-comparison-table">here</a></li>
<li>Rarible. They use 3 different royalties system, including EIP-2981. See <a target="_blank" href="https://docs.rarible.org/overview/tokens-fees-royalties/?h=royalty#royalties">here</a> </li>
<li>LooksRare. Also uses a double system, with EIP-2981 taking precedence. See <a target="_blank" href="https://docs.looksrare.org/guides/faqs/what-are-royalties">here</a></li>
</ul>
<p>I am sure I am missing some others. If so, please let me know!</p>
<p><em>Opensea does NOT support EIP-2981. You can set up contract wide royalties through their UI or a parameter in <code>contractURI</code> see <a target="_blank" href="https://docs.opensea.io/v2.0/docs/10-setting-fees-on-secondary-sales">here</a>.</em></p>
<h3 id="heading-lets-code">Lets code!</h3>
<p>The EIP-2981 implements a single function: </p>
<pre><code class="lang-solidity">royaltyInfo(<span class="hljs-keyword">uint256</span> _tokenID, <span class="hljs-keyword">uint256</span> _salePrice) <span class="hljs-keyword">external</span> <span class="hljs-keyword">view</span> <span class="hljs-keyword">returns</span>(<span class="hljs-keyword">address</span> receiver, <span class="hljs-keyword">uint256</span> royaltyAmount)
</code></pre>
<p>Before implementing the body of the function, we will need some auxiliary structures to store this information and will add a utility function <code>setRoyalties</code> so we are able to modify the recipient of our royalties proceeds and the amount to be paid. </p>
<pre><code class="lang-solidity"><span class="hljs-keyword">struct</span> <span class="hljs-title">RoyaltyInfo</span> {
        <span class="hljs-keyword">address</span> recipient;
        <span class="hljs-keyword">uint256</span> amount;
    }

RoyaltyInfo <span class="hljs-keyword">private</span> _royalties;
</code></pre>
<p>Using the information in the Royalties struct, now we can implement the body of <code>royaltyInfo()</code>. Note that we are calculating the percentage to be paid as a fraction of 10,000 in order to increase the accuracy of the division.</p>
<pre><code class="lang-solidity">    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">royaltyInfo</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span> receiver, <span class="hljs-keyword">uint256</span> royaltyAmount</span>)
    </span>{
        RoyaltyInfo <span class="hljs-keyword">memory</span> royalties <span class="hljs-operator">=</span> _royalties;
        receiver <span class="hljs-operator">=</span> royalties.recipient;
        royaltyAmount <span class="hljs-operator">=</span> (value <span class="hljs-operator">*</span> royalties.amount) <span class="hljs-operator">/</span> <span class="hljs-number">10000</span>;
    }
</code></pre>
<p>How do you set those royalties? You could set the info in <code>_royalties</code> inside the constructor, but it's a common pattern to just create a function that updates that information.</p>
<pre><code class="lang-solidity">    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRoyalties</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> recipient, <span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-built_in">require</span>(value <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> <span class="hljs-number">10000</span>, <span class="hljs-string">'ERC2981Royalties: Too high'</span>);

        _royalties <span class="hljs-operator">=</span> RoyaltyInfo(recipient, value);
    }
</code></pre>
<p>Also, if you want to set up the royalties upon contract creation, then you can still call this function inside the <code>constructor()</code> function.</p>
<p>Lastly, we need to inform other contracts (marketplaces) about the fact that ours is implementing EIP-2981:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">//Interface for royalties</span>
    <span class="hljs-keyword">bytes4</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> _INTERFACE_ID_ERC2981 <span class="hljs-operator">=</span> <span class="hljs-number">0x2a55205a</span>;

       <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">supportsInterface</span>(<span class="hljs-params"><span class="hljs-keyword">bytes4</span> interfaceId</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721</span>) <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)</span>{

        <span class="hljs-keyword">return</span> interfaceId <span class="hljs-operator">=</span><span class="hljs-operator">=</span> _INTERFACE_ID_ERC2981 <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-built_in">super</span>.supportsInterface(interfaceId);
    }
</code></pre>
<p>That was a bit messy and not so well structured. See the whole NFT contract below with proper formatting.</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: UNLICENSED</span>

<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.0;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/Counters.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/ERC721.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/IERC20.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">CoolPinapple</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC721</span>, <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">Counters</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">Counters</span>.<span class="hljs-title">Counter</span>;

    <span class="hljs-keyword">struct</span> <span class="hljs-title">RoyaltyInfo</span> {
        <span class="hljs-keyword">address</span> recipient;
        <span class="hljs-keyword">uint256</span> amount;
    }

    RoyaltyInfo <span class="hljs-keyword">private</span> _royalties;

    <span class="hljs-comment">//Interface for royalties</span>
    <span class="hljs-keyword">bytes4</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">constant</span> _INTERFACE_ID_ERC2981 <span class="hljs-operator">=</span> <span class="hljs-number">0x2a55205a</span>;

    Counters.Counter <span class="hljs-keyword">private</span> _tokenIds;

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> MAX_SUPPLY <span class="hljs-operator">=</span> <span class="hljs-number">10001</span>;

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">constant</span> PRICE <span class="hljs-operator">=</span> <span class="hljs-number">1</span> <span class="hljs-literal">ether</span>;

    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> baseTokenURI;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> baseURI</span>) <span class="hljs-title">ERC721</span>(<span class="hljs-params"><span class="hljs-string">"NFTContract"</span>, <span class="hljs-string">"NFT"</span></span>) </span>{
        baseTokenURI <span class="hljs-operator">=</span> baseURI;
        setRoyalties(owner(), <span class="hljs-number">500</span>); <span class="hljs-comment">//5%</span>

    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
       <span class="hljs-keyword">return</span> baseTokenURI;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mintNFTs</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _number</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-keyword">uint256</span> totalMinted <span class="hljs-operator">=</span> _tokenIds.current();

        <span class="hljs-built_in">require</span>(totalMinted <span class="hljs-operator">+</span> _number <span class="hljs-operator">&lt;</span> MAX_SUPPLY, <span class="hljs-string">"Not enough NFTs!"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span> PRICE <span class="hljs-operator">*</span> _number , <span class="hljs-string">"Not enough/too much ether sent"</span>);

        mintsPerAddress[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">+</span><span class="hljs-operator">=</span> _number;

        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&lt;</span> _number; <span class="hljs-operator">+</span><span class="hljs-operator">+</span>i) {
            _mintSingleNFT();
        }

    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_mintSingleNFT</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
      <span class="hljs-keyword">uint</span> newTokenID <span class="hljs-operator">=</span> _tokenIds.current();
      _safeMint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, newTokenID);
      _tokenIds.increment();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCurrentId</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> _tokenIds.current();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">supportsInterface</span>(<span class="hljs-params"><span class="hljs-keyword">bytes4</span> interfaceId</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721</span>) <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>)</span>{

        <span class="hljs-keyword">return</span> interfaceId <span class="hljs-operator">=</span><span class="hljs-operator">=</span> _INTERFACE_ID_ERC2981 <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-built_in">super</span>.supportsInterface(interfaceId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRoyalties</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> recipient, <span class="hljs-keyword">uint256</span> percentagePoints</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-built_in">require</span>(value <span class="hljs-operator">&lt;</span> <span class="hljs-number">10001</span>, <span class="hljs-string">'ERC2981Royalties: Too high'</span>);

        _royalties <span class="hljs-operator">=</span> RoyaltyInfo(recipient, percentagePoints);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">royaltyInfo</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span> receiver, <span class="hljs-keyword">uint256</span> royaltyAmount</span>)
    </span>{
        RoyaltyInfo <span class="hljs-keyword">memory</span> royalties <span class="hljs-operator">=</span> _royalties;
        receiver <span class="hljs-operator">=</span> royalties.recipient;
        royaltyAmount <span class="hljs-operator">=</span> (value <span class="hljs-operator">*</span> royalties.amount) <span class="hljs-operator">/</span> <span class="hljs-number">10000</span>;
    }

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">onlyOwner</span> </span>{
     <span class="hljs-keyword">uint256</span> balance <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>).<span class="hljs-built_in">balance</span>;
     <span class="hljs-built_in">require</span>(balance <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>, <span class="hljs-string">"No ether left to withdraw"</span>);

     (<span class="hljs-keyword">bool</span> success, ) <span class="hljs-operator">=</span> <span class="hljs-keyword">payable</span>(owner()).<span class="hljs-built_in">call</span>{<span class="hljs-built_in">value</span>: balance}(<span class="hljs-string">""</span>);

     <span class="hljs-built_in">require</span>(success, <span class="hljs-string">"Transfer failed."</span>);
    }

<span class="hljs-function"><span class="hljs-keyword">receive</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{ 
            <span class="hljs-keyword">revert</span>();
    }

}
</code></pre>
<h2 id="heading-bonus-implementing-token-level-royalties">Bonus: Implementing token-level royalties.</h2>
<p>To implement token-level royalties (minter becomes the receiver of the royalties, for example) you just need to add a mapping, modify <code>royaltyInfo()</code> as well as <code>setRoyalties()</code> and then call <code>setRoyalties</code>in your <code>mint()</code> function.</p>
<pre><code class="lang-solidity"><span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint256</span><span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span>RoyaltyInfo) royaltyPerTokenId; <span class="hljs-comment">//Change here</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">royaltyInfo</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId, <span class="hljs-keyword">uint256</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span> receiver, <span class="hljs-keyword">uint256</span> royaltyAmount</span>)
    </span>{
        RoyaltyInfo <span class="hljs-keyword">memory</span> royalties <span class="hljs-operator">=</span> royaltyPerTokenId[tokenId]; <span class="hljs-comment">//Change here</span>
        receiver <span class="hljs-operator">=</span> royalties.recipient;
        royaltyAmount <span class="hljs-operator">=</span> (value <span class="hljs-operator">*</span> royalties.amount) <span class="hljs-operator">/</span> <span class="hljs-number">10000</span>;
    }

 <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setRoyalties</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> recipient, <span class="hljs-keyword">uint256</span> value, <span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-built_in">require</span>(value <span class="hljs-operator">&lt;</span><span class="hljs-operator">=</span> <span class="hljs-number">10000</span>, <span class="hljs-string">'ERC2981Royalties: Too high'</span>);

        royaltyPerTokenId[tokenId] <span class="hljs-operator">=</span> RoyaltyInfo(recipient, value); <span class="hljs-comment">//Change here</span>
    }

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_mintSingleNFT</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
      <span class="hljs-keyword">uint</span> newTokenID <span class="hljs-operator">=</span> _tokenIds.current();
      _safeMint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, newTokenID);
      setRoyalties(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-number">500</span>, newTokenID); <span class="hljs-comment">//Change here</span>
      _tokenIds.increment();

    }
</code></pre>
<p>You can see the full contract as <code>CoolPinapleTokenIdRoyalties.sol</code> in the <a target="_blank" href="https://github.com/IpastorSan/ERC721-NFT-with-EIP2981-royalties">Github Repo</a>.</p>
<h2 id="heading-bonus-2-lets-make-it-cleaner">Bonus 2. Let's make it cleaner</h2>
<p>If separation of concerns and making your project more orderly is important for you, you could implement the EIP-2981 interface and a base contract with the core Royalty logic in separate contracts, and then make your core NFT contract inherit from them. </p>
<p>If you are interested in that kind of file structure, check out this other <a target="_blank" href="https://github.com/IpastorSan/ERC721A-nft-EIP2981-royalties/tree/master/contracts">Github repo</a>. </p>
<h2 id="heading-reference">Reference</h2>
<ul>
<li>Zach Burks, James Morgan, Blaine Malone, James Seibel, "EIP-2981: NFT Royalty Standard," Ethereum Improvement Proposals, no. 2981, September 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2981.</li>
<li>https://medium.com/quick-programming/erc2981-nft-royalty-standard-what-is-it-and-how-do-you-use-it-724f3f12ae36</li>
<li>https://github.com/dievardump/EIP2981-implementation</li>
<li>https://ethereum-blockchain-developer.com/121-erc721-secondary-sales-royalties-erc2981/06-mintable-erc2981-royalties/</li>
<li>https://mintable.medium.com/grant-from-ethereum-foundation-ecosystem-support-eip-2981-royalties-for-nfts-and-a-way-to-mint-dfaa6cb3e08a</li>
<li>https://medium.com/knownorigin/eip-2981-simple-and-effective-royalty-standards-for-all-dbd0b761a0f0</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Deploy your first NFT contract with Foundry]]></title><description><![CDATA[Introduction
What is Foundry
Foundry started as a testing framework for smart contracts in Solidity with a simple premise: Solidity testing should be made using Solidity, not Javascript or Python. From there it has evolved into a fully-fledged develo...]]></description><link>https://blog.fullstackwebthree.com/deploy-your-first-nft-contract-with-foundry</link><guid isPermaLink="true">https://blog.fullstackwebthree.com/deploy-your-first-nft-contract-with-foundry</guid><category><![CDATA[Smart Contracts]]></category><category><![CDATA[foundry]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[NFT]]></category><dc:creator><![CDATA[Ignacio Pastor]]></dc:creator><pubDate>Tue, 05 Jul 2022 16:17:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1693935176479/4a1fc22f-3565-4a49-91f1-78d36a12aa4c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<h3 id="heading-what-is-foundry">What is Foundry</h3>
<p>Foundry started as a testing framework for smart contracts in Solidity with a simple premise: Solidity testing should be made using Solidity, not Javascript or Python. From there it has evolved into a fully-fledged development framework that includes: </p>
<ul>
<li><strong>Forge</strong>: Command line tool that allows you to build, test and deploy your smart contracts using only Solidity. The test themselves are written using the <a target="_blank" href="https://github.com/foundry-rs/forge-std">Forge Standard Library</a> and Forge`s <a target="_blank" href="https://book.getfoundry.sh/forge/cheatcodes.html">Cheatcodes</a> which allows altering the state of the blockchain.</li>
<li><strong>Cast</strong>: Another powerful cli tool used to interact with existing contracts in the blockchain. You can use it to call a contract, send transactions and view on-chain data.</li>
<li><strong>Anvil</strong>: Local blockchain node implementation, written in Rust (fast!). It is the equivalent of Ganache-cli or Hardhat Local Node.</li>
</ul>
<h3 id="heading-why-foundry">Why Foundry</h3>
<p>If you ask me, it just makes more sense, right? Develop your smart contracts in Solidity, test them in Solidity, and deploy them using Solidity scripts (plus a bit of bash here and there).</p>
<p>With other frameworks, (which I use and really like btw, let's not be a framework maxi) you can end up writing more Javascript than Solidity. This has 3 problems:</p>
<ul>
<li>You need to be switching contexts often.</li>
<li>You miss out on the opportunity of actually practicing your Solidity.</li>
<li>If you don´t know Javascript, it doubles the learning curve. Want to be a smart contract developer? Learn Solidity AND Javascript or Python. Not very efficient.</li>
</ul>
<p>On the other side, if you already know Javascript it may be helpful to use other frameworks that abstract a lot of details on the inner workings of blockchain. But there will be a point where you need to make complex test use cases or simply want to get better at Solidity. That is when you should head over to Foundry.</p>
<p>As if this was not enough, Foundry is fast. Blazing fast. With each test case running in milliseconds instead of seconds, you won't lose your day looking at the screen and seeing the test suite painfully and sluggishly advancing.</p>
<h2 id="heading-deploy-your-smart-contract-with-foundry">Deploy your smart contract with Foundry</h2>
<p>Enough with the Intro, I assume that if you are reading this you already have Foundry installed, you have a smart contract and you are ready to deploy it. <strong>Bookmark this article</strong> and use it as a cheatsheet.</p>
<h3 id="heading-have-a-smart-contract-handy">Have a smart contract handy.</h3>
<p>To keep this to the point, I have prepared a simple Alien-themed NFT contract as part of the Road to Web3 course made by the Alchemy team <a target="_blank" href="https://docs.alchemy.com/alchemy/road-to-web3/weekly-learning-challenges/1.-how-to-develop-an-nft-smart-contract-erc721-with-alchemy">(check it out)</a>.</p>
<p>You can see the full code <a target="_blank" href="https://github.com/IpastorSan/RoadToWeb3-week1-nft-foundry/blob/master/src/CryptoAlien.sol">here</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657032774762/239PoudMc.png" alt="00000.png" class="image--center mx-auto" /></p>
<h3 id="heading-prepare-a-basic-deployment-script">Prepare a basic deployment script.</h3>
<p>Foundry lets you prepare a script for deployment similar to the one you would prepare on Hardhat, but written in Solidity instead. 
Under the folder <code>script</code> create a file like <code>deployScript.s.sol</code>. This is a minimal example of what should go inside the script:</p>
<pre><code class="lang-solidity"><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.13;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"forge-std/Script.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../src/CryptoAlien.sol"</span>; <span class="hljs-comment">//Your smart contract goes here</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">NftDeploy</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Script</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        vm.broadcast();

        CryptoAlien nft <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> CryptoAlien();

        vm.stopBroadcast();
    }
}
</code></pre>
<p><strong>Important to note</strong></p>
<ol>
<li>The name of the contract is not important, but having the <code>run()</code> function is essential. </li>
<li><code>vm.broadcast()</code> is a cheatcode to record calls and send the transactions to the actual blockchain. </li>
<li>Any constructor arguments would go inside the <code>new CryptoAlien("name", "symbol")</code> object.</li>
</ol>
<h3 id="heading-deploy-locally-to-anvil">Deploy locally to Anvil</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657033110061/Amk1Khyo7.jpg" alt="Photo by Jonathan Kemper" class="image--center mx-auto" /></p>
<p>First, run <code>anvil</code> in your shell. </p>
<p>You will need to get one of the 10 private keys that Anvil creates for you. Store it in a .env file (not essential, but it is nice to do things right even in localhost) and run <code>source .env</code> in your Bash shell. to import the .env parameters as environment variables. If it doesn't work, head over the <a href="#heading-bonus-troubleshooting"> Troubleshooting </a>  area.</p>
<pre><code class="lang-bash">forge script script/nftDeploy.s.sol:NftDeploy --fork-url http://localhost:8545  --private-key <span class="hljs-variable">$PRIVATE_KEY0</span> --broadcast
</code></pre>
<p>If everything went well you will see a message like this. Congrats!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657034675772/B2LpDo0rj.png" alt="sucess_local.png" /></p>
<h3 id="heading-deploy-to-any-network">Deploy to any network.</h3>
<p>Deploying to an arbitrary network is almost the same as before. You don´t need to run Anvil and you will substitute <code>http:localhost:8545</code> for a public RPC on the network you want to deploy. </p>
<p>In our case, we will deploy to the Goerli testnet and in the same command, we will be submitting the contract´s bytecode for verification on Etherscan so you will also need to have your Etherscan API key in the .env file.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> .env
</code></pre>
<pre><code class="lang-bash">forge script script/nftDeploy.s.sol:NftDeploy --rpc-url <span class="hljs-variable">$GOERLI_RPC_URL</span>  --private-key <span class="hljs-variable">$PRIVATE_KEY</span> --broadcast --verify --etherscan-api-key <span class="hljs-variable">$ETHERSCAN_KEY</span> -vvvv
</code></pre>
<p>And that`s it! Here is the <a target="_blank" href="https://goerli.etherscan.io/address/0x106eee8ba91043946c183cf87409f895e5083450">link</a> for our deployed and verified contract. </p>
<h3 id="heading-bonus-troubleshooting">Bonus: troubleshooting</h3>
<p><strong>Solidity plugin and Remappings.</strong>
If you are using the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity">Solidity extension</a> in VS Code, chances are the remapping in the imports are not working well for you. The quick solution is to create a <code>remappings.txt</code> in the root of your project, and then copy all the remappings to that file, either from your <code>foundry.toml</code> file or using <code>forge remappings &gt; remappings.txt</code> if you are using Foundry´s default remappings.</p>
<p><strong>.env files</strong>
If you are like me and not THAT familiar with Bash, you may find that running <code>source .env</code> gives you an unexpected "Command not Found" error. If this happens make sure that your .env file has this structure on all its elements KEY="VALUE" with no whitespaces anywhere and value properly quoted.</p>
<h3 id="heading-sources">Sources</h3>
<ul>
<li><a target="_blank" href="https://book.getfoundry.sh/index.html">Book of Foundry</a></li>
<li><a target="_blank" href="https://book.getfoundry.sh/tutorials/solidity-scripting.html">Full Example for NFT deployment, Book of Foundry</a></li>
<li><a target="_blank" href="https://docs.alchemy.com/alchemy/road-to-web3/weekly-learning-challenges/1.-how-to-develop-an-nft-smart-contract-erc721-with-alchemy">Road to Web3, week 1</a></li>
</ul>
]]></content:encoded></item></channel></rss>