<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Manny on Medium]]></title>
        <description><![CDATA[Stories by Manny on Medium]]></description>
        <link>https://medium.com/@codingwithmanny?source=rss-da45729a0220------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*RGLqGNDWpLoDaByM_4ScgA.png</url>
            <title>Stories by Manny on Medium</title>
            <link>https://medium.com/@codingwithmanny?source=rss-da45729a0220------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 22 Jun 2026 16:51:04 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@codingwithmanny/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Single Transaction Batch Multiswap to BERA with MetaMask Smart Accounts]]></title>
            <link>https://codingwithmanny.medium.com/single-transaction-batch-multiswap-to-bera-with-metamask-smart-accounts-c9f88d7ef5bc?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/c9f88d7ef5bc</guid>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[berachain]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[evm]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Wed, 17 Jun 2026 15:44:54 GMT</pubDate>
            <atom:updated>2026-06-17T15:44:54.617Z</atom:updated>
            <content:encoded><![CDATA[<h4>Leverage EIP-5792 To Batch Dust Tokens Into BERA</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9tBt64iWKEpM62FTNeo3Aw.png" /><figcaption>Batch Multiswap Dust Tokens For BERA</figcaption></figure><h3>The Problem With Multi-Token Swaps Today</h3><p>If you’ve spent any time in the Berachain ecosystem, your wallet accumulates fast. Incentive tokens, LP rewards, airdrop claims, protocol emissions and before long you’re sitting on a dozen different tokens, each worth a modest amount individually but collectively significant. Converting them all to BERA the traditional way means opening your wallet and signing a separate transaction for every single token: approve, confirm, wait, approve again, confirm again, wait again. For five tokens that’s potentially ten MetaMask popups and ten on-chain transactions.</p><p>This is both tedious and expensive. Every transaction costs gas, every confirmation takes time, and the cognitive overhead of managing the flow manually introduces real risk of mistakes. Miss a step, have gas spike mid-flow, or close your browser at the wrong moment and you’re left with a half-completed swap and tokens stuck in limbo.</p><p><a href="https://eips.ethereum.org/EIPS/eip-5792">EIP-5792</a> (<strong>wallet_sendCalls</strong>) solves this by giving applications a standardized way to submit an array of calls as a single wallet interaction. The user sees one MetaMask prompt, signs once, and all swaps execute atomically in a single on-chain transaction. If any individual swap fails, the entire batch reverts with no partial state, no cleanup required.</p><h3>Why Batching Dust Into BERA (Then swBERA) Makes Sense</h3><p>Berachain’s Proof of Liquidity model means the ecosystem generates a lot of peripheral tokens: BGT emissions and protocol incentive tokens. These accumulate as “dust” that are not worthless, but inconvenient to manage individually.</p><p>BERA is the natural consolidation target. It’s the native gas token, it’s universally liquid, and critically it’s the input token for swBERA (staked BERA, Berachain’s liquid staking derivative that earns validator rewards). Converting your scattered dust tokens to BERA via a single batched transaction, then depositing into swBERA, gets you from a fragmented wallet to a productive yield-bearing position in just two user interactions instead of potentially twenty or more.</p><p>The compounding benefit is time. Swap five tokens individually at 15 seconds of attention each plus 30 seconds of block confirmation per swap and you’re spending over four minutes on rote mechanical work. Batched, the same outcome takes under 30 seconds of active time and that gap only grows as your token count increases.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oWiWwgzCfDWq7SCqM49_UA.png" /><figcaption>Batch swap dusk tokens to BERA</figcaption></figure><h3>Why Not Alternative Batching Options</h3><p>Before landing on EIP-5792, it’s worth understanding why the other batching approaches each fall short for this specific use case.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fcVjHkuhbnd-b0l1gcgozw.png" /><figcaption>Batching Methods Comparison</figcaption></figure><p><strong>Multicall3</strong> is the most universally deployed batching contract on EVM chains and can aggregate multiple calls into one transaction. The critical flaw: <strong>msg.sender</strong> inside the Multicall3 execution context is the Multicall3 contract itself, not the user. Every major DEX router and aggregator checks <strong>msg.sender</strong> for token custody and swap authorization. Routing through Multicall3 breaks these checks entirely, causing every swap call to revert.</p><p><strong>Permit2</strong> solves a real problem of replacing gas-costing <strong>approve()</strong> transactions with gasless off-chain signatures, but unfortunatley it doesn’t batch the swaps themselves. It’s an approval mechanism, not an execution mechanism. You still end up with one swap transaction per token. Permit2 is genuinely useful as a complement to EIP-5792 (it can eliminate the approve calls inside your batch), but it cannot replace the batching layer on its own.</p><p><strong>ERC-4337</strong> is the infrastructure layer that actually powers smart account batching under the hood and it’s what runs beneath EIP-5792 when MetaMask executes your batch. Integrating directly with ERC-4337 means managing <strong>UserOperation</strong> construction, bundler connections, <strong>EntryPoint</strong> interactions, and <strong>Paymaster</strong> configuration from scratch. It’s powerful but exposes significant plumbing that EIP-5792 correctly abstracts away. EIP-5792 is the right interface for application developers; ERC-4337 is an implementation detail the wallet handles for you. Not to mention that this requires quite a bit of infrastructure set up or relying on third-parties.</p><p><strong>EIP-7702</strong> (included in Ethereum’s Pectra upgrade) lets a standard EOA temporarily adopt smart account behaviour for the duration of a single transaction without requiring a full migration. For this use case today the limiting factor is wallet support: EIP-7702 is very new and not yet implemented in production MetaMask builds. It’s the most promising path for eventually bringing batching to all EOA users without requiring a smart account upgrade, but it’s not something you can depend on in production yet due to there lacking proper audited contract code to offer guardrails.</p><p><strong>EIP-5792</strong> hits the correct abstraction level for application developers. You describe what you want (an array of calls) the wallet handles how to execute it, and you get a standardized interface that will keep working as the underlying execution mechanism evolves. <strong>wallet_getCapabilities</strong> lets you detect support gracefully and degrade for unsupported wallets, and <strong>wallet_getCallsStatus</strong> gives you a clean polling interface for tracking the batch result. It’s the right tool for batching.</p><h3>Enabling MetaMask Smart Accounts</h3><p>EIP-5792 batch execution requires a MetaMask smart account rather than a standard EOA. Here’s how to enable it:</p><p><strong>Step 1— Navigate to Accounts</strong> in the top left of the wallet UI.</p><p><strong>Step 2 — Select the 3 Dots &gt; Account</strong> Details for the wallet wanting to upgrade.</p><p><strong>Step 3 — Enable Smart Account</strong> by clicking Set Up.</p><p><strong>Step 4 —Toggle On Smart Account </strong>for the network desired.</p><p><strong>Step 5 — Verify the Upgrade</strong> Once upgraded, your account address remains the same but now has contract code deployed at it. You can verify by checking your address on <a href="https://berascan.com">Berascan</a> where it should show as a contract rather than a plain EOA.</p><blockquote><strong><em>Developing or testing early?</em></strong><em> Use </em><a href="https://metamask.io/flask/"><em>MetaMask Flask</em></a><em> for the developer-focused MetaMask build that includes experimental features, including full EIP-5792 support, before they ship in the stable release.</em></blockquote><h3>The Prompt For The Project</h3><p>To see the full <strong>PLAN.md</strong> for the project take a look here:</p><p><a href="https://github.com/berachain/guides/blob/main/apps/batch-multiswap/PLAN.md">guides/apps/batch-multiswap/PLAN.md at main · berachain/guides</a></p><h3>Batch Transaction Demo</h3><p>Here is a full demo of the entire project:</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FGI4ajJGYf0Y%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DGI4ajJGYf0Y&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FGI4ajJGYf0Y%2Fhqdefault.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/84b2d69672faab6f02a530a182446135/href">https://medium.com/media/84b2d69672faab6f02a530a182446135/href</a></iframe><h3>Full GitHub Code</h3><p>If you’d like to try the code out yourself, take a look at:</p><p><a href="https://github.com/berachain/guides/tree/main/apps/batch-multiswap">guides/apps/batch-multiswap at main · berachain/guides</a></p><h3>Next Steps</h3><p>The source code for this project is a foundation, not a finished product. Several directions are worth exploring depending on your goals.</p><p><strong>swBERA Auto-Compound</strong> — After the batch swap confirms and BERA lands in the user’s wallet, automatically trigger a deposit into the swBERA contract as a follow-on transaction. The full dust-to-yield flow becomes a single user decision rather than two separate ones.</p><p><strong>Dynamic Token Discovery</strong> — Rather than a static list of known Berachain tokens, integrate with a token indexer such as Covalent or Alchemy to surface every non-zero ERC-20 balance the user holds, including obscure incentive tokens and newly launched assets that wouldn’t appear in a hardcoded list.</p><p><strong>EIP-7702 Fallback</strong> — Add a fallback path for when a MetaMask smart account isn’t available. As EIP-7702 wallet support matures, users with plain EOAs will be able to access the same batched flow without upgrading their account type. Detecting capability via <strong>wallet_getCapabilities</strong> already sets you up to route users to the right path when that support lands.</p><p><strong>Slippage and Route Optimization</strong> — The current implementation applies a flat slippage tolerance across all swaps. A smarter approach would apply per-token slippage based on liquidity depth from the KyberSwap route response and warn users when any individual swap in the batch carries outsized price impact before they confirm.</p><p><strong>Multi-Aggregator Routing</strong> — Query multiple aggregators such as KyberSwap, OogaBooga, and Li.Fi in parallel per token and select the best rate for each. Since the calldatas are just bytes to the batch executor, mixing aggregators across the same batch is entirely viable and can meaningfully improve total BERA received.</p><p><strong>Transaction History</strong> — Persist batch IDs and their outcomes to local storage so users can review past batch swaps, see which tokens were included, and verify the final BERA received per swap after the fact.</p><p>If you want to build more on Berachain and see more examples. Take a look at our <a href="https://github.com/berachain/guides"><strong><em>Berachain GitHub Guides Repo</em></strong></a> for a wide variety of implementations that include NextJS, Hardhat, Viem, Foundry, and more.</p><p>❤️ Don’t forget to show some love for this article 👏🏼.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c9f88d7ef5bc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building A Mobile EVM Wallet With React Native]]></title>
            <link>https://codingwithmanny.medium.com/building-a-mobile-evm-wallet-with-react-native-55fbb221ad0d?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/55fbb221ad0d</guid>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[mobile-app-development]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[evm]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Thu, 11 Jun 2026 18:25:58 GMT</pubDate>
            <atom:updated>2026-06-11T18:25:58.211Z</atom:updated>
            <content:encoded><![CDATA[<h4>Understanding The Tech Behind Building A Mobile Blockchain Application</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RSaKbQD6SbFPxzuvcmLjLA.png" /><figcaption>Building An iOS Mobile Wallet with React Native</figcaption></figure><p>The goal of this article isn’t to go through step-by-step building the application (you can do that with AI), but more so understanding the tech needed to build a mobile iOS app for an EVM wallet so that it can help you build one in the future.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4Fcr-9ZlXFUAAJpleSR5CA.png" /><figcaption>EVM iOS Wallet</figcaption></figure><p>For the sake of this article, we’ll be focusing specifically on iOS to understand its features, limitations and work arounds.</p><p>The functionality we’ll limit ourselves to are:</p><p>1 — Setup and manage a new wallet with a passphrase</p><p>2 — Backup the passphrase</p><p>3 — Add an RPC</p><p>4 — Retrieve Balance</p><p>5 — Transfer Transaction</p><h3>Why React Native &amp; Expo</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rghqXNWZsV9G8X34UteNrQ.png" /><figcaption>React Native &amp; Expo</figcaption></figure><p>React Native lets you write your UI once in JavaScript and ship to both iOS and Android. When it comes to a wallet app, the real selling point is the update model. Native Swift apps must go through Apple’s review process for every change, which can take days. React Native (especially with Expo’s over-the-air updates via EAS Update) lets you push JS-layer fixes instantly, without a new App Store submission. For a wallet, where a bug could mean lost funds, that matters enormously.</p><p>Expo specifically adds a lot out of the box: <strong>expo-secure-store</strong> for Keychain access, <strong>expo-local-authentication</strong> for biometrics, and a managed build pipeline (EAS Build) that handles the notoriously painful iOS signing and provisioning certificates. You get a familiar React development experience instead of learning Swift and UIKit from scratch.</p><p>The tradeoff can be rough though. React Native has a reputation for being finicky. Native modules occasionally break between Expo SDK versions, the Metro bundler has its quirks, and you’ll hit moments where something works on Android but not iOS or vice versa. The bridge between JS and native code also adds a thin layer of complexity when you’re doing security-sensitive work.</p><p>Factoring this all in, most production mobile wallets have made exactly this bet. <a href="https://github.com/rainbow-me/rainbow"><strong><em>Rainbow Wallet</em></strong></a><strong> </strong>and <a href="https://github.com/coinbase/wallet-mobile-sdk"><strong><em>Coinbase Wallet SDK</em></strong></a><strong><em> </em></strong>are all built on React Native.</p><h3>What’s Not So Simple</h3><p>Features 1, 3, and 4 are straight forward in terms of their functionality when prompting most LLMs. Backing up a passphrase and interacting with it correctly is the complicated part. Correctly in this case is securely, or as secure as possible.</p><h4>iCloud Backup &amp; Keychain</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U1izS8i9o-5H4-EwDl3A8g.png" /><figcaption>iCloud Backup &amp; Keychain</figcaption></figure><p>The naive approach to backing up a seed phrase seems simple: save it to iCloud or store it in the iOS Keychain, and retrieve it when needed. The problem is how you retrieve it; it’s in plain text.</p><p>iCloud Drive storage is essentially a synced filesystem. A file containing your 12-word mnemonic would sit on Apple’s servers, sync to every signed-in device, and be readable by your app (and anything that can compromise your app) as a raw string. That’s not a backup strategy; it’s a liability.</p><p>The iOS Keychain is better. It’s encrypted storage tied to your app’s bundle identifier, sandboxed from other apps, and persistent across reinstalls. You can gate items with <strong>kSecAttrAccessible</strong> flags like <strong>WhenUnlockedThisDeviceOnly</strong> to prevent the item from being included in unencrypted backups or read on other devices. You can even add <strong>kSecAccessControl</strong> with a biometric requirement so that reading the item triggers a Face ID prompt.</p><p>There’s still a fundamental ceiling here. When your app reads a Keychain item, the raw bytes surface in app memory. At that moment, a compromised JavaScript bundle, a malicious dependency in your <strong>node_modules</strong>, or a memory-inspection attack could exfiltrate the mnemonic. While Keychain is a good base, it’s ultimately not the final solution itself.</p><h4>Apple On-Device Secure Enclave</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aLS9cYulvcpY-BQHIFxzsQ.png" /><figcaption>Apple Secure Enclave (SE)</figcaption></figure><p>Apple’s answer to the memory-exposure problem is the Secure Enclave (SE), which is a dedicated security coprocessor built into every iPhone since the 5s, isolated from the main application processor.</p><p>The key design principle: private keys generated inside the SE never leave it. Your application never holds the private key in memory. Instead, you send data to the SE, the SE performs the cryptographic operation (signing, decryption), and returns only the result. Even if your app, the OS, or the entire file system is compromised, the SE’s keys remain inaccessible.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yEtn6HUzGRxkNicKLplflQ.png" /><figcaption>Secure Enclave Sequence Diagram</figcaption></figure><p>The SE has its own encrypted memory, a hardware random number generator, and runs its own microkernel. When you require biometric authentication to use an SE key, the biometric check happens inside the SE itself and not in the OS where it could be spoofed. It’s the closest thing to a hardware wallet built into your phone.</p><h4>Supported Cryptographic Algorithm Elliptical Curves</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-JLZIxJGqCgIoU76kjo5Eg.png" /><figcaption>Elliptical Curves</figcaption></figure><p>Here’s where EVM wallet development hits its biggest wall. The Secure Enclave only supports <strong>ECDSA on the NIST P-256 curve</strong> (<strong>kSecAttrKeyTypeECSECPrimeRandom</strong>). It does not support:</p><ul><li><strong>secp256k1</strong> — the elliptic curve used by Ethereum and Bitcoin ❌</li><li><strong>ed25519</strong> — used by Solana ❌</li><li><strong>Arbitrary data storage</strong> — you can’t put a 24-word mnemonic inside the SE ❌</li></ul><p>You cannot put an Ethereum private key inside the Secure Enclave and have it sign transactions directly. The SE will simply refuse. This is the single biggest constraint in iOS wallet design, and it forces every major mobile wallet into the same architectural workaround.</p><h4>Encryption Workaround</h4><p>Since we can’t store secp256k1 keys in the SE directly, we use the SE for what it does support, which is to use that capability to protect what we actually need to store. This is the pattern used by most wallets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oLOEpV1vx2p5dGVJL3jP7Q.png" /><figcaption>Encryption Workaround</figcaption></figure><p>The SE-wrapped encryption key pattern:</p><ol><li><strong>Generate a P-256 keypair inside the Secure Enclave</strong>, gated by biometric authentication. The private key never leaves the SE.</li><li><strong>Generate an AES-256 symmetric key </strong>(the “wrapping key”) and store it in the Keychain.</li><li><strong>Encrypt the wrapping key</strong> using the SE’s P-256 public key. Store the ciphertext.</li><li><strong>Encrypt your mnemonic / secp256k1 private keys</strong> with the AES key. Store the ciphertext in Keychain or on disk.</li></ol><p>To sign an Ethereum transaction:</p><p>Biometric prompt triggers → SE unwraps the AES key → AES key decrypts the mnemonic → derive the secp256k1 private key → sign → immediately zero the memory.</p><p>The mnemonic is in plaintext for milliseconds, only after a successful biometric prompt, and the root of trust (the SE keypair) is hardware-bound and non-exportable. No amount of JS compromise can extract the SE key.</p><h4>iOS Simulator Limitations</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*thH0idT5T_8atBqg_m5ncA.png" /><figcaption>iOS Simulator</figcaption></figure><p>The iOS Simulator runs on your Mac’s x86/ARM processor, which means it doesn’t have a Secure Enclave. Biometric APIs exist in the Simulator, but Face ID is simulated (you trigger it via the menu: <em>Features → Face ID → Matching Face</em>) and <strong>kSecAttrTokenIDSecureEnclave</strong> operations simply fail or are silently downgraded.</p><p>This means you need <strong>two code paths</strong>:</p><ul><li><strong>Simulator</strong>: Fall back to standard Keychain storage without SE-backed keys. You can use <strong>expo-secure-store</strong> directly, or generate a software P-256 key stored in Keychain as a stand-in.</li><li><strong>Physical device</strong>: Full SE-backed key generation with real biometric gating. <strong>NOTE: </strong>Due to the additional native modules needed, an <strong><em>Apple Paid Developer</em></strong> account is needed to build and deploy on your device for testing.</li></ul><p>In practice, you’d detect this with a runtime check (<strong>expo-device</strong>&#39;s <strong>isDevice</strong> flag), or wrapping SE key generation in a try/catch that falls back gracefully. Flag your simulator builds clearly in development so you never accidentally test security-critical paths against the fake implementation.</p><p>The only way to properly test the full security model is to use real SE keys, real biometrics, and real attestation and load the app onto a physical iPhone via EAS Build or Xcode.</p><h3>App Demo</h3><p>We’ll now walkthrough a demo of the application running on iOS device.</p><iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DIxY8_hNG6kk&amp;type=text%2Fhtml&amp;schema=youtube&amp;display_name=YouTube&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FIxY8_hNG6kk%3Ffeature%3Doembed" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/965f05c80ef8f40f4c561bc51622bd63/href">https://medium.com/media/965f05c80ef8f40f4c561bc51622bd63/href</a></iframe><h3>Final Code</h3><p>If you’d like to try the code out yourself, take a look at <a href="https://github.com/berachain/guides/tree/main/apps/evm-wallet">https://github.com/berachain/guides/tree/main/apps/evm-wallet</a>:</p><p><a href="https://github.com/berachain/guides/tree/main/apps/evm-wallet">guides/apps/evm-wallet at main · berachain/guides</a></p><h3>Next Steps</h3><p>A few natural directions from here:</p><p><strong>WalletConnect integration</strong> — letting the wallet connect to dApps via QR code is the feature that makes it actually useful day-to-day. The <strong>@walletconnect/web3wallet</strong> SDK handles the session management; the tricky part is routing signing requests back through your SE-wrapped key pipeline.</p><p><strong>Transaction history</strong> — querying an indexer or a block explorer API gives you a proper activity feed without running your own node.</p><p><strong>Push notifications for incoming transactions</strong> — requires a backend listener (or a webhook from a web service), but transforms the app from a read/sign tool into something that feels live.</p><p>If you want to build more on Berachain and see more examples. Take a look at our <a href="https://github.com/berachain/guides"><strong><em>Berachain GitHub Guides Repo</em></strong></a> for a wide variety of implementations that include NextJS, Hardhat, Viem, Foundry, and more.</p><p>❤️ Don’t forget to show some love for this article 👏🏼.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=55fbb221ad0d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How 2FA Is Possible Thanks to EIP-7951]]></title>
            <link>https://codingwithmanny.medium.com/how-2fa-is-possible-thanks-to-eip-7951-42c3ecbd3d38?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/42c3ecbd3d38</guid>
            <category><![CDATA[fusaka-upgrade]]></category>
            <category><![CDATA[eip-7951]]></category>
            <category><![CDATA[berachain]]></category>
            <category><![CDATA[evm]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Fri, 29 May 2026 13:03:48 GMT</pubDate>
            <atom:updated>2026-05-29T13:03:48.892Z</atom:updated>
            <content:encoded><![CDATA[<h4>EIP-7951 Allows For Two-Factor Authentication On EVM Chains Like Berachain</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lRSTLsas57V-9Ka-WO19dA.png" /><figcaption>How 2FA Is Possible Thanks to EIP-7951</figcaption></figure><p>Whenever a finger touches a MacBook or Face ID recognises a face, your identity is confirmed in milliseconds. This is possible thanks to the Secure Enclave, a dedicated security chip that generates and stores a P-256 private key that never leaves the device. The same cryptographic primitive secures billions of phones, laptops, and hardware tokens worldwide. Until recently, Ethereum &amp; Berachain had no efficient way to verify it on-chain. <a href="https://eips.ethereum.org/EIPS/eip-7951"><strong><em>EIP-7951</em></strong></a> changes that.</p><h3>What Is EIP-7951 and P-256?</h3><p>EIP-7951 introduces native support for understanding signatures that come from devices like your phone or laptop, in the form of a precompile. A precompile is a special contract built directly into the <a href="https://ethereum.org/roadmap/fusaka/"><strong><em>Ethereum</em></strong></a> and <a href="https://github.com/berachain/BRIPs/blob/main/meta/BRIP-0010.md#8-proof-of-liquidity-consensus-layer-parameter-updates-1"><strong><em>Berachain</em></strong></a> protocol rather than deployed in Solidity, which means it runs fast and cheap compared to equivalent on-chain code.</p><p>That precompile is called P256VERIFY and lives at address 0x100. It performs ECDSA signature verification over the secp256r1 elliptic curve, also known as P-256 or prime256v1. This differs from secp256k1, the curve native to EMV chains that your EOA already uses for signing transactions. P-256 is a NIST-standardised curve built into nearly every piece of modern secure hardware: Apple Secure Enclave, Android Keystore, YubiKeys, HSMs, and FIDO2/WebAuthn authenticators. Both curves provide approximately 128 bits of security, but only secp256k1 was verifiable on EVM chains efficiently until now.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1JkAykK5no8w41eoYZpgAQ.png" /><figcaption>Comparison of secp256k1 vs secp256r1 (Scaled down)</figcaption></figure><p>The precompile takes exactly 160 bytes of input, packed as five 32-byte fields:</p><pre>input = hash (32 bytes)<br>      | r    (32 bytes)<br>      | s    (32 bytes)<br>      | qx   (32 bytes)<br>      | qy   (32 bytes)<br><br># Example<br># hash = 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824<br># r    = 0xa9f2f9a9c6b1e2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6<br># s    = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5<br># qx   = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296<br># qy   = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5</pre><p>On successful verification it returns a single 32-byte word. On failure or invalid input it returns empty bytes:</p><pre>// Valid signature<br>0x0000000000000000000000000000000000000000000000000000000000000001<br><br>// Invalid signature or bad input<br>0x (empty)</pre><p>The cost is 6900 gas. Before EIP-7951, verifying a P-256 signature required a full Solidity elliptic curve library, which typically consumed 200,000 to 300,000 gas per verification. The precompile reduces that by roughly 97%.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rMD9QbAzO2NQFoohOcYEqg.png" /><figcaption>Comparing Ethereum and Berachain P-256 Verification Costs Before &amp; After EIP-7951</figcaption></figure><h3>The Problem Before EIP-7951</h3><p>Before this precompile, verifying a P-256 signature on Ethereum required implementing the full elliptic curve arithmetic in Solidity. This was not just expensive in gas terms; it was often prohibitively slow, error-prone, and impractical for production use. Hardware-backed signing through devices like the Secure Enclave or WebAuthn authenticators was theoretically possible but had no viable path to production on L1.</p><p>The problems ran deeper than gas costs. Every aspect of the user experience was built around assumptions that excluded mainstream users: you needed a seed phrase, you needed ETH in your wallet before you could do anything, and the only signing key Ethereum understood was one you managed yourself. There was no path from Touch ID to a transaction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jcUaqSGWx08kxhL7WzaUEA.png" /><figcaption>UX Before &amp; After EIP-7951</figcaption></figure><h3>What EIP-7951 Unlocks</h3><p>Native P-256 verification on EVM chains opens up a category of use cases that were previously either impossible or impractical.</p><p><strong>Passkey-native wallets.</strong> Users can sign transactions using device biometrics (Face ID, Touch ID, Windows Hello) without ever managing a seed phrase. The signing key lives in hardware and cannot be exported.</p><p><strong>WebAuthn as on-chain authentication.</strong> FIDO2/WebAuthn devices generate P-256 signatures by default. These can now be verified directly in smart contracts, enabling hardware security keys as account signers.</p><p><strong>Multi-factor authentication on-chain.</strong> A contract can require both a standard secp256k1 signature and a P-256 signature before executing sensitive operations. This is the basis for the 2FA pattern described below.</p><p><strong>Enterprise and institutional signing.</strong> Hardware Security Modules (HSMs) and Trusted Execution Environments (TEEs) commonly use P-256. Organizations can now integrate these into their on-chain key management strategies without custom cryptography libraries.</p><h3>Example 2FA Solidity Contract</h3><p>The following contract holds funds and executes calls on behalf of its owner. It acts as the account: ETH/BERA lives in the contract, and all transactions originate from it. The owner never sends transactions directly. Instead, anyone (a relayer, a bundler, or the owner themselves) can submit a call to execute, and the contract verifies two things before proceeding: that the request was authorised by the owner&#39;s EOA key, and that the owner&#39;s hardware device confirmed it with a P-256 signature.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5gw0oguk1S-f70ps9yZIFA.png" /><figcaption>2FA Solidity Contract Sequence Diagram</figcaption></figure><pre>// SPDX-License-Identifier: MIT<br>pragma solidity ^0.8.24;<br><br>contract TwoFactorAccount {<br>    address public constant P256VERIFY = address(0x100);<br><br>    // secp256k1 curve order — used for low-s malleability check<br>    uint256 constant SECP256K1_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;<br><br>    address public owner;<br>    uint256 public p256PublicKeyX;<br>    uint256 public p256PublicKeyY;<br>    uint256 public nonce;<br><br>    constructor(<br>        address _owner,<br>        uint256 _p256x,<br>        uint256 _p256y<br>    ) {<br>        owner = _owner;<br>        p256PublicKeyX = _p256x;<br>        p256PublicKeyY = _p256y;<br>    }<br><br>    function execute(<br>        address target,<br>        uint256 value,<br>        bytes calldata data,<br>        uint8 v,<br>        bytes32 r,<br>        bytes32 s,<br>        bytes32 p256R,<br>        bytes32 p256S<br>    ) external {<br>        // Contract computes the intent hash from calldata it already has.<br>        // Binds target, value, calldata, nonce and chain to prevent replay.<br>        bytes32 intentHash = keccak256(<br>            abi.encodePacked(target, value, data, nonce, block.chainid)<br>        );<br><br>        bytes32 ethHash = keccak256(<br>            abi.encodePacked(&quot;\x19Ethereum Signed Message:\n32&quot;, intentHash)<br>        );<br><br>        // Signature malleability: enforce canonical low-s for the EOA sig.<br>        // For any valid (r, s), (r, n-s) is also valid. Requiring s &lt;= n/2<br>        // ensures only one form is accepted, preventing replays via the<br>        // alternate form.<br>        require(uint256(s) &lt;= SECP256K1_N / 2, &quot;Non-canonical s&quot;);<br><br>        // Verify owner EOA signature over the intent hash<br>        address recovered = ecrecover(ethHash, v, r, s);<br>        require(recovered == owner, &quot;Invalid owner signature&quot;);<br><br>        // Signature malleability: bind p256Hash to intentHash so the hardware<br>        // sig is scoped to the same nonce-bound payload as the EOA sig.<br>        // Prevents a stale P-256 sig from a previous tx being recycled.<br>        bytes memory p256Input = abi.encodePacked(<br>            intentHash,<br>            p256R,<br>            p256S,<br>            p256PublicKeyX,<br>            p256PublicKeyY<br>        );<br>        (bool success, bytes memory result) = P256VERIFY.staticcall(p256Input);<br>        require(<br>            success &amp;&amp; result.length == 32 &amp;&amp; uint256(bytes32(result)) == 1,<br>            &quot;Invalid P-256 signature&quot;<br>        );<br><br>        // Both factors verified — execute from the contract<br>        nonce++;<br>        (bool executed,) = target.call{value: value}(data);<br>        require(executed, &quot;Execution failed&quot;);<br>    }<br><br>    receive() external payable {}<br>}</pre><p>The contract holds the ETH and initiates every call via target.call. The submitter of the execute transaction pays gas but has no ability to alter what gets called: the target, value, and calldata are all bound into the signed intent hash. The owner signs that intent offline with their EOA key and confirms it on their hardware device, then hands both signatures to whoever is submitting. The nonce prevents the same pair of signatures from being replayed.</p><h3>Taking It Further with EIP-7702</h3><p>The contract above works well for newly deployed smart accounts. But what about existing EOAs that already hold assets and have history?</p><p>EIP-7702 solves this. It introduces a new transaction type that lets an EOA temporarily delegate its code to a smart contract implementation. In a single transaction, an EOA can point to the TwoFactorAccount implementation above, making it behave like a smart contract while keeping the same address and balance. The delegation is reversible: the EOA can remove it with another EIP-7702 transaction.</p><p>This means a user with an existing MetaMask wallet can upgrade it to require 2FA for high-value operations without migrating funds, without changing addresses, and without deploying a new contract. Their wallet address stays the same. Their ETH and tokens stay put. They simply gain additional validation logic.</p><p>A practical flow looks like this: the user submits an EIP-7702 authorization that delegates their EOA to the TwoFactorAccount implementation and registers their Secure Enclave public key. From that point on, sensitive transactions require both their existing private key and a Touch ID confirmation. The P-256 signature from the Secure Enclave is verified on-chain by the P256VERIFY precompile.</p><p>One important caveat: EIP-7702 does not make transactions gasless. The transaction still needs to be submitted by someone who can pay gas. In practice this means relying on a bundler or relayer service, which introduces a trust dependency that developers and users should account for in their threat model.</p><h3>Security Caveats</h3><p><strong>Signature malleability.</strong> Unlike secp256k1 on Ethereum, P-256 signatures under NIST FIPS 186–5 are not required to be non-malleable. A valid signature (r, s) has a counterpart (r, n - s) that is also valid. Applications that require non-malleability, for example to prevent transaction replays in edge cases, must implement additional checks at the application layer. The nonce pattern shown in the example contract above handles replay protection, but developers should be explicit about this requirement.</p><p>Threats can include Mempool front-running or re-used P-256 signatures.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o1Ewc-YTfjxtTvY3YPe3cA.png" /><figcaption>Signature Malleability Example</figcaption></figure><h3>Demo &amp; Full Code Repository</h3><p>Here is a demo of the entire project and the full source code.</p><h4>Project Demo</h4><p>See the full interaction of the app here:</p><iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Dj-sa1RVevsc&amp;type=text%2Fhtml&amp;schema=youtube&amp;display_name=YouTube&amp;src=https%3A%2F%2Fwww.youtube.com%2Fembed%2Fj-sa1RVevsc%3Ffeature%3Doembed" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/2d980290bb542262168ffd9630ec7a35/href">https://medium.com/media/2d980290bb542262168ffd9630ec7a35/href</a></iframe><h4>GitHub Full Source Code</h4><p>Full source code can be found here:<br><a href="https://github.com/berachain/guides/tree/main/apps/eip7951">https://github.com/berachain/guides/tree/main/apps/eip7951</a></p><p><a href="https://github.com/berachain/guides/tree/main/apps/eip7951">guides/apps/eip7951 at main · berachain/guides</a></p><h3>Next Steps</h3><p>EIP-7951 is a small addition to the EVM chains with significant practical implications. A single precompile at 0x100 and 6900 gas bridges the gap between the hardware authentication infrastructure already in billions of devices and the on-chain verification that EVM smart contracts need.</p><p>Combined with EIP-7702, it enables existing EOAs to gain smart account capabilities including hardware-backed 2FA without migration, without new addresses, and without abandoning existing tooling.</p><p>The 2FA pattern explored in this article is one application. Others worth exploring include:</p><ul><li><strong>Passkey-native onboarding</strong> where new users never see a seed phrase and authenticate entirely through device biometrics.</li><li><strong>WebAuthn session keys</strong> for dApps that want hardware-grade authentication without wallet popups on every interaction.</li><li><strong>HSM-backed institutional signers</strong> for DAOs and protocols that need auditable, hardware-bound signing authority.</li><li><strong>Cross-chain identity</strong> using P-256 keys that are already recognised by enterprise blockchains and interoperability protocols.</li><li><strong>Recovery mechanisms</strong> where a Secure Enclave key acts as a guardian for account recovery without a centralised custodian.</li></ul><p>The authentication hardware already exists in your users’ pockets. EIP-7951 gives EVM chains the ability to trust it.</p><p>If you want to build more on Berachain and see more examples. Take a look at our <a href="https://github.com/berachain/guides"><strong><em>Berachain GitHub Guides Repo</em></strong></a> for a wide variety of implementations.</p><p>❤️ Don’t forget to show some love for this article 👏🏼.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=42c3ecbd3d38" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building AI Agent Micropayment APIs with x402 on Berachain]]></title>
            <link>https://codingwithmanny.medium.com/building-ai-agent-micropayment-apis-with-x402-on-berachain-e8c24167c19f?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/e8c24167c19f</guid>
            <category><![CDATA[x402]]></category>
            <category><![CDATA[payments]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[evm]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Tue, 12 May 2026 15:53:40 GMT</pubDate>
            <atom:updated>2026-05-12T15:53:40.247Z</atom:updated>
            <content:encoded><![CDATA[<h4>Understanding x402 &amp; Using $HONEY To Pay For An API Request</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ICM-CSxK5lvE2lismG8o8g.png" /><figcaption>Building an x402 API That Accepts Berachain $HONEY Payments</figcaption></figure><p><a href="https://x402.org"><strong>x402</strong></a> is a payments standard built on HTTP that brings a 402 Payment Required status code back to use. The core idea is straightforward: a user or a client makes a request for some data from a server and instead of returning the result right away it responds with a 402 status code and a set of payment instructions (token address, amount, recipient, etc). The client pays, attaches proof to a follow-up request, and the server verifies and responds with the actual payload. No subscriptions, no API keys, no billing dashboards. Just a token transfer and a retry.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j97JnhaerbhB1gCBDOHCuw.png" /><figcaption>x402 Sequence Diagram</figcaption></figure><p>In this tutorial we’re using x402 to build a weather API that accepts <strong>Honey ($HONEY), </strong>Berachain’s native stablecoin, as the price of admission. Hit the endpoint without paying and you get a 402 with the Honey payment terms. Send the required amount to the server&#39;s wallet on Berachain and retry with your transaction hash, and the weather data comes back. The goal is to show how straightforward it is to build a resource that any HTTP client (or AI agent) can discover, pay for, and consume, using a stable on-chain token as the unit of exchange.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*muL1XJqmCA3sq-cD" /><figcaption>weather-x402 app</figcaption></figure><h3>Prerequisites</h3><p>Before starting, make sure you have the following:</p><ul><li><strong>Bun</strong> installed (curl -fsSL https://bun.sh/install | bash)</li><li>Basic familiarity with TypeScript</li><li>Metamask wallet Chrome extension installed with Smart Accounts enabled and Bepolia network configured</li><li>Testnet BERA (for gas) and testnet Honey (<a href="https://bepolia.faucet.berachain.com"><strong><em>https://bepolia.faucet.berachain.com</em></strong></a>)</li><li>Thirdweb account — We will set this up</li><li>Cursor, Claude Code, or AI centric IDE</li></ul><p><strong>NOTE:</strong> Currently Honey and USDC have the functionality required for x402</p><h3>Setting Up Thirdweb Facilitator</h3><p>The most involved part of setup is obtaining the right API keys and server wallet needed for the x402 facilitator.</p><p><strong>NOTE:</strong> We are using Bepolia Testnet (Chain ID 80069) because if you wanted to process transactions on Mainnet, you would need to set up a paid plan with Thirdweb.</p><h4>Setting Up A Project</h4><p>The first thing we’ll need to do is sign up to <a href="https://thirdweb.com/login?next=%2Fteam"><strong><em>Thirdweb</em></strong></a> and create a new project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IdjLXqgDgc8A3tNUqXDdjg.png" /><figcaption><a href="https://thirdweb.com/login?next=%2Fteam">https://thirdweb.com/login</a></figcaption></figure><p>Once you’ve signed in, you’ll probably need to create a team at <a href="https://thirdweb.com/account">https://thirdweb.com/account</a> if you haven’t done so already. I’m going to assume you have gone through the process and you have maybe set up a free account for that team.</p><p>After you’ve created a team, go into its projects and create a new one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hQGDAOUR1blFJJ89Y_6zeg.png" /><figcaption>Thirdweb Create Project</figcaption></figure><p>While configuring the new project, we’re going to ensure that requests can be made from localhost:3000, which will be our backend api.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KgGzMtB4mgTzBKSADv21aQ.png" /><figcaption>Configuring New Project</figcaption></figure><p>Copy your Client ID and your Secret Key and store them somewhere so that we can reference this later in our code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gy3khMX4skLMrfHwFLNRTg.png" /><figcaption>Thirdweb Project API Keys</figcaption></figure><h4>Setup Server Wallet</h4><p>Now that we have our project and API keys set up, the next thing we’ll need to do is create a server wallet. The server wallet is that will execute the transactions on behalf of the user for x402. We’ll be building this on Bepolia Testnet, but if you were running this on Berachain Mainnet it would likely need to have a balance of the native gas token BERA in order to sponsor those transactions.</p><blockquote>When the wallet wanting to pay for a resource is ready, it doesn’t submit an on-chain transaction itself. Instead, it creates an offchain cryptographic signature (a payment authorization) that it attaches to the HTTP request. A facilitator server then verifies that signature and uses its own server wallet to settle the payment on-chain on the client’s behalf. The server wallet is what actually broadcasts the transaction and covers gas, which is why facilitators like Thirdweb’s use managed server wallets to handle this across many chains without manual gas management.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SU-QuaGpW4-C6wpBuXXV8A.png" /><figcaption>Thirdweb Server Wallets</figcaption></figure><p>Now that we have our API Keys and the Server Wallet address, we can start building.</p><h3>Building Our Backend API With Cursor</h3><p>Using Cursor, let’s start with a simple prompt using Opus 4.7 in plan mode to create the x402 backend API</p><pre>I want to build a backend api that let&#39;s a user perform an x402 request to get the weather of a city in the US and process a payment on the Berachain EVM network.<br>It should include the ability to:<br>- Make a request that doesn&#39;t require an x402 payment to ensure that the request for the weather is working correctly<br>- Ability to make a x402 request for the weather of a city and require a payment<br>- Process the payment leveraging a facilitator from Thirdweb<br>- The payment should be made using Berachain&#39;s stablecoin called $HONEY<br><br>Make sure to look up Thirdweb&#39;s docs on x402.<br><br>Use the following APIs<br>- https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(city)}&amp;format=json&amp;limit=1<br>- https://api.weather.gov/points/${lat.toFixed(4)},${lon.toFixed(4)}<br><br>For the tech stack<br>- Use bun as the package manager<br>- Use viem for any evm or wallet interactions<br>- Make sure it&#39;s built with TypeScript<br>- Utilizing Hono instead of Express<br>- Use Thirdweb as a Facilitator<br>- It should be built on top Berachain Bepolia Testnet - Chain ID 80069<br>- It should have have .env and .env.example with presets<br><br>Build this in a folder called /api</pre><p>This is a good starting point and will definitely require a few more prompts to get it to place that we need. This is what was generated for me, but if you’d like to see the working code, see the full github repository link further down.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jtkeaRVEDT59JQoqnX-mgQ.png" /><figcaption>x402 Backend API</figcaption></figure><p>Once the project is setup, we’ll need to make sure our .env file has the right Thirdweb API keys, the receiver of the funds is setup, and we give the server wallet address we generated.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DR3ZClZWbgmmoZHYIemoZg.png" /><figcaption>Cursor Backend Configuing Environment Variables</figcaption></figure><h3>Building Our Frontend With Cursor</h3><p>For the frontend, let’s work with the following prompt to plan things out:</p><pre>I want to build a frontend that allows a user to connect their EVM wallet with the Berachain network to the page and perform the following:<br>1 - Make a request leveraging the non-x402 endpoint to get the weather for a city and show the response<br>2 - Make a request to the x402 weather api and return the response for the payment details<br>3 - Make a request to the x402 weather api, prompt the evm wallet to sign the necessary transaction, and pass the signature to process the payment through the x402 endpoint<br><br>These should be 3 different card sections for the user to interact with and shouldn&#39;t be dependent of each other.<br><br>Ensure that the backend has CORS enabled correctly to allow for requests coming from the frontend.<br><br>The tech stack should include:<br>- Use bun as the package manager<br>- Use viem for any evm or wallet interactions<br>- Make sure it&#39;s built with TypeScript<br>- Use React with Vite<br>- Ensure there the right integrations with Thirdweb&#39;s x402 facilitator<br>- It should be built on top Berachain Bepolia Testnet - Chain ID 80069<br>- It should have have .env and .env.example with presets</pre><p>This should generate quite a bit, and this may take some additional prompts in terms of getting it to read through Thirdweb’s documentation and x402 to fully understand how to retrieve the signature and pass it to the x402 payment gated API.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LqXybWBamyh1ONaTo7tg1w.png" /><figcaption>x402 Web</figcaption></figure><p>The frontend in this case will need the Thirdweb Client ID, in order to make the requests to Thirdweb.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uH5VGzet2cgOqiIDnbPRKw.png" /><figcaption>x402 Frontend Configuring Environment Variables</figcaption></figure><p>Now we’re ready to run the backend and the frontend in two different terminals and get it working together.</p><h3>x402 Project In Action</h3><p>The first request we’re going to make sure that the weather API works as expected.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NxvAKHXOjAcNC1VH8UkeNg.png" /><figcaption>Successful Free Request</figcaption></figure><p>Next, we just want to see what the request for the x402 endpoint returns when it expects a payment and responds with instructions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sjXbR1BfIvHAtQ4ZIAAGBw.png" /><figcaption>x402 API Endpoint Payment Instructions</figcaption></figure><p>Lastly, we’ll put that all together by connecting our wallet, getting the instructions for the x402 payment, signing a transaction, and sending that to validate and retrieve data.</p><blockquote><strong>NOTE:</strong> It’s not uncommon where it comes back with an error that the simulation fails. In those cases just try again and it should go through.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TfqtONkVvj4ojMED1a70jg.png" /><figcaption>Successful x402 API Transaction Paid with Berachain $HONEY</figcaption></figure><p>This demonstrates the full cycle of getting payment instructions and making an x402 payment with Berachain’s native stablecoin $HONEY in order to receive the API response.</p><h3>Full Code Project</h3><p>If you’d like to run the code yourself, you can check out the full source code and results from the project here:<br><a href="https://github.com/berachain/guides/tree/main/apps/x402"><strong><em>https://github.com/berachain/guides/tree/main/apps/x402</em></strong></a></p><p><a href="https://github.com/berachain/guides/tree/main/apps/x402">guides/apps/x402 at main · berachain/guides</a></p><h3>Next Steps</h3><p>Now that you’ve built a working AI-native micropayment system on Berachain, here are some ideas to take it further:</p><ul><li><strong>Dynamic pricing.</strong> Return different X-Payment-Amount values per route (historical weather costs more than current), or per caller wallet based on usage history.</li><li><strong>MCP configuration.</strong> Set up an agent wallet MCP which leverages this x402 API.</li><li><strong>Multi-token support . </strong>Extend the payment headers to list multiple accepted tokens. The fetch_or_get_payment_terms tool can select whichever token the agent wallet holds a balance of.</li></ul><p>If you want to build more on Berachain and see more examples. Take a look at our <a href="https://github.com/berachain/guides"><strong><em>Berachain GitHub Guides Repo</em></strong></a> for a wide variety of implementations that include NextJS, Hardhat, Viem, Foundry, and more.</p><p>❤️ Don’t forget to show some love for this article 👏🏼.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e8c24167c19f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build A Berachain Mobile dApp With WalletConnect & Expo]]></title>
            <link>https://medium.com/berachain-devs/build-a-berachain-mobile-dapp-with-walletconnect-expo-c5cde75dbac2?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/c5cde75dbac2</guid>
            <category><![CDATA[expo]]></category>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[walletconnect]]></category>
            <category><![CDATA[react-native]]></category>
            <category><![CDATA[berachain]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Thu, 29 Feb 2024 21:16:39 GMT</pubDate>
            <atom:updated>2024-05-21T17:06:17.598Z</atom:updated>
            <content:encoded><![CDATA[<h4>How To Build A Mobile Web3 dApp With WalletConnect &amp; React Native Using Expo</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w2YU-AfXyLKmRK_MIoVa5A.png" /><figcaption>Learn To Build A Berachain Mobile DApp</figcaption></figure><h3>🛠️ Let’s Build A Mobile dApp On Berachain</h3><p>Most web3 dApps are typically built for web and don’t have much love for mobile. In this tutorial we’re going to change that by building an iOS app with <a href="https://expo.dev/"><strong><em>Expo</em></strong></a> that works with <a href="https://www.berachain.com"><strong><em>Berachain</em></strong></a>.</p><p>Our mobile dApp will be able to connect our MetaMask mobile wallet, sign a message, and deploy the bytecode for a <a href="https://github.com/berachain/guides/blob/main/apps/hardhat-viem-helloworld/contracts/HelloWorld.sol"><strong><em>HelloWorld contract</em></strong></a><strong><em> </em></strong>to Berachain Artio (Berachain Testnet).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*J6sztHxqq-rGBsv77qWvRA.png" /><figcaption>Berachain Mobile dApp With WalletConnect Web3Modal &amp; Expo</figcaption></figure><h4>Why Expo?</h4><p>Expo has been gaining popularity in the React Native space for mobile development and three main reasons why developers are choosing Expo over Native development or just React Native are:</p><ol><li>Support for multiple platforms — iOS and Android</li><li>Prebuilt components and functionality</li><li>Expo EAS (Expo Application Service) which allows for immediate updates versus going through AppStore approvals for each update</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IpttAhdyojpj674uE_Odcw.png" /><figcaption><a href="https://expo.dev/">https://expo.dev/</a></figcaption></figure><h4>WalletConnect &amp; Mobile</h4><p>WalletConnect has been a staple for most web3 dApps and making sure projects are setup to support has many wallets, while giving easy tooling for developers to get out-of-the-box wallet connection integration with their SDKs.</p><p>WalletConnect also has a mobile specific SDK that allows for wallet connections and interactions to happen directly on your mobile device. This is the SDK that we’ll be using for this tutorial.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6U1OxVR1Yotz9WTqmhjEgg.png" /><figcaption><a href="https://docs.walletconnect.com/web3modal/react-native/about"><em>WalletConnect Web3Modal For React Native</em></a></figcaption></figure><h3>📋 Requirements</h3><p>Make sure to have the following installed or setup prior to continuing.</p><blockquote><strong>NOTE:</strong> For the purposes of this tutorial we’ll be focusing on iOS</blockquote><ul><li>NVM or Node v20.11.0</li><li><a href="https://expo.dev/expo-go"><strong><em>Expo Go</em></strong></a></li><li>Xcode with iOS Simulator</li><li><a href="https://metamask.io/download/"><strong><em>iOS MetaMask Mobile Wallet</em></strong></a></li><li>Berachain $BERA tokens — (Need <a href="https://artio.faucet.berachain.com"><strong><em>Berachain faucet tokens</em></strong></a>?)</li></ul><h3>💻 Pre-Step iOS Simulator On Sonoma</h3><p>With the latest Mac OS Sonoma and Xcode, Apple has decided to create an extra step that would require developers to download a specific runtime simulator that may not be pre-installed.</p><p>To start, run <strong><em>Xcode</em></strong>, and go to <strong><em>Window</em></strong> &gt; <strong><em>Devices &amp; Simulators</em></strong>. This will open up a new window. Select <strong><em>Simulators</em></strong> in the top left, and from the dropdown for <strong><em>Device Type</em></strong> select <strong><em>iPhone 15 Pro Max</em></strong> and for <strong><em>OS Version</em></strong> choose <strong><em>Download more simulator runtimes….</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*58v6k2Gn4flBW_pKPLeKAg.png" /><figcaption>Xcode Simulator Runtimes</figcaption></figure><p>This will open another window to then download <strong>iOS 17.2</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iGU3xySMqLmLc7hjU2RhDQ.png" /><figcaption>Xcode Simulator Runtimes Download Platforms</figcaption></figure><p>Wait until this download completes and double check that your Simulator.app has successfully downloaded by running:</p><pre>open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GL8vh94rTaP6AcIfuHuUrw.png" /><figcaption>iOS Simulator Successfully Running</figcaption></figure><h3>📱Building A Berachain Mobile dApp</h3><p>Now that we our iOS Simulator setup, we should be good to build out our entire mobile application.</p><h4>WalletConnect Project ID</h4><p>Before we start coding, we’ll need to make sure that we setup a WalletConnect Project ID at <a href="https://cloud.walletconnect.com/app"><em>https://cloud.walletconnect.com/app</em></a>.</p><p>If you already have a project id, you can skip this step.</p><p>Go the <a href="https://cloud.walletconnect.com/app"><strong><em>WalletConnect Cloud Sign-In</em></strong></a><strong> </strong>to create a new account.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f914LLFN7JB_j2gdyzV6Gw.png" /><figcaption><a href="https://cloud.walletconnect.com/sign-in">https://cloud.walletconnect.com/sign-in</a></figcaption></figure><p>When in the dashboard, create a new project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vg99c-yEmLYI1Gj8IfHveg.png" /><figcaption>WalletConnect — Create Project</figcaption></figure><p>Enter the details of your project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7m8_w14M7m9bZpddWn1cNw.png" /><figcaption>WalletConnect — Project Details</figcaption></figure><p>Get your project id, we’ll need this for later.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qHpE9WMTTSv8M1H9x-TU_w.png" /><figcaption>WalletConnect Project ID</figcaption></figure><h4>Project Setup</h4><p>Now that we have our WalletConnect project id, let’s create a base expo project and add our needed dependencies.</p><pre> npx create-expo-app berachain-walletconnect-expo -t;<br><br># [Expected Prompt &amp; Output]:<br># ? Choose a template: › - Use arrow-keys. Return to submit.<br>#     Blank<br># ❯   Blank (TypeScript) - blank app with TypeScript enabled<br>#     Navigation (TypeScript)<br>#     Blank (Bare)<br># ...<br># ✅ Your project is ready!<br>#<br># To run your project, navigate to the directory and run one of the following npm commands.<br>#<br># - cd berachain-walletconnect-expo<br># - npm run android<br># - npm run ios<br># - npm run web</pre><p>Install our needed dependencies.</p><blockquote><strong>NOTE:</strong> We’ll be using pnpm which has been known to have some issues with expo. We also recommend using yarn with expo whenever possible.</blockquote><pre>cd berachain-walletconnect-expo;<br><br>pnpm add @web3modal/wagmi-react-native wagmi@1.4.13 viem@1.21.4 @react-native-async-storage/async-storage react-native-get-random-values react-native-svg react-native-modal @react-native-community/netinfo @walletconnect/react-native-compat expo-application expo-linking;<br># If in a monorepo / turbo repo, run the following right after in the dir<br># pnpm install --ignore-workspace;<br><br># @web3modal/wagmi-react-native - walletconnect react native web3modal<br># wagmi@1.4.13 - web3 react hooks (currently only supports v1)<br># viem@1.21.4 # js library for interacting with EVM (currently only support<br># @react-native-async-storage/async-storage - for local device storage<br># react-native-get-random-values - local random val generator<br># react-native-svg - native svg support<br># react-native-modal - modals support<br># @react-native-community/netinfo - api for network info for device<br># @walletconnect/react-native-compat - shims / polyfills for additional support<br># expo-application - for native app information<br># expo-linking - allows for external links to open the browser</pre><p>Let’s test that our app is working correctly — hint it won’t 😅.</p><blockquote><strong>Note:</strong> This is an issue specifically with pnpm. If you did everything with yarn then this won’t happen.</blockquote><pre># FROM: ./berachain-walletconnect-expo;<br><br>pnpm ios;<br><br># [Expected Output]:<br># </pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_6Y60takxUGveT3EJpGQLg.png" /><figcaption>Expo Not Working — Expected</figcaption></figure><p>⚠️ <strong>The PNPM Expo Fix</strong></p><p>If you’re using yarn you can still make these modifications and it’ll work for all package managers.</p><p>To fix this specific pnpm issue, we’ll need to make 3 modifications to our project.</p><p>1 — Install Babel Runtime Dependency</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>pnpm add -D @babel/runtime;</pre><p>2 — Modify Package JSON App Entry</p><p><strong>File:</strong> ./package.json</p><pre>{<br>  &quot;name&quot;: &quot;berachain-walletconnect-expo&quot;,<br>  &quot;version&quot;: &quot;1.0.0&quot;,<br>-  &quot;main&quot;: &quot;node_modules/expo/AppEntry.js&quot;,<br>+ &quot;main&quot;: &quot;index.ts&quot;,<br>  &quot;scripts&quot;: {<br>    &quot;start&quot;: &quot;expo start&quot;,<br>    &quot;android&quot;: &quot;expo start --android&quot;,<br>    &quot;ios&quot;: &quot;expo start --ios&quot;,<br>    &quot;web&quot;: &quot;expo start --web&quot;<br>  },<br>  &quot;dependencies&quot;: {<br>    &quot;@react-native-async-storage/async-storage&quot;: &quot;^1.22.1&quot;,<br>    &quot;@react-native-community/netinfo&quot;: &quot;^11.3.0&quot;,<br>    &quot;@walletconnect/react-native-compat&quot;: &quot;^2.11.1&quot;,<br>    &quot;@web3modal/wagmi-react-native&quot;: &quot;^1.2.0&quot;,<br>    &quot;expo&quot;: &quot;~50.0.7&quot;,<br>    &quot;expo-application&quot;: &quot;^5.8.3&quot;,<br>    &quot;expo-status-bar&quot;: &quot;~1.11.1&quot;,<br>    &quot;react&quot;: &quot;18.2.0&quot;,<br>    &quot;react-native&quot;: &quot;0.73.4&quot;,<br>    &quot;react-native-get-random-values&quot;: &quot;^1.10.0&quot;,<br>    &quot;react-native-modal&quot;: &quot;^13.0.1&quot;,<br>    &quot;react-native-svg&quot;: &quot;^14.1.0&quot;,<br>    &quot;viem&quot;: &quot;1.21.4&quot;,<br>    &quot;wagmi&quot;: &quot;1.4.13&quot;<br>  },<br>  &quot;devDependencies&quot;: {<br>    &quot;@babel/core&quot;: &quot;^7.20.0&quot;,<br>    &quot;@types/react&quot;: &quot;~18.2.45&quot;,<br>    &quot;typescript&quot;: &quot;^5.1.3&quot;<br>  },<br>  &quot;private&quot;: true<br>}</pre><p>3 — Add New Entry File &amp; Move Our App.tsx</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>touch index.ts;<br>mkdir app;<br>mv App.tsx app;</pre><p><strong>File:</strong> ./index.ts</p><pre>// Imports<br>// ========================================================<br>import { registerRootComponent } from &quot;expo&quot;;<br><br>// Main App<br>// ========================================================<br>import App from &quot;./app/App&quot;;<br><br>// registerRootComponent calls AppRegistry.registerComponent(&#39;main&#39;, () =&gt; App);<br>// It also ensures that whether you load the app in Expo Go or in a native build,<br>// the environment is set up appropriately<br>registerRootComponent(App);</pre><p>Let’s try this again, with pnpm ios and we should get the following working. This should also work with yarn or npm as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I-DYSWn5KLP32jYwJ377Dw.png" /><figcaption>Expo iOS Simulator Working After PNPM Fix</figcaption></figure><h4>Styling Our Project</h4><p>For styling, we’re going to use <a href="https://tailwindcss.com"><strong><em>Tailwind</em></strong></a> with <a href="https://www.nativewind.dev"><strong><em>Nativewind</em></strong></a> to keep that same consistent styling with all of our app.</p><p>These next few steps, are a few but once it’s setup, it’ll make sure that we have Tailwind configured correctly.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>pnpm add nativewind@^4.0.1 tailwindcss react-native-css-interop;<br>npx tailwindcss init;<br><br># Move our main App.tsx to an app directory for better tailwind changes<br>mkdir app;<br>mv App.tsx app/;</pre><p>Make the appropriate modification to our newly generated tailwind.config.js file.</p><p><strong>File:</strong> ./tailwind.config.js</p><pre>/** @type {import(&#39;tailwindcss&#39;).Config} */<br>module.exports = {<br>  // NOTE: Update this to include the paths to all of your component files.<br>  content: [<br>    &quot;./app/**/*.{js,jsx,ts,tsx}&quot;, <br>    &quot;./components/**/*.{js,jsx,ts,tsx}&quot;<br>  ],<br>  presets: [require(&quot;nativewind/preset&quot;)],<br>  theme: {<br>    extend: {},<br>  },<br>  plugins: [],<br>}</pre><p>Create a new global.css and add Tailwind styles.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>touch global.css;</pre><p><strong>File:</strong> ./global.css</p><pre>@tailwind base;<br>@tailwind components;<br>@tailwind utilities;<br><br>@layer base {<br>  .App {<br>    @apply bg-[#F47226] text-[#2E1E1A] flex w-full h-full items-center justify-center;<br>  }<br><br>  .H1 {<br>    @apply text-lg mb-2 block font-semibold text-[#121312] text-center;<br>  }<br><br>  .Text {<br>    @apply mb-2 text-[#2E1E1A] block;<br>  }<br><br>  .TextInput {<br>    @apply bg-white text-base h-12 px-2 align-text-top rounded w-full mb-4;<br>  }<br><br>  .TextError {<br>    @apply text-red-800 bg-red-200 mb-2 text-base p-4;<br>  }<br><br>  .Button {<br>    @apply bg-[#2E1E1A] h-12 flex items-center justify-center rounded-lg mb-4 w-full;<br>  }<br><br>  .Code {<br>    @apply block bg-[#ff843d] whitespace-nowrap overflow-scroll mb-4 text-[#874c2a] text-base h-12 leading-[3rem] px-2 rounded w-full;<br>  }<br>  <br>  .Connect {<br>    @apply my-4 block;<br>  }<br><br>  .Balance {<br>    @apply block w-full px-8;<br>  }<br><br>  .SignMessage {<br>    @apply block w-full px-8;<br>  }<br><br>  .Deploy {<br>    @apply block w-full px-8;<br>  }<br>}</pre><p>Modify our babel.config.js to support nativewind.</p><p><strong>File:</strong> ./babel.config.js</p><pre>module.exports = (api) =&gt; {<br>  api.cache(true);<br>  return {<br>    presets: [<br>      [&quot;babel-preset-expo&quot;, { jsxImportSource: &quot;nativewind&quot; }],<br>      &quot;nativewind/babel&quot;,<br>    ],<br>  };<br>};</pre><p>Generate a new metro.config.js file which will help with build.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>npx expo customize metro.config.js;</pre><p>Make some modifications to that config file.</p><p><strong>File:</strong> ./metro.config.js</p><pre>// Learn more https://docs.expo.io/guides/customizing-metro<br>const { getDefaultConfig } = require(&#39;expo/metro-config&#39;);<br>+ const { withNativeWind } = require(&#39;nativewind/metro&#39;);<br><br>/** @type {import(&#39;expo/metro-config&#39;).MetroConfig} */<br>- const config = getDefaultConfig(__dirname);<br>+ const config = getDefaultConfig(__dirname, { isCSSEnabled: true })<br><br>- module.exports = config;<br>+ module.exports = withNativeWind(config, { input: &#39;./global.css&#39; });</pre><p>Let’s modify our main App.tsx and replace everything with the following code.</p><p><strong>File:</strong> ./app/App.tsx</p><pre>// Imports<br>// ========================================================<br>import { StatusBar } from &#39;expo-status-bar&#39;;<br>import { Text, View } from &#39;react-native&#39;;<br>import &#39;../global.css&#39;;<br><br>// Main App Component<br>// ========================================================<br>export default function App() {<br>  return (<br>    &lt;View className=&quot;App&quot;&gt;<br>      &lt;StatusBar style=&quot;auto&quot; /&gt;<br>      &lt;Text className=&quot;H1&quot;&gt;Berachain WalletConnect Expo Example&lt;/Text&gt;<br>      &lt;Text className=&quot;Text&quot;&gt;Demonstrating how to build mobile dApps&lt;/Text&gt;<br>    &lt;/View&gt;<br>  );<br>};</pre><p>Run our app again and we should see the following.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CugFT4gkbKNyH9LZ9D9veA.png" /><figcaption>Berachain Mobile iOS dApp Working With Tailwind</figcaption></figure><p>Let’s also add an SVG logo to our app as a separate component.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>mkdir components;<br>mkdir components/Icons;<br>touch components/Icons/index.tsx;</pre><p>Add the following code to our new icons file.</p><p><strong>File:</strong> ./components/Icons/index.tsx</p><pre>// Imports<br>// ========================================================<br>import Svg, { Path } from &quot;react-native-svg&quot;;<br><br>// Icons<br>// ========================================================<br>export const Logo = ({ className = &#39;&#39;, width = 128, height = 128  }) =&gt; &lt;Svg className={className} width={width} height={height} viewBox=&quot;0 0 1024 1024&quot; fill=&quot;none&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;<br>  &lt;Path d=&quot;M477.298 474.489C476.946 472.605 476.506 470.802 475.801 469.084C476.066 468.675 534.55 392.929 480.381 346.092C426.299 299.252 363.145 360.339 362.793 360.667C352.751 357.718 342.622 355.918 332.581 355.099C332.581 355.099 332.581 355.099 332.493 355.099C311.882 351.906 282.99 355.099 282.99 355.099C273.037 355.918 262.996 357.718 253.042 360.585C252.69 360.258 189.536 299.17 135.454 346.01C81.3722 392.847 139.77 468.675 140.034 469.002C139.418 470.802 138.889 472.605 138.537 474.407C132.724 506.833 92.8228 516.823 92.8228 573.325C92.8228 630.891 134.485 676.256 219.572 676.256H254.452C254.628 676.42 268.106 694.27 295.938 695.335C295.938 695.335 302.369 695.99 317.165 695.499C346.673 695.499 361.031 676.583 361.12 676.338H395.999C481.085 676.338 522.748 630.972 522.748 573.407C523.012 516.987 483.111 506.915 477.298 474.489Z&quot; fill=&quot;#F9F4D5&quot; /&gt;<br>  &lt;Path d=&quot;M692.83 628.84V584.622C720.048 575.287 739.867 546.3 739.867 511.99C739.867 477.679 720.048 448.61 692.83 439.355V395.137C718.463 386.376 737.577 360.092 739.69 328.319H708.51V341.83C708.51 352.229 702.167 361.4 692.83 365.986V359.682C692.83 350.429 684.726 342.894 674.773 342.894H673.188C663.234 342.894 655.133 350.429 655.133 359.682V365.986C645.796 361.4 639.453 352.311 639.453 341.83V328.319H608.272C610.386 360.092 629.5 386.376 655.133 395.137V439.355C628.003 448.61 608.096 477.598 608.096 511.99C608.096 546.382 627.915 575.369 655.133 584.622V628.84C629.5 637.602 610.386 663.888 608.272 695.658H639.453V682.148C639.453 671.748 645.796 662.577 655.133 657.992V664.297C655.133 673.55 663.234 681.083 673.188 681.083H674.773C684.726 681.083 692.83 673.55 692.83 664.297V657.992C702.167 662.577 708.51 671.666 708.51 682.148V695.658H739.69C737.577 663.888 718.463 637.602 692.83 628.84ZM639.453 531.315V492.583C639.453 482.183 645.796 473.012 655.133 468.427V474.73C655.133 483.983 663.234 491.518 673.188 491.518H674.773C684.726 491.518 692.83 483.983 692.83 474.73V468.427C702.167 473.012 708.51 482.101 708.51 492.583V531.315C708.51 541.714 702.167 550.885 692.83 555.471V549.165C692.83 539.912 684.726 532.38 674.773 532.38H673.188C663.234 532.38 655.133 539.912 655.133 549.165V555.471C645.884 550.885 639.453 541.796 639.453 531.315Z&quot; fill=&quot;#F9F4D5&quot; /&gt;<br>  &lt;Path d=&quot;M884.142 535.49V488.407C911.358 479.07 931.176 450.083 931.176 415.773C931.176 381.462 911.358 352.393 884.142 343.14V328.319H846.53V343.14C819.315 352.475 799.496 381.462 799.496 415.773C799.496 450.083 819.315 479.152 846.53 488.407V535.49C819.315 544.825 799.496 573.813 799.496 608.123C799.496 642.433 819.315 671.502 846.53 680.755V695.576H884.142V680.755C911.358 671.42 931.176 642.433 931.176 608.123C931.176 573.813 911.358 544.825 884.142 535.49ZM830.765 435.097V396.366C830.765 385.966 837.106 376.795 846.442 372.21V378.515C846.442 387.768 854.546 395.301 864.5 395.301H865.997C875.95 395.301 884.054 387.768 884.054 378.515V372.21C893.391 376.795 899.731 385.884 899.731 396.366V435.097C899.731 445.497 893.391 454.668 884.054 459.254V452.95C884.054 443.697 875.95 436.162 865.997 436.162H864.412C854.458 436.162 846.354 443.697 846.354 452.95V459.254C837.194 454.668 830.765 445.579 830.765 435.097ZM899.819 627.53C899.819 637.929 893.479 647.1 884.142 651.686V642.433C884.142 633.18 876.038 625.648 866.085 625.648H864.588C854.634 625.648 846.53 633.18 846.53 642.433V651.686C837.194 647.1 830.853 638.011 830.853 627.53V588.798C830.853 578.398 837.194 569.227 846.53 564.642V567.998C846.53 577.253 854.634 584.786 864.588 584.786H866.173C876.126 584.786 884.23 577.253 884.23 567.998V564.642C893.567 569.227 899.907 578.316 899.907 588.798V627.53H899.819Z&quot; fill=&quot;#F9F4D5&quot; /&gt;<br>&lt;/Svg&gt;</pre><p>Next we just need to modify our main App.tsx file to include the logo.</p><p><strong>File:</strong> ./app/App.tsx</p><pre>// Imports<br>// ========================================================<br>import { StatusBar } from &#39;expo-status-bar&#39;;<br>import { Text, View } from &#39;react-native&#39;;<br>import &#39;../global.css&#39;;<br>+ import { Logo } from &#39;../components/Icons&#39;;<br><br>// Main App Component<br>// ========================================================<br>export default function App() {<br>  return (<br>    &lt;View className=&quot;App&quot;&gt;<br>      &lt;StatusBar style=&quot;auto&quot; /&gt;<br>+       &lt;Logo className=&quot;w-auto h-10 mx-auto&quot; /&gt;<br>      &lt;Text className=&quot;H1&quot;&gt;Berachain WalletConnect Expo Example&lt;/Text&gt;<br>      &lt;Text className=&quot;Text&quot;&gt;Demonstrating how to build mobile dApps&lt;/Text&gt;<br>    &lt;/View&gt;<br>  );<br>};</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LGEY-4XgDEgxhIolA1SJyw.png" /><figcaption>Berachain Mobile Web3 Expo App With Berachain Logo</figcaption></figure><h4>WalletConnect Web3Modal</h4><p>The next step we’re going to do is add WalletConnect’s Web3Modal support into our Expo app.</p><p>The first step will be to configure our environment variables.</p><blockquote><strong>Note:</strong> Make sure not to commit this to our git repository</blockquote><pre># FROM: ./berachain-walletconnect-expo;<br><br>touch .env;</pre><p>Add the following contents and remember to replace the WALLET_CONNECT_PROJECT_ID with the newly created project from WalletConnect Cloud directly.</p><p><strong>File:</strong> ./.env</p><pre># Expo Metadata<br>EXPO_PUBLIC_METADATA_NAME=&quot;Berachain WalletConnect Expo&quot;<br>EXPO_PUBLIC_METADATA_DESCRIPTION=&quot;Berachain WalletConnect Expo Example&quot;<br>EXPO_PUBLIC_METADATA_URL=&quot;https://berachain.com&quot;<br>EXPO_PUBLIC_METADATA_ICONS=&quot;https://avatars.githubusercontent.com/u/96059542&quot;<br>EXPO_PUBLIC_METADATA_REDIRECT_NAME=&quot;YOUR_APP_SCHEME://&quot;<br>EXPO_PUBLIC_METADATA_REDIRECT_UNIVERSAL=&quot;YOUR_APP_UNIVERSAL_LINK.com&quot;<br><br># WalletConnect - See https://cloud.walletconnect.com<br>EXPO_PUBLIC_WALLET_CONNECT_PROJECT_ID=&quot;YOUR_PROJECT_ID&quot;<br><br># Chain<br>EXPO_PUBLIC_CHAIN_ID=80085<br>EXPO_PUBLIC_CHAIN_NAME=&quot;berachainTestnet&quot;<br>EXPO_PUBLIC_CHAIN_NETWORK=&quot;Berachain&quot;<br>EXPO_PUBLIC_CHAIN_NATIVECURRENCY_DECIMALS=18<br>EXPO_PUBLIC_CHAIN_NATIVECURRENCY_NAME=&quot;Bera Token&quot;<br>EXPO_PUBLIC_CHAIN_NATIVECURRENCY_SYMBOL=&quot;BERA&quot;<br>EXPO_PUBLIC_CHAIN_RPC_URL=&quot;https://rpc.ankr.com/berachain_testnet&quot;<br>EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_NAME=&quot;Beratrail&quot;<br>EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_URL=&quot;https://artio.beratrail.io&quot;</pre><p>Now that we have this setup, we’re going wrap our main App.tsx with some of the configured providers from WalletConnect that adds its own configuration to the wagmi library.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>mkdir providers;<br>touch providers/index.tsx;<br>touch providers/wagmi.tsx;</pre><p>First up is configuring our wagmi provider. This will ensure that we can take advantage of all of wagmi’s hooks but also some default functionality built into WalletConnet Web3Modal.</p><p><strong>File:</strong> ./providers/wagmi.tsx</p><pre>// Imports<br>// ========================================================<br>import &quot;@walletconnect/react-native-compat&quot;;<br>import {<br>  createWeb3Modal,<br>  defaultWagmiConfig,<br>} from &quot;@web3modal/wagmi-react-native&quot;;<br>import { defineChain } from &quot;viem&quot;;<br>import { WagmiConfig } from &quot;wagmi&quot;;<br><br>// Config<br>// ========================================================<br>// 1. Get projectId at https://cloud.walletconnect.com<br>const projectId = `${process.env.EXPO_PUBLIC_WALLET_CONNECT_PROJECT_ID}`;<br><br>if (!projectId) throw Error(&#39;Error: Missing `EXPO_PUBLIC_WALLET_CONNECT_PROJECT_ID`.&#39;);<br><br>// 2. Create config for our app - defined by our env vars<br>const metadata = {<br>  name: `${process.env.EXPO_PUBLIC_METADATA_NAME}`,<br>  description: `${process.env.EXPO_PUBLIC_METADATA_DESCRIPTION}`,<br>  url: `${process.env.EXPO_PUBLIC_METADATA_URL}`,<br>  icons: [`${process.env.EXPO_PUBLIC_METADATA_ICONS}`],<br>  redirect: {<br>    native: `${process.env.EXPO_PUBLIC_METADATA_REDIRECT_NATIVE}`,<br>    universal: `${process.env.EXPO_PUBLIC_METADATA_REDIRECT_UNIVERSAL}`,<br>  },<br>};<br><br>// 3. Configure our custom chain - Note this is needed for wagmi and viem v1<br>/**<br> * @dev Custom chain configuration<br> */<br>const chainConfiguration = defineChain({<br> id: parseInt(`${process.env.EXPO_PUBLIC_CHAIN_ID}`),<br> name:`${process.env.EXPO_PUBLIC_CHAIN_NAME}`,<br> network: `${process.env.EXPO_PUBLIC_CHAIN_NETWORK}`,<br> nativeCurrency: {<br>  decimals: parseInt(`${process.env.EXPO_PUBLIC_CHAIN_NATIVECURRENCY_DECIMALS}`),<br>  name: `${process.env.EXPO_PUBLIC_CHAIN_NATIVECURRENCY_NAME}`,<br>  symbol:`${process.env.EXPO_PUBLIC_CHAIN_NATIVECURRENCY_SYMBOL}`,<br> },<br> rpcUrls: {<br>  default: {<br>   http: [`${process.env.EXPO_PUBLIC_CHAIN_RPC_URL}`],<br>  },<br>  public: {<br>   http: [`${process.env.EXPO_PUBLIC_CHAIN_RPC_URL}`],<br>  },<br> },<br> blockExplorers: {<br>  default: { <br>      name: `${process.env.EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_NAME}`,<br>      url: `${process.env.EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_URL}` <br>    },<br> },<br>});<br><br>/**<br> * @dev supported chains<br> */<br>const chains = [chainConfiguration];<br><br>/**<br> * <br> */<br>const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata });<br><br>// 4. Create modal configuration<br>createWeb3Modal({<br>  projectId,<br>  chains,<br>  wagmiConfig,<br>});<br><br>// Provider<br>// ========================================================<br>export default function Wagmi({ children }: { children: React.ReactNode }) {<br>  return &lt;WagmiConfig config={wagmiConfig}&gt;{children}&lt;/WagmiConfig&gt;<br>};</pre><p>Next, to keep things clean, we’ll modify our main provider file that will wrap our entire application. This helps to keep our App.tsx file clean, and allows us to add more providers easily with our main provider wrapper.</p><p><strong>File:</strong> ./providers/index.tsx</p><pre>// Imports<br>// ========================================================<br>import WagmiProvider from &quot;./wagmi&quot;;<br><br>// Root Provider<br>// ========================================================<br>export default function RootProvider({ children }: { children: React.ReactNode }) {<br>  return &lt;WagmiProvider&gt;{children}&lt;/WagmiProvider&gt;<br>};</pre><p>Then we’ll want this root provider to wrap our App.tsx and we’ll also add our web3modal button.</p><p><strong>File:</strong> ./app/App.tsx</p><pre>// Imports<br>// ========================================================<br>import { StatusBar } from &quot;expo-status-bar&quot;;<br>import { Text, View } from &quot;react-native&quot;;<br>import &quot;../global.css&quot;;<br>import { Logo } from &quot;../components/Icons&quot;;<br>+ import RootProvider from &quot;../providers&quot;;<br>+ import { Web3Modal } from &quot;@web3modal/wagmi-react-native&quot;;<br><br>// Main App Component<br>// ========================================================<br>export default function App() {<br> return (<br>+   &lt;RootProvider&gt;<br>   &lt;View className=&quot;App&quot;&gt;<br>    &lt;StatusBar style=&quot;auto&quot; /&gt;<br>+     &lt;Web3Modal /&gt;<br>    &lt;Logo className=&quot;w-auto h-10 mx-auto&quot; /&gt;<br>    &lt;Text className=&quot;H1&quot;&gt;Berachain WalletConnect Expo Example&lt;/Text&gt;<br>    &lt;Text className=&quot;Text&quot;&gt;Demonstrating how to build mobile dApps&lt;/Text&gt;<br>   &lt;/View&gt;<br>+   &lt;/RootProvider&gt;<br> );<br>}</pre><h4>Connect Button</h4><p>Now that the app fully supports WalletConnect, we can now add support for a connect button. We’re going to put this in a separate file to make it easier for us to modify by itself.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>mkdir components/Connect;<br>touch components/Connect/index.tsx;</pre><p><strong>File:</strong> ./components/Connect/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { W3mButton } from &quot;@web3modal/wagmi-react-native&quot;;<br>import { View } from &quot;react-native&quot;;<br><br>// Component<br>// ========================================================<br>export default function Connect() {<br> return (<br>  &lt;View className=&quot;Connect&quot;&gt;<br>   {/* Customizing the web3modal button requires passing it certain props */}<br>   &lt;W3mButton<br>    connectStyle={{<br>     backgroundColor: &quot;#2E1E1A&quot;,<br>    }}<br>    accountStyle={{<br>     backgroundColor: &quot;#2E1E1A&quot;,<br>    }}<br>   /&gt;<br>  &lt;/View&gt;<br> );<br>}</pre><p>Let’s add this button to our App.tsx file.</p><pre>// Imports<br>// ========================================================<br>import { StatusBar } from &quot;expo-status-bar&quot;;<br>import { Text, View } from &quot;react-native&quot;;<br>import &quot;../global.css&quot;;<br>import { Logo } from &quot;../components/Icons&quot;;<br>+ import Connect from &quot;../components/Connect&quot;;<br>import RootProvider from &quot;../providers&quot;;<br>import { Web3Modal } from &quot;@web3modal/wagmi-react-native&quot;;<br><br><br>// Main App Component<br>// ========================================================<br>export default function App() {<br> return (<br>  &lt;RootProvider&gt;<br>   &lt;View className=&quot;App&quot;&gt;<br>    &lt;StatusBar style=&quot;auto&quot; /&gt;<br>        &lt;Web3Modal /&gt;<br>    &lt;Logo className=&quot;w-auto h-10 mx-auto&quot; /&gt;<br>    &lt;Text className=&quot;H1&quot;&gt;Berachain WalletConnect Expo Example&lt;/Text&gt;<br>    &lt;Text className=&quot;Text&quot;&gt;Demonstrating how to build mobile dApps&lt;/Text&gt;<br>+     &lt;Connect /&gt;<br>   &lt;/View&gt;<br>  &lt;/RootProvider&gt;<br> );<br>}</pre><p>We should now have a our connect button supported, but you’ll quickly notice there isn’t a way to connect with our wallet on our mobile because MetaMask isn’t installed on Simulator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qIHqpfVTMBSi4Iq31RNE0A.png" /><figcaption>Berachain WalletConnect Web3Modal Working</figcaption></figure><p>When you click the <strong><em>Connect</em></strong> button, we’ll see that there are zero wallets available.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2o5Qp47Oycxj8gX7StuaKA.png" /><figcaption>Berachain WalletConnect Web3Modal Cannot Connect To Wallet On Simulator</figcaption></figure><p>This is the part where we can take advantage of <a href="https://expo.dev/expo-go"><strong><em>Expo Go</em></strong></a> on our phone to connect with our existing wallets.</p><blockquote><strong>Note:</strong> Make sure that you have MetaMask Mobile installed on your phone and <a href="https://docs.berachain.com/developers/"><strong>configure your MetaMask mobile to have a new network with Berachain</strong></a>.</blockquote><p>If you notice when we run pnpm ios a QR code shows up. We should be able to use that with the <strong><em>Expo Go app</em></strong> to load the app directly into our phone.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-4QEz6MKbGhth38zFLna5A.png" /><figcaption>Warp Terminal Running Expo With QR Code</figcaption></figure><p>Now when we use our iPhone’s camera, we can connect and open up our app in Expo Go.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_TsWVHCZyWB1d43MR_siTg.png" /><figcaption>Connecting To Expo Go On With Your iPhone</figcaption></figure><p>When the app loads on our phone, we should be able to tap <strong><em>Connect</em></strong>, be presented with <strong><em>MetaMask</em></strong> as an option, select <strong><em>MetaMask</em></strong>, confirm our connection via the <strong><em>MetaMask</em></strong> app, and then be pushed back to the app to confirm that we’re connected.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rvgJSgtipb1Lt3E4hIjlsQ.jpeg" /><figcaption>Berachain Expo WalletConnect Successfully Connecting To MetaMask</figcaption></figure><h4>Display $BERA Balance</h4><p>Now that we have successfully connected our account with MetaMask, let’s add a basic component that displays our current balance on Berachain.</p><blockquote><strong>Note:</strong> Make sure to switch your MetaMask wallet to the Berachain Artio Testnet Network before hand.</blockquote><p>Our component will check if the account is already connected and the make a request to show the current balance.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>mkdir components/Balance;<br>touch components/Balance/index.tsx;</pre><p>Let’s add the following code for our new Balance component.</p><p><strong>File:</strong> ./components/Balance/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { View, Text } from &quot;react-native&quot;;<br>import { useAccount, useBalance } from &quot;wagmi&quot;;<br><br>// Component<br>// ========================================================<br>export default function Balance() {<br>  // Hooks<br>  const { isConnected, address } = useAccount();<br>  const { data, isError, isLoading } = useBalance({<br>    address<br>  });<br><br>  // Return<br>  /**<br>   * If still loading, show a loading state<br>   */<br>  if (isLoading)<br>    return &lt;Text className=&quot;Text&quot;&gt;Fetching balance...&lt;/Text&gt;;<br><br><br>  /**<br>   * Show error if having a problem fetching the balance<br>   */<br>  if (isError)<br>    return (<br>      &lt;Text className=&quot;mText&quot;&gt;Error fetching balance&lt;/Text&gt;<br>    );<br><br>  /**<br>   * If not connected don&#39;t show anything<br>   */<br>  if (!isConnected) return null;<br><br><br>  /**<br>   * Successfully connected<br>   */<br>  return (<br>    &lt;View className=&quot;Balance&quot;&gt;<br>      &lt;Text className=&quot;Text&quot;&gt;Balance&lt;/Text&gt;<br>      &lt;Text className=&quot;Code&quot;&gt;{(parseInt((data?.value ?? &#39;&#39;).toString()) / 1000000000000000000).toFixed(2)} $BERA&lt;/Text&gt;<br>    &lt;/View&gt;<br>  );<br>}</pre><p>Then we’ll add the Balance component to our App.tsx.</p><p><strong>File:</strong> ./app/App.tsx</p><pre>// Imports<br>// ========================================================<br>import { StatusBar } from &quot;expo-status-bar&quot;;<br>import { Text, View } from &quot;react-native&quot;;<br>import &quot;../global.css&quot;;<br>import { Logo } from &quot;../components/Icons&quot;;<br>import Connect from &quot;../components/Connect&quot;;<br>+ import Balance from &quot;../components/Balance&quot;;<br>import RootProvider from &quot;../providers&quot;;<br>import { Web3Modal } from &quot;@web3modal/wagmi-react-native&quot;;<br><br>// Main App Component<br>// ========================================================<br>export default function App() {<br> return (<br>  &lt;RootProvider&gt;<br>   &lt;View className=&quot;App&quot;&gt;<br>    &lt;StatusBar style=&quot;auto&quot; /&gt;<br>        &lt;Web3Modal /&gt;<br>    &lt;Logo className=&quot;w-auto h-10 mx-auto&quot; /&gt;<br>    &lt;Text className=&quot;H1&quot;&gt;Berachain WalletConnect Expo Example&lt;/Text&gt;<br>    &lt;Text className=&quot;Text&quot;&gt;Demonstrating how to build mobile dApps&lt;/Text&gt;<br>    &lt;Connect /&gt;<br>+     &lt;Balance /&gt;<br>   &lt;/View&gt;<br>  &lt;/RootProvider&gt;<br> );<br>}</pre><p>Now when we connect with MetaMask, we should see our $BERA balance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*L2yseYuEUWVhiLkWXlelRQ.jpeg" /><figcaption>Berachain WalletConnect Expo Showing $BERA Balance Functionality</figcaption></figure><h4>Wallet Sign Message</h4><p>On top of every basic RPC request, we also want to get some basic wallet interactions going. The simplest version would be signing a message with your MetaMask wallet and getting a signature.</p><p>We’re going to build an input that accepts a message, prompts the user’s MetaMask wallet to sign the message, and output the signature.</p><p>We’ll first create the file.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>mkdir components/SignMessage;<br>touch components/SignMessage/index.tsx;</pre><p>Add the functionality to it.</p><p><strong>File:</strong> ./components/SignMessage/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { useState } from &quot;react&quot;;<br>import { View, Pressable, Text, TextInput } from &quot;react-native&quot;;<br>import { useAccount, useSignMessage } from &quot;wagmi&quot;;<br><br>// Component<br>// ========================================================<br>export default function SignMessage() {<br> // Hooks<br> const [message, setMessage] = useState(&quot;&quot;);<br> const [signature, setSignature] = useState(&quot;(Signature will show up here)&quot;);<br> const [error, setError] = useState(&quot;&quot;);<br> const { isConnected, address } = useAccount();<br> const { signMessageAsync } = useSignMessage();<br><br> // Functions<br>  /**<br>   * @dev Handles signing message<br>   */<br> const onPressSignMessage = async () =&gt; {<br>  console.group(&quot;onPressSignMessage&quot;);<br>  setError(&quot;&quot;);<br><br>  try {<br>   const signature = await signMessageAsync({<br>    message,<br>   });<br>   setSignature(signature);<br>  } catch (error: unknown) {<br>   console.error({ error });<br>   setError(&quot;Error signing message.&quot;);<br>  }<br>  console.groupEnd();<br> };<br><br> // Return<br>  /**<br>   * If not connected and no address, then don&#39;t show anything<br>   */<br> if (!isConnected || !address) return null;<br><br> return (<br>  &lt;View className=&quot;SignMessage&quot;&gt;<br>   &lt;Text className=&quot;Text&quot;&gt;Sign Message&lt;/Text&gt;<br>   &lt;TextInput<br>    className=&quot;TextInput&quot;<br>    placeholder=&quot;Message to sign&quot;<br>    onChangeText={setMessage}<br>    value={message}<br>   /&gt;<br>   &lt;Text className=&quot;Text&quot;&gt;Signature Generated&lt;/Text&gt;<br>   &lt;Text<br>    className=&quot;Code&quot;<br>   &gt;{signature}&lt;/Text&gt;<br>   &lt;Pressable<br>    className=&quot;Button&quot;<br>    onPress={onPressSignMessage}<br>   &gt;<br>    &lt;Text className=&quot;text-white text-base&quot;&gt;Sign Message&lt;/Text&gt;<br>   &lt;/Pressable&gt;<br><br>   {error ? (<br>    &lt;Text className=&quot;TextError&quot;&gt;<br>     {error}<br>    &lt;/Text&gt;<br>   ) : null}<br>  &lt;/View&gt;<br> );<br>}</pre><p>We’ll add the Sign Message functionality to our App.tsx.</p><p><strong>File:</strong> ./app/App.tsx</p><pre>// Imports<br>// ========================================================<br>import { StatusBar } from &quot;expo-status-bar&quot;;<br>import { Text, View } from &quot;react-native&quot;;<br>import &quot;../global.css&quot;;<br>import { Logo } from &quot;../components/Icons&quot;;<br>import Connect from &quot;../components/Connect&quot;;<br>import Balance from &quot;../components/Balance&quot;;<br>+ import SignMessage from &quot;../components/SignMessage&quot;;<br>import RootProvider from &quot;../providers&quot;;<br>import { Web3Modal } from &quot;@web3modal/wagmi-react-native&quot;;<br><br>// Main App Component<br>// ========================================================<br>export default function App() {<br> return (<br>  &lt;RootProvider&gt;<br>   &lt;View className=&quot;App&quot;&gt;<br>    &lt;StatusBar style=&quot;auto&quot; /&gt;<br>        &lt;Web3Modal /&gt;<br>    &lt;Logo className=&quot;w-auto h-10 mx-auto&quot; /&gt;<br>    &lt;Text className=&quot;H1&quot;&gt;Berachain WalletConnect Expo Example&lt;/Text&gt;<br>    &lt;Text className=&quot;Text&quot;&gt;Demonstrating how to build mobile dApps&lt;/Text&gt;<br>    &lt;Connect /&gt;<br>    &lt;Balance /&gt;<br>+     &lt;SignMessage /&gt;<br>   &lt;/View&gt;<br>  &lt;/RootProvider&gt;<br> );<br>}</pre><p>We should now see the functionality of inputting a message, prompting our wallet to sign the message, and seeing the generated signature.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tNMgE-k0qWAdQ_hjgQ68jg.jpeg" /><figcaption>Berachain WalletConnect Wallet Signature Working With Ooga Booga</figcaption></figure><h4>Deploy Contract</h4><p>The last piece of functionality I want to add to this app is the ability to deploy a contract’s bytecode directly from the mobile app itself. This would essentially prompt our wallet to initiate a transaction that would deploy a contract to Berachain.</p><p>To start, let’s create a new Deploy component.</p><pre># FROM: ./berachain-walletconnect-expo;<br><br>mkdir components/Deploy;<br>touch components/Deploy/index.tsx;</pre><p>We’ll add the following functionality to our Deploy component.</p><p><strong>File:</strong> ./components/Deploy/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { useState } from &quot;react&quot;;<br>import { Pressable, Text } from &quot;react-native&quot;;<br>import { useAccount, useWaitForTransaction } from &quot;wagmi&quot;;<br>import { encodeAbiParameters } from &quot;viem&quot;;<br>import { openURL } from &quot;expo-linking&quot;;<br><br>// Constants<br>// ========================================================<br>/**<br> * @dev ByteCode for HelloWorld Contract see https://github.com/berachain/guides/blob/main/apps/hardhat-viem-helloworld/contracts/HelloWorld.sol<br> */<br>const CONTRACT_BYTECODE =<br>  &quot;0x60806040523480156200001157600080fd5b5060405162000da238038062000da283398181016040528101906200003791906200021e565b8060009081620000489190620004ba565b507fcbc299eeb7a1a982d3674880645107c4fe48c3227163794e48540a752272235433826040516200007c92919062000638565b60405180910390a1506200066c565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620000f482620000a9565b810181811067ffffffffffffffff82111715620001165762000115620000ba565b5b80604052505050565b60006200012b6200008b565b9050620001398282620000e9565b919050565b600067ffffffffffffffff8211156200015c576200015b620000ba565b5b6200016782620000a9565b9050602081019050919050565b60005b838110156200019457808201518184015260208101905062000177565b60008484015250505050565b6000620001b7620001b1846200013e565b6200011f565b905082815260208101848484011115620001d657620001d5620000a4565b5b620001e384828562000174565b509392505050565b600082601f8301126200020357620002026200009f565b5b815162000215848260208601620001a0565b91505092915050565b60006020828403121562000237576200023662000095565b5b600082015167ffffffffffffffff8111156200025857620002576200009a565b5b6200026684828501620001eb565b91505092915050565b600081519050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620002c257607f821691505b602082108103620002d857620002d76200027a565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b600060088302620003427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000303565b6200034e868362000303565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006200039b620003956200038f8462000366565b62000370565b62000366565b9050919050565b6000819050919050565b620003b7836200037a565b620003cf620003c682620003a2565b84845462000310565b825550505050565b600090565b620003e6620003d7565b620003f3818484620003ac565b505050565b5b818110156200041b576200040f600082620003dc565b600181019050620003f9565b5050565b601f8211156200046a576200043481620002de565b6200043f84620002f3565b810160208510156200044f578190505b620004676200045e85620002f3565b830182620003f8565b50505b505050565b600082821c905092915050565b60006200048f600019846008026200046f565b1980831691505092915050565b6000620004aa83836200047c565b9150826002028217905092915050565b620004c5826200026f565b67ffffffffffffffff811115620004e157620004e0620000ba565b5b620004ed8254620002a9565b620004fa8282856200041f565b600060209050601f8311600181146200053257600084156200051d578287015190505b6200052985826200049c565b86555062000599565b601f1984166200054286620002de565b60005b828110156200056c5784890151825560018201915060208501945060208101905062000545565b868310156200058c578489015162000588601f8916826200047c565b8355505b6001600288020188555050505b505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620005ce82620005a1565b9050919050565b620005e081620005c1565b82525050565b600082825260208201905092915050565b600062000604826200026f565b620006108185620005e6565b93506200062281856020860162000174565b6200062d81620000a9565b840191505092915050565b60006040820190506200064f6000830185620005d5565b8181036020830152620006638184620005f7565b90509392505050565b610726806200067c6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063fe50cc7214610057575b600080fd5b610055600480360381019061005091906102ad565b610075565b005b61005f6100c1565b60405161006c9190610375565b60405180910390f35b806000908161008491906105ad565b507fcbc299eeb7a1a982d3674880645107c4fe48c3227163794e48540a752272235433826040516100b69291906106c0565b60405180910390a150565b6060600080546100d0906103c6565b80601f01602080910402602001604051908101604052809291908181526020018280546100fc906103c6565b80156101495780601f1061011e57610100808354040283529160200191610149565b820191906000526020600020905b81548152906001019060200180831161012c57829003601f168201915b5050505050905090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6101ba82610171565b810181811067ffffffffffffffff821117156101d9576101d8610182565b5b80604052505050565b60006101ec610153565b90506101f882826101b1565b919050565b600067ffffffffffffffff82111561021857610217610182565b5b61022182610171565b9050602081019050919050565b82818337600083830152505050565b600061025061024b846101fd565b6101e2565b90508281526020810184848401111561026c5761026b61016c565b5b61027784828561022e565b509392505050565b600082601f83011261029457610293610167565b5b81356102a484826020860161023d565b91505092915050565b6000602082840312156102c3576102c261015d565b5b600082013567ffffffffffffffff8111156102e1576102e0610162565b5b6102ed8482850161027f565b91505092915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610330578082015181840152602081019050610315565b60008484015250505050565b6000610347826102f6565b6103518185610301565b9350610361818560208601610312565b61036a81610171565b840191505092915050565b6000602082019050818103600083015261038f818461033c565b905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806103de57607f821691505b6020821081036103f1576103f0610397565b5b50919050565b60008190508160005260206000209050919050565b60006020601f8301049050919050565b600082821b905092915050565b6000600883026104597fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8261041c565b610463868361041c565b95508019841693508086168417925050509392505050565b6000819050919050565b6000819050919050565b60006104aa6104a56104a08461047b565b610485565b61047b565b9050919050565b6000819050919050565b6104c48361048f565b6104d86104d0826104b1565b848454610429565b825550505050565b600090565b6104ed6104e0565b6104f88184846104bb565b505050565b5b8181101561051c576105116000826104e5565b6001810190506104fe565b5050565b601f82111561056157610532816103f7565b61053b8461040c565b8101602085101561054a578190505b61055e6105568561040c565b8301826104fd565b50505b505050565b600082821c905092915050565b600061058460001984600802610566565b1980831691505092915050565b600061059d8383610573565b9150826002028217905092915050565b6105b6826102f6565b67ffffffffffffffff8111156105cf576105ce610182565b5b6105d982546103c6565b6105e4828285610520565b600060209050601f8311600181146106175760008415610605578287015190505b61060f8582610591565b865550610677565b601f198416610625866103f7565b60005b8281101561064d57848901518255600182019150602085019450602081019050610628565b8683101561066a5784890151610666601f891682610573565b8355505b6001600288020188555050505b505050505050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006106aa8261067f565b9050919050565b6106ba8161069f565b82525050565b60006040820190506106d560008301856106b1565b81810360208301526106e7818461033c565b9050939250505056fea264697066735822122051a137f3f2f370792efdafdfd52aa1721451dfaa2e804a5236730d97a26f237664736f6c63430008110033&quot;;<br><br><br>// Component<br>// ========================================================<br>export default function Deploy() {<br>  // Hooks<br>  const [isLoading, setIsLoading] = useState(false);<br>  const [error, setError] = useState(&#39;&#39;);<br>  const [transactionHash, setTransactionHash] = useState&lt;`0x${string}` | undefined&gt;();<br>  const { isConnected, address, connector } = useAccount();<br><br>  // Functions<br>  /**<br>   * @dev hook that waits for a transaction hash<br>   */<br>  const txResult = useWaitForTransaction({<br>    hash: transactionHash<br>  });<br><br>  /**<br>   * @dev handles deploying the contract<br>   */<br>  const onPressDeployContract = async () =&gt; {<br>    console.group(&#39;onPressDeployContract&#39;);<br>    setError(&#39;&#39;);<br>    setIsLoading(true);<br>    try {<br>      const provider = await connector?.getProvider(); // get&#39;s the provider from wagmi directly - needed for the walletconnection<br>      console.log({ provider });<br>      console.log({ request: provider?.request });<br><br>      // Based on constructor - constructor(string memory _greeting) {<br>      // encodes the function name and the input of `Hello World!`<br>      const encodedData = encodeAbiParameters(<br>        [{ name: &quot;_greeting&quot;, type: &quot;string&quot; }],<br>        [&quot;Hello World!&quot;]<br>      );<br><br>      // Need slide(2) to remove 0x from encodedData at the beginning<br>      const fullByteCode = `${CONTRACT_BYTECODE}${encodedData.slice(2)}`;<br><br>      // Send eth transsaction<br>      const tx = await provider.request({<br>        method: &quot;eth_sendTransaction&quot;,<br>        params: [<br>          {<br>            from: address,<br>            data: fullByteCode,<br>          },<br>        ],<br>      });<br>      console.log({ tx });<br><br>      // Set the state transaction hash for `useWaitForTransaction` to wait for it<br>      setTransactionHash(tx);<br>    } catch (error: unknown) {<br>      console.error(error);<br>      setError(&#39;Error could not deploy contract.&#39;)<br>    }<br>    setIsLoading(false);<br><br>    console.groupEnd();<br>  };<br><br>  /**<br>   * @dev function that handles opening url to the final transaction hash in a block explorer<br>   */<br>  const onPressSeeTransaction = () =&gt; {<br>    openURL(`${process.env.EXPO_PUBLIC_CHAIN_BLOCKEXPLORER_URL}/tx/${txResult?.data?.transactionHash}`);<br>  };<br><br>  /**<br>   * If not connected and no address, then don&#39;t show anything<br>   */<br>  if (!isConnected || !address) return null;<br><br>  return (<br>    &lt;&gt;<br>      &lt;Text className=&quot;Text&quot;&gt;Deploy Contract&lt;/Text&gt;<br>      &lt;Pressable<br>        disabled={isLoading}<br>        className={&quot;Button&quot;}<br>        onPress={onPressDeployContract}&gt;<br>        &lt;Text className=&quot;text-white text-base&quot;&gt;<br>          Deploy<br>        &lt;/Text&gt;<br>      &lt;/Pressable&gt;<br><br>      {isLoading &amp;&amp; &lt;Text className=&quot;Code&quot;&gt;Loading...&lt;/Text&gt;}<br><br>      {!isLoading &amp;&amp; txResult?.data?.transactionHash<br>        ? &lt;Pressable<br>          className=&quot;Button&quot;<br>          onPress={onPressSeeTransaction}&gt;<br>          &lt;Text className=&quot;text-white text-base&quot;&gt;<br>            See Successful Transaction<br>          &lt;/Text&gt;<br>        &lt;/Pressable&gt;<br>        : null}<br>      {error ? &lt;Text className=&quot;TextError&quot;&gt;{error}&lt;/Text&gt; : null}<br>    &lt;/&gt;<br>  );<br>}</pre><p>Now if we go through the process, we should be able to tap Deploy, be prompted by our wallet to confirm the transaction, wait for the transaction to complete and see the transaction on the block explorer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zFD4Ba_Op3wnGEzndlzpxQ.jpeg" /><figcaption>Berachain WalletConnect Successfully Deployed Contract</figcaption></figure><h3>🐻 Full Code Repository</h3><p>If you want to see the final code and see other guides, check out <a href="https://github.com/berachain/guides/tree/main/apps/walletconnect-expo"><strong><em>Berachain WalletConnect Expo Guide Code</em></strong></a>.</p><p><a href="https://github.com/berachain/guides/tree/main/apps/walletconnect-expo">guides/apps/walletconnect-expo at main · berachain/guides</a></p><h3>🛠️ Want To Build More?</h3><p>Want to build more on Berachain and see more examples. Take a look at our <a href="https://github.com/berachain/guides"><strong><em>Berachain GitHub Guides Repo</em></strong></a> for a wide variety of implementations that include NextJS, Hardhat, Viem, Foundry, and more.</p><p><a href="https://github.com/berachain/guides">GitHub - berachain/guides: A demonstration of different contracts, languages, and libraries that work with Berachain EVM.</a></p><p>If you’re looking to dive deeper into the details, take a look at our <a href="https://docs.berachain.com"><strong><em>Berachain Docs</em></strong></a>.</p><p><a href="https://docs.berachain.com">Berachain Docs</a></p><h4>Looking For Dev Support?</h4><p>Make sure to join our <a href="https://discord.com/invite/berachain"><strong><em>Berachain Discord</em></strong></a> server and check out our developer channels to ask questions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/1*1aH5owNqr3WwIh4sGfk9Gw.png" /><figcaption><a href="https://discord.com/invite/berachain">https://discord.com/invite/berachain</a></figcaption></figure><p><a href="https://discord.com/invite/berachain">Join the Berachain Discord Server!</a></p><p>❤️ Don’t forget to show some love for this article 👏🏼.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c5cde75dbac2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/berachain-devs/build-a-berachain-mobile-dapp-with-walletconnect-expo-c5cde75dbac2">Build A Berachain Mobile dApp With WalletConnect &amp; Expo</a> was originally published in <a href="https://medium.com/berachain-devs">berachain-devs</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[3 Ways To Configure CORS for NextJS 13 App Router API Route Handlers]]></title>
            <link>https://codingwithmanny.medium.com/3-ways-to-configure-cors-for-nextjs-13-app-router-api-route-handlers-427e10929818?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/427e10929818</guid>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[nextjs-app-router]]></category>
            <category><![CDATA[cors]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[networking]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Wed, 30 Aug 2023 21:51:22 GMT</pubDate>
            <atom:updated>2023-08-30T21:51:22.233Z</atom:updated>
            <content:encoded><![CDATA[<h4>Configure Cross-Origin Resource Sharing For NextJS 13 App Router</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xtUmPY4kkCQu3ZiWcg_gYw.png" /><figcaption>How To Configuring CORS For NextJS 13 App Router</figcaption></figure><p>If you have ever created a backend and a frontend that both live on different servers trying to communicate with each other, then you will know of CORS issues.</p><blockquote>Cross-Origin Resource Sharing (<a href="https://developer.mozilla.org/en-US/docs/Glossary/CORS">CORS</a>) is an <a href="https://developer.mozilla.org/en-US/docs/Glossary/HTTP">HTTP</a>-header based mechanism that allows a server to indicate any <a href="https://developer.mozilla.org/en-US/docs/Glossary/Origin">origins</a> (domain, scheme, or port) other than its own from which a browser should permit loading resources.</blockquote><blockquote>— <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS</a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xEd5Wn-X4ybKMJuwyNSXQw.png" /><figcaption>Typical CORS Error With NextJS 13</figcaption></figure><p>If you have started working with NextJS 13, you’ll also notice that there are a bunch of changes which makes it a bit harder to get up and running with existing libraries, such as <a href="https://github.com/yonycalsin/nextjs-cors"><strong><em>nextjs-cors</em></strong></a>.</p><p>I’m going to cover 3 ways that you can configure CORS to allow for resource sharing between a backend API from NextJS and a frontend.</p><h3>NextJS CORS Environment Setup</h3><p>In order to get things up running, we’ll be running two services, NextJS as an API, and a simple Client-Side application that makes HTTP requests to the API.</p><h4>Initial NextJS Project</h4><p>We’ll be using the default scaffolded NextJS 13 template.</p><pre><br>npx create-next-app@latest nextjs13-cors;<br><br># [Expected Prompts]:<br># ✔ Would you like to use TypeScript? … No / Yes<br># ✔ Would you like to use ESLint? … No / Yes<br># ✔ Would you like to use Tailwind CSS? … No / Yes<br># ✔ Would you like to use `src/` directory? … No / Yes<br># ✔ Would you like to use App Router? (recommended) … No / Yes<br># ✔ Would you like to customize the default import alias? … No / Yes<br># Creating a new Next.js app in /path/to/nextjs13-cors.</pre><h4>Creating First NextJS AppRouter Endpoint</h4><p>We won’t bother with creating a fully functional endpoint, but we’ll create both a GET and POST request.</p><pre># FROM: ./<br><br>mkdir app/api;<br>mkdir app/api/users;<br>touch app/api/users/route.ts;</pre><p><strong>File:</strong> app/api/users/route.ts</p><pre>// Imports<br>// ========================================================<br>import { NextResponse, type NextRequest } from &quot;next/server&quot;;<br><br>// Endpoints<br>// ========================================================<br>/**<br> * Basic GET Request to simuluate LIST in LCRUD<br> * @param request<br> * @returns<br> */<br>export const GET = async (request: NextRequest) =&gt; {<br>  // Return Response<br>  return NextResponse.json(<br>    {<br>      data: [<br>        {<br>          id: &quot;45eb616b-7283-4a16-a4e7-2a25acbfdf02&quot;,<br>          name: &quot;John Doe&quot;,<br>          email: &quot;john.doe@email.com&quot;,<br>          createdAt: new Date().toISOString(),<br>        },<br>      ],<br>    },<br>    {<br>      status: 200,<br>    }<br>  );<br>};<br><br>/**<br> * Basic POST Request to simuluate CREATE in LCRUD<br> * @param request<br> */<br>export const POST = async (request: NextRequest) =&gt; {<br>  // Get JSON payload<br>  const data = await request.json();<br><br>  // Return Response<br>  return NextResponse.json(<br>    {<br>      data,<br>    },<br>    {<br>      status: 200,<br>    }<br>  );<br>};</pre><h4>Testing NextJS GET &amp; POST Requests</h4><p>You can use an API Client for this, like Postman, or you can use curl in your terminal.</p><p>Let’s run our app.</p><pre># FROM: /<br>pnpm dev;<br><br># [Expected Output]:<br># &gt; nextjs13-cors@0.1.0 dev /path/to/nextjs13-cors<br># &gt; next dev<br># <br># - ready started server on [::]:3000, url: http://localhost:3000</pre><p>Let’s verify that the endpoints work as expected in another Terminal window.</p><pre># FROM: /<br><br># TEST 1 - GET Method for NextJS Endpoint<br>curl --location &#39;http://localhost:3000/api/users&#39;;<br><br># [Expected Output]:<br># {&quot;data&quot;:[{&quot;id&quot;:&quot;45eb616b-7283-4a16-a4e7-2a25acbfdf02&quot;,&quot;name&quot;:&quot;John Doe&quot;,&quot;email&quot;:&quot;john.doe@email.com&quot;,&quot;createdAt&quot;:&quot;2023-08-30T18:17:39.277Z&quot;}]}<br><br># TEST 2- POST Method for NextJS Endpoint<br>curl --location &#39;http://localhost:3000/api/users&#39; \<br>--header &#39;Content-Type: application/json&#39; \<br>--data &#39;{<br>    &quot;hello&quot;: &quot;there&quot;<br>}&#39;;<br># [Expected Output]:<br># {&quot;data&quot;:{&quot;hello&quot;:&quot;there&quot;}}</pre><h4>Creating Client-Side App</h4><p>Next we want to use another frontend application to make request to our backend API. To get things working quickly without having to configure React or a server, we’re going to use VanillaJS and a package called http-server.</p><pre># FROM: ./<br><br>pnpm add -D http-server;</pre><p>We’ll then create a folder called client with an HTML and JavaScript file that makes HTTP requests to our endpoints.</p><pre># FROM: /<br><br>mkdir client;<br>touch client/index.html;<br>touch client/script.js;</pre><p>In our HTML file, we’ll put the following:</p><p><strong>File:</strong> client/index.html</p><pre>&lt;!DOCTYPE html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>  &lt;head&gt;<br>    &lt;meta charset=&quot;UTF-8&quot; /&gt;<br>    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;<br>    &lt;title&gt;Client-Side Tests For NextJS Cors&lt;/title&gt;<br>    &lt;script src=&quot;/script.js&quot;&gt;&lt;/script&gt;<br>  &lt;/head&gt;<br>  &lt;body&gt;<br>    &lt;main&gt;<br>      &lt;h1&gt;Client-Side Application Testing Cors&lt;/h1&gt;<br><br>      &lt;h2&gt;GET&lt;/h2&gt;<br><br>      &lt;form id=&quot;form-get&quot;&gt;<br>        &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;<br>      &lt;/form&gt;<br><br>      &lt;pre<br>        style=&quot;<br>          width: 500px;<br>          height: 400px;<br>          background: #efefef;<br>          overflow: scroll;<br>        &quot;<br>      &gt;&lt;code id=&quot;result-get&quot;&gt;&lt;/code&gt;&lt;/pre&gt;<br><br>      &lt;hr /&gt;<br><br>      &lt;h2&gt;POST&lt;/h2&gt;<br><br>      &lt;form id=&quot;form-post&quot;&gt;<br>        &lt;div&gt;<br>          &lt;label for=&quot;payload&quot; style=&quot;display: block; margin: 0px 0px 10px 0px;&quot;&gt;JSON Payload&lt;/label&gt;<br>          &lt;textarea rows=&quot;10&quot; name=&quot;payload&quot; id=&quot;payload&quot;&gt;&lt;/textarea&gt;<br>        &lt;/div&gt;<br>        &lt;div&gt;<br>          &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;<br>        &lt;/div&gt;<br>      &lt;/form&gt;<br><br>      &lt;pre<br>        style=&quot;<br>          width: 500px;<br>          height: 400px;<br>          background: #efefef;<br>          overflow: scroll;<br>        &quot;<br>      &gt;&lt;code id=&quot;result-post&quot;&gt;&lt;/code&gt;&lt;/pre&gt;<br>    &lt;/main&gt;<br>  &lt;/body&gt;<br>&lt;/html&gt;</pre><p>Next create the associated JavaScript file.</p><p><strong>File:</strong> client/script.js</p><pre>// Config<br>// ========================================================<br>const API_URL = &quot;http://localhost:3000/api&quot;;<br><br>// Functions<br>// ========================================================<br>const requests = {<br>  GET: async (callback) =&gt; {<br>    const response = await fetch(`${API_URL}/users`);<br>    const data = await response.json();<br>    if (callback) {<br>      callback(data);<br>    }<br>  },<br>  POST: async (payload, callback) =&gt; {<br>    const response = await fetch(`${API_URL}/users`, {<br>      method: &quot;POST&quot;,<br>      headers: {<br>        &quot;Content-Type&quot;: &quot;application/json&quot;,<br>      },<br>      body: JSON.stringify(payload),<br>    });<br>    const data = await response.json();<br>    if (callback) {<br>      callback(data);<br>    }<br>  },<br>};<br><br>// Init<br>// ========================================================<br>/**<br> * When window is loaded<br> */<br>window.onload = () =&gt; {<br>  console.group(&quot;Window loaded&quot;);<br><br>  // Elements<br>  const formGet = document.getElementById(&quot;form-get&quot;);<br>  const resultGet = document.getElementById(&quot;result-get&quot;);<br>  const formPost = document.getElementById(&quot;form-post&quot;);<br>  const resultPost = document.getElementById(&quot;result-post&quot;);<br><br>  // Event Listeners<br>  formGet.addEventListener(&quot;submit&quot;, (event) =&gt; {<br>    event.preventDefault();<br>    requests.GET((data) =&gt; {<br>      resultGet.innerHTML = JSON.stringify(data, null, 2);<br>    });<br>  });<br>  formPost.addEventListener(&quot;submit&quot;, (event) =&gt; {<br>    event.preventDefault();<br>    requests.POST(<br>      JSON.parse(event.currentTarget.payload.value),<br>      (data) =&gt; {<br>        resultPost.innerHTML = JSON.stringify(data, null, 2);<br>      }<br>    );<br>  });<br><br>  console.groupEnd();<br>};</pre><p>Now we can run our client by modifying our package.json file with a new run command called “client”.</p><p><strong>File:</strong> package.json</p><pre>{<br>  &quot;name&quot;: &quot;nextjs13-cors&quot;,<br>  &quot;version&quot;: &quot;0.1.0&quot;,<br>  &quot;private&quot;: true,<br>  &quot;scripts&quot;: {<br>    &quot;dev&quot;: &quot;next dev&quot;,<br>    &quot;build&quot;: &quot;next build&quot;,<br>    &quot;start&quot;: &quot;next start&quot;,<br>    &quot;lint&quot;: &quot;next lint&quot;,<br>    &quot;client&quot;: &quot;http-server -p 3001 ./client&quot;<br>  },<br>  &quot;dependencies&quot;: {<br>    &quot;@types/node&quot;: &quot;20.5.7&quot;,<br>    &quot;@types/react&quot;: &quot;18.2.21&quot;,<br>    &quot;@types/react-dom&quot;: &quot;18.2.7&quot;,<br>    &quot;eslint&quot;: &quot;8.48.0&quot;,<br>    &quot;eslint-config-next&quot;: &quot;13.4.19&quot;,<br>    &quot;next&quot;: &quot;13.4.19&quot;,<br>    &quot;react&quot;: &quot;18.2.0&quot;,<br>    &quot;react-dom&quot;: &quot;18.2.0&quot;,<br>    &quot;typescript&quot;: &quot;5.2.2&quot;<br>  },<br>  &quot;devDependencies&quot;: {<br>    &quot;http-server&quot;: &quot;^14.1.1&quot;<br>  }<br>}</pre><p>Run the new app.</p><pre># FROM: /<br><br>pnpm client;<br><br># [Expected Output]:<br># &gt; nextjs13-cors@0.1.0 client /path/to/nextjs13-cors<br># &gt; http-server -p 3001 ./client<br># <br># Starting up http-server, serving ./client<br>#<br># http-server version: 14.1.1<br># <br># http-server settings: <br># CORS: disabled<br># Cache: 3600 seconds<br># Connection Timeout: 120 seconds<br># Directory Listings: visible<br># AutoIndex: visible<br># Serve GZIP Files: false<br># Serve Brotli Files: false<br># Default File Extension: none<br># <br># Available on:<br>#   http://127.0.0.1:3001<br>#   http://192.168.1.140:3001<br># Hit CTRL-C to stop the server</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YOm-GOhFlLljo2DZowmgtQ.png" /><figcaption>Client-Side Application Running On Port 3001</figcaption></figure><p>If our NextJS server is still running on port 3000 and then try with our client-side application, we should see that we encounter CORS issues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jUqffNpMF8_ckF_Tex1JwQ.png" /><figcaption>Client-Side Application Getting CORS Errors</figcaption></figure><p>Now that we have identified the issue, I’ll walk you through 3 ways to fix CORS issues with NextJS 13 AppRouter.</p><h3>CORS Fix Option 1 — Next Config</h3><p>One of the quickest ways to get things fixed is to apply a blanket statement over all endpoints, and this can be achieved through NextJS config file, by setting the right headers.</p><p><strong>File:</strong> next.config.js</p><pre>/** @type {import(&#39;next&#39;).NextConfig} */<br>const nextConfig = {<br>  async headers() {<br>    return [<br>      {<br>        // Routes this applies to<br>        source: &quot;/api/(.*)&quot;,<br>        // Headers<br>        headers: [<br>          // Allow for specific domains to have access or * for all<br>          {<br>            key: &quot;Access-Control-Allow-Origin&quot;,<br>            value: &quot;*&quot;,<br>            // DOES NOT WORK<br>            // value: process.env.ALLOWED_ORIGIN,<br>          },<br>          // Allows for specific methods accepted<br>          {<br>            key: &quot;Access-Control-Allow-Methods&quot;,<br>            value: &quot;GET, POST, PUT, DELETE, OPTIONS&quot;,<br>          },<br>          // Allows for specific headers accepted (These are a few standard ones)<br>          {<br>            key: &quot;Access-Control-Allow-Headers&quot;,<br>            value: &quot;Content-Type, Authorization&quot;,<br>          },<br>        ],<br>      },<br>    ];<br>  },<br>};<br><br>module.exports = nextConfig;</pre><p>Our NextJS Server will prompt us to restart, and if do and go back to our client-side app, we can see that the request is successful.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*78D2v-6pppkxwpHMw9ocIg.png" /><figcaption>NextJS AppRoute CORS Fix Option 1 — Next Config</figcaption></figure><p>One of the downfalls for this option is that you can’t dynamically changes values if you want to allow more than one allowed origin. You would basically have to hard code it, as ENV variables are not read.</p><p>This means, if you want to update an allowed origin, update to accept a custom header, and/or configure max age or more, you would need to submit a new PR to have your code redeployed.</p><p>It is however a great place to get things applied to many routes at once.</p><h3>CORS Fix Option 2— Route Handler</h3><p>Option 2 involves creating CORS configurations directly in the route itself. Let’s make sure to comment out our next.config.js headers and start working with the users route directly.</p><p>For this, we’re going to create two environment variables in .env to start.</p><pre># FROM: ./<br><br>touch .env;</pre><p><strong>File:</strong> .env</p><pre>ALLOWED_METHODS=&quot;GET, POST, PUT, DELETE, OPTIONS&quot;<br>ALLOWED_ORIGIN=&quot;*&quot;<br>ALLOWED_HEADERS=&quot;Content-Type, Authorization&quot;<br>DOMAIN_URL=&quot;http://localhost:3000&quot;</pre><p>Next we can now modify our route to validate an origin and take those values from our environment variable file. You’ll also notice that in the file, we had to create an explicit OPTIONS request to handle preflight requests.</p><p><strong>File:</strong> app/api/users/route.ts</p><pre>// Imports<br>// ========================================================<br>import { NextResponse, type NextRequest } from &quot;next/server&quot;;<br><br>// Config CORS<br>// ========================================================<br>/**<br> *<br> * @param origin<br> * @returns<br> */<br>const getCorsHeaders = (origin: string) =&gt; {<br>  // Default options<br>  const headers = {<br>    &quot;Access-Control-Allow-Methods&quot;: `${process.env.ALLOWED_METHODS}`,<br>    &quot;Access-Control-Allow-Headers&quot;: `${process.env.ALLOWED_HEADERS}`,<br>    &quot;Access-Control-Allow-Origin&quot;: `${process.env.DOMAIN_URL}`,<br>  };<br><br>  // If no allowed origin is set to default server origin<br>  if (!process.env.ALLOWED_ORIGIN || !origin) return headers;<br><br>  // If allowed origin is set, check if origin is in allowed origins<br>  const allowedOrigins = process.env.ALLOWED_ORIGIN.split(&quot;,&quot;);<br><br>  // Validate server origin<br>  if (allowedOrigins.includes(&quot;*&quot;)) {<br>    headers[&quot;Access-Control-Allow-Origin&quot;] = &quot;*&quot;;<br>  } else if (allowedOrigins.includes(origin)) {<br>    headers[&quot;Access-Control-Allow-Origin&quot;] = origin;<br>  }<br><br>  // Return result<br>  return headers;<br>};<br><br>// Endpoints<br>// ========================================================<br>/**<br> * Basic OPTIONS Request to simuluate OPTIONS preflight request for mutative requests<br> */<br>export const OPTIONS = async (request: NextRequest) =&gt; {<br>  // Return Response<br>  return NextResponse.json(<br>    {},<br>    {<br>      status: 200,<br>      headers: getCorsHeaders(request.headers.get(&quot;origin&quot;) || &quot;&quot;),<br>    }<br>  );<br>};<br><br>/**<br> * Basic GET Request to simuluate LIST in LCRUD<br> * @param request<br> * @returns<br> */<br>export const GET = async (request: NextRequest) =&gt; {<br>  // Return Response<br>  return NextResponse.json(<br>    {<br>      data: [<br>        {<br>          id: &quot;45eb616b-7283-4a16-a4e7-2a25acbfdf02&quot;,<br>          name: &quot;John Doe&quot;,<br>          email: &quot;john.doe@email.com&quot;,<br>          createdAt: new Date().toISOString(),<br>        },<br>      ],<br>    },<br>    {<br>      status: 200,<br>      headers: getCorsHeaders(request.headers.get(&quot;origin&quot;) || &quot;&quot;),<br>    }<br>  );<br>};<br><br>/**<br> * Basic POST Request to simuluate CREATE in LCRUD<br> * @param request<br> */<br>export const POST = async (request: NextRequest) =&gt; {<br>  // Get JSON payload<br>  const data = await request.json();<br><br>  // Return Response<br>  return NextResponse.json(<br>    {<br>      data,<br>    },<br>    {<br>      status: 200,<br>      headers: getCorsHeaders(request.headers.get(&quot;origin&quot;) || &quot;&quot;),<br>    }<br>  );<br>};</pre><p>We should see the same result, where requests are working as expected with no CORS issues.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sNYULQEpK7j8SWN-mVYyaQ.png" /><figcaption>NextJS AppRoute CORS Fix Option 2— Route Handler CORS Configuration</figcaption></figure><p>The main benefit of this method is that we can be very specific as to which endpoint would get specific CORS configurations, and we can leverage environment variables to switch out the values without deploying hard-coded values.</p><p>The main downfall of this option is that it’s siloed and doesn’t provide a wider configuration to apply to many endpoints.</p><h3>CORS Fix Option 3— Middleware (Recommended)</h3><p>This method I recommend because it’s the best of Option 1, where it applies to multiple endpoints, and Option 2, where you can configure things further through the use of environment variables.</p><p>To start, I’m going to comment out what we did in Option 2, so that we can leverage the middleware.</p><p><strong>File:</strong> app/api/users/route.ts</p><pre>// Imports<br>// ========================================================<br>import { NextResponse, type NextRequest } from &quot;next/server&quot;;<br><br>// Config CORS<br>// ========================================================<br>// /**<br>//  *<br>//  * @param origin<br>//  * @returns<br>//  */<br>// const getCorsHeaders = (origin: string) =&gt; {<br>//   // Default options<br>//   const headers = {<br>//     &quot;Access-Control-Allow-Methods&quot;: `${process.env.ALLOWED_METHODS}`,<br>//     &quot;Access-Control-Allow-Headers&quot;: `${process.env.ALLOWED_HEADERS}`,<br>//     &quot;Access-Control-Allow-Origin&quot;: `${process.env.DOMAIN_URL}`,<br>//   };<br><br>//   // If no allowed origin is set to default server origin<br>//   if (!process.env.ALLOWED_ORIGIN || !origin) return headers;<br><br>//   // If allowed origin is set, check if origin is in allowed origins<br>//   const allowedOrigins = process.env.ALLOWED_ORIGIN.split(&quot;,&quot;);<br><br>//   // Validate server origin<br>//   if (allowedOrigins.includes(&quot;*&quot;)) {<br>//     headers[&quot;Access-Control-Allow-Origin&quot;] = &quot;*&quot;;<br>//   } else if (allowedOrigins.includes(origin)) {<br>//     headers[&quot;Access-Control-Allow-Origin&quot;] = origin;<br>//   }<br><br>//   // Return result<br>//   return headers;<br>// };<br><br>// Endpoints<br>// ========================================================<br>// /**<br>//  * Basic OPTIONS Request to simuluate OPTIONS preflight request for mutative requests<br>//  */<br>// export const OPTIONS = async (request: NextRequest) =&gt; {<br>//   // Return Response<br>//   return NextResponse.json(<br>//     {},<br>//     {<br>//       status: 200,<br>//       headers: getCorsHeaders(request.headers.get(&quot;origin&quot;) || &quot;&quot;),<br>//     }<br>//   );<br>// };<br><br>/**<br> * Basic GET Request to simuluate LIST in LCRUD<br> * @param request<br> * @returns<br> */<br>export const GET = async (request: NextRequest) =&gt; {<br>  // Return Response<br>  return NextResponse.json(<br>    {<br>      data: [<br>        {<br>          id: &quot;45eb616b-7283-4a16-a4e7-2a25acbfdf02&quot;,<br>          name: &quot;John Doe&quot;,<br>          email: &quot;john.doe@email.com&quot;,<br>          createdAt: new Date().toISOString(),<br>        },<br>      ],<br>    },<br>    {<br>      status: 200,<br>      // headers: getCorsHeaders(request.headers.get(&quot;origin&quot;) || &quot;&quot;),<br>    }<br>  );<br>};<br><br>/**<br> * Basic POST Request to simuluate CREATE in LCRUD<br> * @param request<br> */<br>export const POST = async (request: NextRequest) =&gt; {<br>  // Get JSON payload<br>  const data = await request.json();<br><br>  // Return Response<br>  return NextResponse.json(<br>    {<br>      data,<br>    },<br>    {<br>      status: 200,<br>      // headers: getCorsHeaders(request.headers.get(&quot;origin&quot;) || &quot;&quot;),<br>    }<br>  );<br>};</pre><p>I’m also going to add some additional CORS configurations to our .env file that would be good to have for further customization later.</p><p><strong>File:</strong> .env</p><pre>ALLOWED_METHODS=&quot;GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS&quot;<br>ALLOWED_ORIGIN=&quot;http://localhost:3001,http://localhost:3000&quot; # * for all<br>ALLOWED_HEADERS=&quot;Content-Type, Authorization&quot;<br>EXPOSED_HEADERS=&quot;&quot;<br>MAX_AGE=&quot;86400&quot; # 60 * 60 * 24 = 24 hours<br>CREDENTIALS=&quot;true&quot;<br>DOMAIN_URL=&quot;http://localhost:3000&quot;</pre><p>Next I’m going to create a new file in the root of the project called middleware.ts.</p><pre># FROM: ./<br><br>touch middleware.ts;</pre><p><strong>File:</strong> middleware.ts</p><pre>// Imports<br>// ========================================================<br>import { NextResponse, type NextRequest } from &quot;next/server&quot;;<br><br>// Config<br>// ========================================================<br>const corsOptions: {<br>  allowedMethods: string[];<br>  allowedOrigins: string[];<br>  allowedHeaders: string[];<br>  exposedHeaders: string[];<br>  maxAge?: number;<br>  credentials: boolean;<br>} = {<br>  allowedMethods: (process.env?.ALLOWED_METHODS || &quot;&quot;).split(&quot;,&quot;),<br>  allowedOrigins: (process.env?.ALLOWED_ORIGIN || &quot;&quot;).split(&quot;,&quot;),<br>  allowedHeaders: (process.env?.ALLOWED_HEADERS || &quot;&quot;).split(&quot;,&quot;),<br>  exposedHeaders: (process.env?.EXPOSED_HEADERS || &quot;&quot;).split(&quot;,&quot;),<br>  maxAge: process.env?.MAX_AGE &amp;&amp; parseInt(process.env?.MAX_AGE) || undefined, // 60 * 60 * 24 * 30, // 30 days<br>  credentials: process.env?.CREDENTIALS == &quot;true&quot;,<br>};<br><br>// Middleware<br>// ========================================================<br>// This function can be marked `async` if using `await` inside<br>export async function middleware(request: NextRequest) {<br>  // Response<br>  const response = NextResponse.next();<br><br>  // Allowed origins check<br>  const origin = request.headers.get(&#39;origin&#39;) ?? &#39;&#39;;<br>  if (corsOptions.allowedOrigins.includes(&#39;*&#39;) || corsOptions.allowedOrigins.includes(origin)) {<br>    response.headers.set(&#39;Access-Control-Allow-Origin&#39;, origin);<br>  }<br><br>  // Set default CORS headers<br>  response.headers.set(&quot;Access-Control-Allow-Credentials&quot;, corsOptions.credentials.toString());<br>  response.headers.set(&quot;Access-Control-Allow-Methods&quot;, corsOptions.allowedMethods.join(&quot;,&quot;));<br>  response.headers.set(&quot;Access-Control-Allow-Headers&quot;, corsOptions.allowedHeaders.join(&quot;,&quot;));<br>  response.headers.set(&quot;Access-Control-Expose-Headers&quot;, corsOptions.exposedHeaders.join(&quot;,&quot;));<br>  response.headers.set(&quot;Access-Control-Max-Age&quot;, corsOptions.maxAge?.toString() ?? &quot;&quot;);<br><br>  // Return<br>  return response;<br>}<br><br>// See &quot;Matching Paths&quot; below to learn more<br>export const config = {<br>  matcher: &quot;/api/:path*&quot;,<br>};</pre><p>Now if we try our client-side application again with the updated environment variables, we can see that things work as expected for GET and POST HTTP requests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dlAXrr4h0Ej2n31SHWYbXg.png" /><figcaption>NextJS AppRoute CORS Fix Option 3— Middleware CORS Configuration</figcaption></figure><h3>Full Code Repository</h3><p>If you want to see all these implementations within a single repository, here is the full repository link to <a href="https://github.com/codingwithmanny/nextjs13-cors"><strong><em>NextJS13 CORS</em></strong></a>.</p><p><a href="https://github.com/codingwithmanny/nextjs13-cors">GitHub - codingwithmanny/nextjs13-cors: A simple example of how to configure CORS in a NextJS 13 app using AppRouter.</a></p><h3>What’s Next?</h3><p>The next step would be to get off localhost and get this code deployed through Vercel to see it working and understand how to create APIs that allow Client-Side Applications to interact with it.</p><p>If you got value from this, please give it some love, and please also follow me on X / Twitter (where I’m quite active) <a href="http://twitter.com/codingwithmanny"><strong><em>@codingwithmanny</em></strong></a><strong><em> </em></strong>and Youtube at <a href="https://youtube.com/@codingwithmanny"><strong><em>@codingwithmanny</em></strong></a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=427e10929818" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Fleek Network’s Co-Founder Let Me Publish Our Private DMs Convo]]></title>
            <link>https://codingwithmanny.medium.com/fleek-networks-co-founder-let-me-publish-our-private-dms-convo-2932fdc66c6f?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/2932fdc66c6f</guid>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[edge-computing]]></category>
            <category><![CDATA[cloud]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[fleek]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Tue, 29 Aug 2023 20:59:38 GMT</pubDate>
            <atom:updated>2023-08-29T21:04:05.095Z</atom:updated>
            <content:encoded><![CDATA[<h4>Part 2 Wen Fleek Network— Fleek Network’s Co-Founder Harrison Hines Reveals Alpha</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kcabcRyXYymRgS5mJC1Iyg.png" /><figcaption>Part 2 Series of a DM conversation with Fleek Network Co-Founder Harrison Hines</figcaption></figure><p>This is Part 2 of a 2 part post. If you want to know more about what Fleek Network is doing, check out Part 1 — <a href="https://codingwithmanny.medium.com/private-dms-with-fleek-networks-co-founder-harrison-hines-7d937678ac"><strong><em>Private DMs With Fleek Network’s Co-Founder Harrison Hines</em></strong></a>.</p><p><a href="https://codingwithmanny.medium.com/private-dms-with-fleek-networks-co-founder-harrison-hines-7d937678ac">Private DMs With Fleek Network’s Co-Founder Harrison Hines</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6aug8Pxv1ZChAIE-JE_UTQ.png" /><figcaption>Fleek Network has a lot to offer, but who do you see as the first people/developers building on the Network?</figcaption></figure><h3>Who Is Fleek Network For?</h3><h4>Manny:</h4><p>Help me understand something.</p><p>Fleek Network has been described as a platform where developers can build on top of it to build out custom service offerings.</p><p>Who do you see as the first people/developers building on the Network and why them for now?</p><h4>Harrison:</h4><p>A good mental model to understand how the Fleek Network ecosystem will develop is to think about it similar to a smart contract platform from the perspective that you will have a portion of developers who are actually building services on the network (so compare service builders to smart contract developers).</p><p>And then a much larger portion of developers who are consuming/using those services (ex. using a CDN service or edge compute service for their app), similar to how the majority of smart contract platform users are just consuming/using smart contracts, not necessarily building them.</p><p>The main difference being on a smart contract platform you are typically developing decentralized financial services, whereas with Fleek Network you are building decentralized web and edge services. And our initial focus will be on those service developers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VML3tEw_dw51yAR4pbNGFw.png" /><figcaption>Fleek Network Has Developer Incentivization From The Start</figcaption></figure><h4>Manny:</h4><p>What do you think would entice developers more to start building on Fleek Network and what do you think is missing?</p><h4>Harrison:</h4><p>Service developer incentivization is one of the things we are most excited about. Because besides grants and other traditional tactics, there are incentives for service developers built into the protocol itself.</p><p>For example 20% of revenue generated for the protocol is given to the services that are generating those fees. And so if you are early and for example you create an AWS Lambda type service or a DynamoDB type service, and that service gets surfaced in Fleek platform and packaged into a nice serverless functions or database type feature, now you basically have your business model (built in 20% of revenue generated) and initial distribution (Fleek platform) taken care of for you, and all you gotta worry about is maintaining/improving your service code.</p><h4>Manny:</h4><p>Sounds like a win-win for developers.</p><h4>Harrison:</h4><p>We think smart, opportunistic devs will realize that there is big opportunity to be early and build popular web and edge services on Fleek Network, the same way early Ethereum projects found success by building popular decentralized financial services.</p><p>But now imagine if Uniswap for example was getting 20% of all the gas fees they are driving to Ethereum, they’d never need to worry about value capture or token model again. That’s essentially how it will work on Fleek Network, so it’s very exciting for service developers.</p><h4>Manny:</h4><p>And what do you think is missing for Fleek Network?</p><h4>Harrison:</h4><p>What’s missing is we gotta finish the SDK and add services to the Testnet in September, and then focus a lot on the developer tooling and resources to make building/deploying/managing services super seamlessly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qiolbXuFkAidJtnWXY6L3g.png" /><figcaption>Fleek Network is potentially even more performant than centralized edge platforms</figcaption></figure><h3>What To Expect When Fleek Network Is Out</h3><h4>Manny:</h4><p>What would you say you are most excited about Fleek Network and what could be built on it short-term and long-term?</p><h4>Harrison:</h4><p>I’m most excited to prove out the performance of the network over the next few months. Because if we can show that we can be as performant, or potentially even more performant than centralized edge platforms, then I think that will be an enormous unlock for the modern web stack and the type of apps that can be built.</p><p>Because as the google data shows, speed is the lifeblood of the web. Loading times are the largest predictor of which apps people use. That’s why CDN’s and edge networks are so popular these days. And so if we can bring that web2 speed to web3 without sacrificing on web3 values, we think a lot of things will become possible.</p><h4>Manny:</h4><p>Sounds like a major win for web3 in terms of speed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*88kLCTNubqasKLt04CBU5w.png" /><figcaption>The possibilities for Fleek Network are endless and unknown</figcaption></figure><h4>Harrison:</h4><p>Those benefits don’t stop at web3 and are applicable to all devs and all web applications. Everyone needs performance/speed, and those needs are only increasing with the internet’s fast growing global user base, and the popularity of performance intensive use cases like video, streaming, gaming, AI, AR/VR, metaverse, etc..</p><p>So, I’m also most excited about the unknown.</p><h4>Manny:</h4><p>How do you mean?</p><h4>Harrison:</h4><p>For example when Ethereum launched, nobody actually knew what use cases would be big. DeFi and NFT’s weren’t obvious until a few years after launch. For Fleek Network we at least know a handful of web services that have already proven successful on the edge that we expect people to start with, but we are most excited to see people experiment and build things that nobody is expecting.</p><h4>Manny:</h4><p>I’d like to run a scenario by you.</p><p>Imagine you’re not the co-founder of Fleek Network, but obviously still a 10x engineer.</p><p>You read the <a href="https://whitepaper.fleek.network/"><strong><em>Fleek Network Whitepaper</em></strong></a> and you’re sold.</p><p>What would you start building?</p><h4>Harrison:</h4><p>Depending on my background/interests (ex. more web2 vs. web3 dev) I would either start by building traditional web services that have already proven success on the edge (different flavors of edge compute or serverless functions, edge dbs, CDN’s, container orchestration, SSR, etc.).</p><p>Just because of what I was saying above about the built in service developer incentives (20% cut of revenue) and looking at how Ethereum played out (biggest winners were the early ones who built decentralized alternatives to the biggest traditional financial services: trading, lending, derivatives, etc.).</p><p>Or if I was a more web3 native dev I would probably build things that I know web3 devs need that are currently centralized.</p><p>For example gateways, hosting, IPFS/IPNS pinning, graphQL backend, running nodes or validators for certain networks, etc.</p><p>You talk to pretty much any web3 project and you will quickly find points of centralization in their stack that they would love to decentralize. Or also I would maybe experiment with some more exotic things, like building an EVM service, or new types of rollups, or exploring topics like State Rent, or snapshotting blockchains for fast node syncing, etc.</p><h4>Manny:</h4><p>Sounds like a lot of great ideas for devs to start building.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bkQitzluTZQheYNVwBFE4g.png" /><figcaption>Fleek Network Phase 0 Testnet Will Go Out This Week</figcaption></figure><h3>Wen Fleek Network?</h3><h4>Manny:</h4><p>The Fleek Network Whitepaper outlines a pretty big dream, but when can developers start building with it?</p><p>And if they can’t start building now, what should devs be learning/researching/focusing on in preparation?</p><h4>Harrison:</h4><p>So the initial phase 0 alpha Testnet will go out next week [this week]. But as mentioned that first phase will be focused mostly on nodes.</p><p>The next Testnet phase in September will introduce the SDK and services and that’s really when people can start building.</p><p>If I was a developer interested in building services for now I would be researching and ideating on the first service I might try to build, and maybe reach out in Discord with any ideas/questions so we can help guide your thinking as we are releasing more information on how to build services.</p><h4>Manny:</h4><blockquote>[Just to going to paste this here for the Discord link — <a href="https://discord.com/invite/fleekxyz"><strong><em>https://discord.com/invite/fleekxyz</em></strong></a>]</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nl7gNJqtpyWaX770ymA2IQ.png" /><figcaption><a href="https://discord.com/invite/fleekxyz">https://discord.com/invite/fleekxyz</a></figcaption></figure><h4>Harrison:</h4><p>We put a bunch of examples in the Whitepaper to get people’s brain juices flowing.</p><p>What’s fun that we do internally is just think of all sorts of different web3 infra/projects/use cases and think about if/how those things make sense to put on the edge.</p><p>Surprisingly you will realize that a lot of things could make sense to build as edge services, much like we are seeing happen in the modern web.</p><h4>Manny:</h4><p>I’m pretty pumped at the all the ideas that you could build with Fleek Network.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xTzygyjlVu6iPrpqx7-x7w.png" /><figcaption>Fleek Twitter Handles</figcaption></figure><h3>How Do I Keep Up To Date With Fleek Network?</h3><h4>Manny:</h4><p>Fleek and Fleek Network has a lot going on.</p><p>What is the best place to keep up to date on… well, everything and who should they be following on socials?</p><h4>Harrison:</h4><p>The best place is prob to sign up for the mailing list, but also twitter, discord, and our blogs are great places to keep up to date on things. Besides following the <a href="https://twitter.com/fleek_net"><strong><em>@fleek_net</em></strong></a> and <a href="https://twitter.com/fleekxyz"><strong><em>@fleekxyz</em></strong></a> accounts I’d say following <a href="https://twitter.com/ParsaIsBack"><strong><em>@parsaisback</em></strong></a> and <a href="https://twitter.com/DaltonCoder"><strong><em>@daltoncoder</em></strong></a> is a good move.</p><p>They are the Fleek Network lead engineers.</p><h4>Manny:</h4><p>Done.</p><h4>Harrison:</h4><p>They are pretty heads down building right now but this fall they will start sharing more useful content. I will also try to share more helpful content as we start releasing more.</p><h4>Manny:</h4><p>Awesome!</p><p>Thank you for taking the time from your busy schedule and reply to my DMs and to <a href="https://twitter.com/itsnicoggi"><strong><em>@itsnicoggi</em></strong></a> for setting this up.</p><h4>Harrison:</h4><p>Thanks Manny.</p><h3>Enjoy This Article?</h3><p>Make sure to like this article, and also follow me on X (Twitter) to see when the announcement will be made at <a href="http://twitter.com/codingwithmanny"><strong><em>@codingwithmanny</em></strong></a>.</p><p>Manny thanks to <a href="http://twitter.com/harris0nhines"><strong><em>@harris0nhines</em></strong></a> for letting me ask him so many questions.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2932fdc66c6f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Private DMs With Fleek Network’s Co-Founder Harrison Hines]]></title>
            <link>https://codingwithmanny.medium.com/private-dms-with-fleek-networks-co-founder-harrison-hines-7d937678ac?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/7d937678ac</guid>
            <category><![CDATA[fleek]]></category>
            <category><![CDATA[interview]]></category>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[edge-computing]]></category>
            <category><![CDATA[protocol]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Wed, 23 Aug 2023 20:56:52 GMT</pubDate>
            <atom:updated>2023-08-29T21:01:19.570Z</atom:updated>
            <content:encoded><![CDATA[<h4>Part 1 What The Fleek — How This Network Changes How Developers Build For The Web</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KAfGlZc4cyO9LIsb6SoO9g.png" /><figcaption>A Private DM Conversation With Fleek Network Co-Founder Harrison Hines</figcaption></figure><p>I finally did it! I managed to (digitally) corner Harrison Hines, the co-founder of <a href="http://fleek.network"><strong>Fleek Network</strong></a> to talk to me more about the newly released <a href="https://whitepaper.fleek.network"><strong>Whitepaper</strong></a>.</p><p>After a little bit of back and forth, Harrison and I agreed to publish a series of questions I had asked him via direct messages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vKlYFPTR9Ul9wIWohOjoRw.png" /><figcaption>What Science Degree Do You Need To Interpret The Fleek Network Whitepaper?</figcaption></figure><h3>The Fleek &amp; Nothing But The Fleek, Please</h3><h4><strong>Manny:</strong></h4><p>Gm gm, Harrison. It’s a pleasure to e-meet via tg.</p><p>I’ll jump right into it. For those who are going to be reading this later, could you give a brief overview of Fleek as a whole, what was the motivation behind building it, and what level of science degree do you need to interpret the Fleek Network Whitepaper?</p><h4><strong>Harrison:</strong></h4><p>For sure. Fleek’s mission as a whole is to help free the web and make web3 a real thing.</p><p>When I joined the Ethereum space in 2017, I realized there were a lot of smart people and projects focused on building decentralized financial infrastructure, but not nearly as many focused on decentralizing internet infrastructure, and so that is where I felt we could have the biggest impact and contribution to web3.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FP5x54ASeuRBKEJuT4163w.png" /><figcaption>Fleek’s Mission — <a href="https://twitter.com/harris0nhines">https://twitter.com/harris0nhines</a></figcaption></figure><p>While certain pieces of the infra stack were starting to get decentralized (ex. smart contracts), we realized that unless you decentralize the full stack, everything is at risk.</p><h4><strong>Manny:</strong></h4><p>Is that when you decided that Fleek was the missing piece?</p><h4><strong>Harrison:</strong></h4><p>It kinda took a while for the decentralized web infra side of the space to get going, and so we watched and experimented for a while as things evolved until we were confident about how to approach things and where we could best fit in to add the most value to the web3 stack.</p><p>Last year was when everything culminated into Fleek Network and building a decentralized edge network.</p><p>The Whitepaper was us putting everything down on “paper” and we tried to make it as digestible as possible, but the subject matter made that a bit tricky lol.</p><p>We will def produce some easier to read material in the coming weeks/months, so stay tuned for that.</p><h4><strong>Manny:</strong></h4><p>I definitely had to read it a few times myself to get the full picture.</p><blockquote>Small plug from me, on the Competition Winning X Thread I did, if you haven’t seen it yet.</blockquote><h3>Manny (🧱,🚀) on Twitter: &quot;⚡👀 I spent 72 hours obsessing over this Whitepaper on a Decentralized Edge Network.It&#39;s not what I thought it was, and that&#39;s a good thing.Here&#39;s everything I learned. pic.twitter.com/qCjgcv2mcn / Twitter&quot;</h3><p>⚡👀 I spent 72 hours obsessing over this Whitepaper on a Decentralized Edge Network.It&#39;s not what I thought it was, and that&#39;s a good thing.Here&#39;s everything I learned. pic.twitter.com/qCjgcv2mcn</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-yeP8sHq1b2NLk1sxTL02w.png" /><figcaption>Fleek.xyz, Fleek Network, how do these differ and/or are potentially complimenting each other?</figcaption></figure><h3>Fleek.xyz, Fleek Network, I’m Confused</h3><h4><strong>Manny:</strong></h4><p>I noticed on Twitter (I mean X), people see Fleek.xyz and Fleek Network.</p><p>How do these differ and/or are potentially complimenting each other, and more importantly why two X accounts?</p><h4><strong>Harrison:</strong></h4><p>Great question.</p><p>Essentially Fleek Network represents the protocol (the decentralized edge network). And <a href="https://fleek.xyz"><strong><em>Fleek.xyz</em></strong></a> is the developer platform built on top of Fleek Network and other protocols.</p><p>Think of it kinda like how there’s the Uniswap protocol, and Uniswap the dapp and wallet, which essentially just serve as distribution tools for their protocol.</p><p>That’s exactly how we view Fleek.xyz, just a distribution/discovery channel for web3 infra protocols as well as for services built on Fleek Network.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MIc0xMG2tvYBpeMoc6lYMw.png" /><figcaption>Fleek Network vs Platform — <a href="https://twitter.com/harris0nhines">https://twitter.com/harris0nhines</a></figcaption></figure><h4>Manny:</h4><p>Would you say Fleek.xyz is sorta like the Vercel of web3?</p><h4>Harrison:</h4><p>Very<strong> </strong>close.</p><p>If you look at other modern dev platforms like Vercel and Netlify they work in a similar way.</p><p>They have the underlying infra/edge platform that powers their products/features, and then they package it all into a super seamless UI/UX.</p><p>Packaging the infra into a seamless UX is what enabled them to compete with incumbents like AWS, Cloudflare, etc. and drive distribution to their infra, and so we plan to follow that exact same approach as a way to drive usage to Fleek Network and other protocols.</p><h4>Manny:</h4><p>Sounds like a lot of infrastructure foundation and a polished product to show off what devs can do with it.</p><h4>Harrison:</h4><p>Yeah, which is why we probably have two X accounts.</p><p>We feel the two (network and platform) do very different things, and have very different developer audiences (network = infra devs; platform = app devs).</p><p>Combining them both felt like it might confuse everybody.</p><h4>Manny:</h4><p>That makes sense.</p><h3>What Does Fleek Network Look Like, Not On Paper?</h3><h4>Manny:</h4><p>Fleek Network seems like a massive idea as a whole. I guess that’s why there needed to be a Whitepaper.</p><p>What do you see as the big idea behind, in terms of implementation, for Fleek Network?</p><p>You’re Michelangelo, Sistine Chapel is your masterpiece, but right now I’m now seeing a blank canvas. Help me understand where we’re going.</p><h4>Harrison:</h4><p>So, good news!</p><p>We are pretty far along in terms of implementation.</p><p>We will have an initial alpha testnet next week that will start testing some of the core network functionality and performance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RnCresSIyf6cVjCOO01-ig.png" /><figcaption>Fleek Network Testnet Next Week— <a href="https://twitter.com/harris0nhines">https://twitter.com/harris0nhines</a></figcaption></figure><h4>Manny:</h4><p>That’s next week, as in August 28th at 12:01am PST? Can you tell I want access to the testnet?</p><h4>Harrison:</h4><p>lol. Maybe not that exact date and time, but next week for sure.</p><p>But what’s also very exciting is in September we will introduce the SDK and Services in a second testnet phase.</p><p>That is where things start to get super interesting because then anybody can start building web and edge services on the network.</p><h4>Manny:</h4><p>Super cool. I can’t stress how cool that sounds. But I have a feeling you’re going to pull a Steve Jobs and say “one more thing.”</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DtYZ08DeMs6V68VWsgTQhg.png" /><figcaption>Harrison From Fleek Network Unveils “One more thing…”</figcaption></figure><h4>Harrison:</h4><p>Guilty 😂. Shortly after the second phase will be followed by a few additional testnet phases throughout the remainder of this year, leading to a full mainnet launch most likely in Q1 2024.</p><h4>Manny:</h4><p>There we go! 🔥🔥🔥</p><h4>Harrison:</h4><p>And services can be deployed and run performantly and reliably already at the Testnet stage.</p><p>We will start surfacing some initial features/use cases/services in the Fleek.xyz platform starting in September/October to make it easy for people to experiment and get usage on the services they build.</p><h4>Manny:</h4><p>Why do I feel like it’s my birthday year?</p><h3>Part 2 — Fleek Alpha — Coming Soon (Now Out)</h3><p>Make sure to keep follow my Medium page for the second part of the conversation, and also follow me on X (Twitter) to see when the announcement will be made at <a href="https://x.com/codingwithmanny"><strong>@codingwithmanny</strong></a>.</p><p>I’ll also make sure to update this article with the link when Part 2 is out.</p><p>Manny thanks to <a href="https://twitter.com/harris0nhines"><strong>@harris0nhines</strong></a> for letting me bombard him with messages.</p><h4>Part 2 is out!</h4><p>Second part can be found here:</p><p><a href="https://codingwithmanny.medium.com/fleek-networks-co-founder-let-me-publish-our-private-dms-convo-2932fdc66c6f">Fleek Network’s Co-Founder Let Me Publish Our Private DMs Convo</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7d937678ac" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Combine Sign-In With Ethereum With Create-T3-App]]></title>
            <link>https://codingwithmanny.medium.com/combine-sign-in-with-ethereum-with-create-t3-app-8f54604caeeb?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/8f54604caeeb</guid>
            <category><![CDATA[sign-in-with-ethereum]]></category>
            <category><![CDATA[web3]]></category>
            <category><![CDATA[t3-stack]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[ethereum]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Mon, 10 Apr 2023 00:42:37 GMT</pubDate>
            <atom:updated>2023-04-17T15:36:35.226Z</atom:updated>
            <content:encoded><![CDATA[<h4>Configure SIWE wallet authentication with popular t3 stack which includes NextJS, TypeScript, Tailwind, tRPC, Prisma, and AuthJS (previously NextAuthJS)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SO5lkdKPWomOHOGPwehY1w.png" /><figcaption>Combining Sign-In With Ethereum &amp; Create T3 Stack To Build A Session Authentication App</figcaption></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FQq_9yRf4cvA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DQq_9yRf4cvA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FQq_9yRf4cvA%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/9f4a5542d98599833465f5e90194fae5/href">https://medium.com/media/9f4a5542d98599833465f5e90194fae5/href</a></iframe><h3>What Is The t3 Stack?</h3><p>Create T3 Stack is definitely one of the fastest ways to get up and running to build an application today. It’s a stack, similar to MEAN, MERN, MAMP, etc. The main benefit is that it comes with is the best tools in the industry to get started building for developers.</p><p>The T3 stack comes with NextJS, NextAuth, Prisma, tRPC, Tailwind, and all with TypeScript support. This is everything you need to get started in building an app from scratch and even gives you options to pick and choose which you’d like to include/exclude in your stack to optimize even further.</p><p>Give this tutorial a try and you should be able to see how easy it is to build a frontend, backend, session management, and database queries.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yq9TtAmfnyO1emqYPfWelg.png" /><figcaption><a href="https://create.t3.gg">https://create.t3.gg</a></figcaption></figure><h4>What Is Sign-In With Ethereum (SIWE)?</h4><p>For developers in web3, Sign-In With Ethereum is a standard that let’s you verify a message by signing it with a crypto wallet, more specifically a Ethereum crypto wallet.</p><p>The benefits of this unlock how we could do native verification of messages, and essentially get at authentication. In this tutorial, I’ll be showing you how we can merge SIWE with a full application built with the t3 stack.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yaQsZDa7KL_OCN5BDh10Vw.png" /><figcaption><a href="https://login.xyz">https://login.xyz</a></figcaption></figure><h3>Requirements</h3><p>Before we begin, make sure you have the latest version of these requirements installed on your computer.</p><ul><li>NVM or Node v18.5.0</li><li>npm</li></ul><h3>Integrating SIWE With T3 Stack</h3><p>What we’ll be aiming for is getting the create-t3-stack working locally with the Sign-In With Ethereum functionality.</p><h4>Create New create-t3-app</h4><p>The first thing we’ll do is scaffold out the stack as a new project.</p><pre>pnpm create t3-app@latest; # npm create t3-app@latest;<br><br># Expected Output:<br>#    ___ ___ ___   __ _____ ___   _____ ____    __   ___ ___<br>#   / __| _ \ __| /  \_   _| __| |_   _|__ /   /  \ | _ \ _ \<br>#  | (__|   / _| / /\ \| | | _|    | |  |_ \  / /\ \|  _/  _/<br>#   \___|_|_\___|_/‾‾\_\_| |___|   |_| |___/ /_/‾‾\_\_| |_|<br># <br># <br># ? What will your project be called? t3-siwe<br># ? Will you be using TypeScript or JavaScript? TypeScript<br># Good choice! Using TypeScript!<br># ? Which packages would you like to enable? nextAuth, prisma, tailwind, trpc<br># ? Initialize a new git repository? Yes<br># Nice one! Initializing repository!<br># ? Would you like us to run &#39;pnpm install&#39;? Yes<br># Alright. We&#39;ll install the dependencies for you!<br># ? What import alias would you like configured? ~/<br># <br># Using: pnpm<br># <br># ✔ t3-siwe-wip scaffolded successfully!<br># Adding boilerplate...<br># ✔ Successfully setup boilerplate for nextAuth<br># ✔ Successfully setup boilerplate for prisma<br># ✔ Successfully setup boilerplate for tailwind<br># ✔ Successfully setup boilerplate for trpc<br># ✔ Successfully setup boilerplate for envVariables<br>#<br># Installing dependencies...<br># ✔ Successfully installed dependencies!<br>#<br># Initializing Git...<br># ✔ Successfully initialized and staged git<br># <br># Next steps:<br>#  cd t3-siwe<br>#  pnpm prisma db push<br>#  pnpm dev</pre><p>One of the options we selected is Prisma, so we’ll need to initiate the database. We’ll use the default db.sqlite set int he .env file.</p><pre>cd t3-siwe;<br>npx prisma migrate dev;<br><br># Expected Output:<br># Environment variables loaded from .env<br># Prisma schema loaded from prisma/schema.prisma<br># Datasource &quot;db&quot;: SQLite database &quot;db.sqlite&quot; at &quot;file:./db.sqlite&quot;<br># <br># ? Enter a name for the new migration: › create_new_example_account_session_user_verificationtoken<br># Applying migration `20230408143224_create_new_example_account_session_user_verificationtoken`<br>#<br># The following migration(s) have been created and applied from new schema changes:<br># <br># migrations/<br>#   └─ 20230408143224_create_new_example_account_session_user_verificationtoken/<br>#     └─ migration.sql<br># <br># Your database is now in sync with your schema.<br># <br># ✔ Generated Prisma Client (4.11.0 | library) to ./node_modules/.pnpm/@prisma+client@4.11.0_prisma@4.11.0/node_modules<br># /@prisma/client in 611ms</pre><p>In a new Terminal, let’s take a look at our new database.</p><pre># FROM: ./t3-siwe<br><br>npx prisma studio;<br><br># Expected Output:<br># Environment variables loaded from .env<br># Prisma schema loaded from prisma/schema.prisma<br># Prisma Studio is up on http://localhost:5555</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DPZj14zvp02Vr3oHhGOR-Q.png" /><figcaption>Prisma Studio http://localhost:5555</figcaption></figure><p>Now that we have our database set up, let’s start the stack and see what it looks like.</p><pre># FROM: ./t3-siwe<br><br>pnpm dev;<br><br># Expected Output:<br># &gt; t3-siwe-wip@0.1.0 dev /Users/username/path/to/t3-siwe-wip<br># &gt; next dev<br># <br># ready - started server on 0.0.0.0:3000, url: http://localhost:3000</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OUAiU-YdXPDvLGWB6QhcSQ.png" /><figcaption>Create T3 App — http://localhost:3000</figcaption></figure><p>You’ll notice if we click the Sign in button, we’ll be prompted to a screen that comes with the default <strong>Sign in with Discord</strong>.</p><p>We’re going to replace this functionality.</p><h4>Adding Connect With Wallet Functionality</h4><p>To start, we’re going to first establish if the user has connected to the site in order to be prompted to sign the message needed for Sign-In With Ethereum.</p><p>For this we’ll need to install some dependencies first.</p><pre># FROM: ./t3-siwe<br><br>pnpm add wagmi siwe ethers@^5;</pre><p>With the dependencies, we need to configure Wagmi as provider for our entire app.</p><p><strong>File:</strong> ./src/pages/_app.tsx</p><pre>// Imports<br>// ========================================================<br>import { type AppType } from &quot;next/app&quot;;<br>import { type Session } from &quot;next-auth&quot;;<br>import { SessionProvider } from &quot;next-auth/react&quot;;<br>import { api } from &quot;~/utils/api&quot;;<br>import &quot;~/styles/globals.css&quot;;<br>// SIWE Integration<br>import { WagmiConfig, createClient, configureChains } from &quot;wagmi&quot;;<br>import { mainnet, polygon, optimism, arbitrum } from &quot;wagmi/chains&quot;;<br>import { publicProvider } from &quot;wagmi/providers/public&quot;;<br><br>// Config<br>// ========================================================<br>/**<br> * Configure chains supported<br> */<br>const { provider } = configureChains(<br>  [mainnet, polygon, optimism, arbitrum],<br>  [publicProvider()]<br>);<br><br>/**<br> * Configure client with providers and allow for auto wallet connection<br> */<br>const client = createClient({<br>  autoConnect: true,<br>  provider,<br>});<br><br>// App Wrapper Component<br>// ========================================================<br>const MyApp: AppType&lt;{ session: Session | null }&gt; = ({<br>  Component,<br>  pageProps: { session, ...pageProps },<br>}) =&gt; {<br>  return (<br>    &lt;WagmiConfig client={client}&gt;<br>      &lt;SessionProvider session={session}&gt;<br>        &lt;Component {...pageProps} /&gt;<br>      &lt;/SessionProvider&gt;<br>    &lt;/WagmiConfig&gt;<br>  );<br>};<br><br>// Exports<br>// ========================================================<br>export default api.withTRPC(MyApp);</pre><p>With the configuration done, we can now modify our main home page to allow the user to connect their wallet to the website. This will show when the wallet has been connected and allows the user to also disconnect.</p><p><strong>File:</strong> ./src/pages/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { type NextPage } from &quot;next&quot;;<br>import Head from &quot;next/head&quot;;<br>import Link from &quot;next/link&quot;;<br>// (Will add back)<br>// import { signIn, signOut, useSession } from &quot;next-auth/react&quot;;<br>import { api } from &quot;~/utils/api&quot;;<br>// SIWE Integration<br>import { useAccount, useConnect, useDisconnect } from &quot;wagmi&quot;;<br>import { InjectedConnector } from &#39;wagmi/connectors/injected&#39;;<br><br>// Auth Component<br>// ========================================================<br>const AuthShowcase: React.FC = () =&gt; {<br>  // Hooks<br>  // (Will add back)<br>  // const { data: sessionData } = useSession();<br>  // const { data: secretMessage } = api.example.getSecretMessage.useQuery(<br>  //   undefined, // no input<br>  //   { enabled: sessionData?.user !== undefined },<br>  // );<br><br>  // Wagmi Hooks<br>  const { address, isConnected } = useAccount();<br>  const { connect } = useConnect({<br>    connector: new InjectedConnector(),<br>  });<br>  const { disconnect } = useDisconnect();<br><br>  // Render<br>  return (<br>    &lt;div className=&quot;flex flex-col items-center justify-center gap-4&quot;&gt;<br>      &lt;div className=&quot;text-center&quot;&gt;<br>        {address<br>          ? &lt;p className=&quot;mb-4&quot;&gt;<br>            &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{address}&lt;/code&gt;<br>          &lt;/p&gt;<br>          : null<br>        }<br>        &lt;button<br>          className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>          onClick={() =&gt; !isConnected ? connect() : disconnect()}<br>        &gt;<br>          {!isConnected ? &#39;Connect Wallet&#39; : &#39;Disconnect&#39;}<br>        &lt;/button&gt;<br>      &lt;/div&gt;<br>    &lt;/div&gt;<br>  );<br>};<br><br>// Page Component<br>// ========================================================<br>const Home: NextPage = () =&gt; {<br>  // Requests<br>  const hello = api.example.hello.useQuery({ text: &quot;from tRPC&quot; });<br><br>  // Render<br>  return (<br>    &lt;&gt;<br>      &lt;Head&gt;<br>        &lt;title&gt;Create T3 App&lt;/title&gt;<br>        &lt;meta name=&quot;description&quot; content=&quot;Generated by create-t3-app&quot; /&gt;<br>        &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;<br>      &lt;/Head&gt;<br>      &lt;main className=&quot;flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]&quot;&gt;<br>        &lt;div className=&quot;container flex flex-col items-center justify-center gap-12 px-4 py-16 &quot;&gt;<br>          &lt;h1 className=&quot;text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]&quot;&gt;<br>            Create &lt;span className=&quot;text-[hsl(280,100%,70%)]&quot;&gt;T3&lt;/span&gt; App<br>          &lt;/h1&gt;<br>          &lt;div className=&quot;grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8&quot;&gt;<br>            &lt;Link<br>              className=&quot;flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20&quot;<br>              href=&quot;https://create.t3.gg/en/usage/first-steps&quot;<br>              target=&quot;_blank&quot;<br>            &gt;<br>              &lt;h3 className=&quot;text-2xl font-bold&quot;&gt;First Steps →&lt;/h3&gt;<br>              &lt;div className=&quot;text-lg&quot;&gt;<br>                Just the basics - Everything you need to know to set up your<br>                database and authentication.<br>              &lt;/div&gt;<br>            &lt;/Link&gt;<br>            &lt;Link<br>              className=&quot;flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20&quot;<br>              href=&quot;https://create.t3.gg/en/introduction&quot;<br>              target=&quot;_blank&quot;<br>            &gt;<br>              &lt;h3 className=&quot;text-2xl font-bold&quot;&gt;Documentation →&lt;/h3&gt;<br>              &lt;div className=&quot;text-lg&quot;&gt;<br>                Learn more about Create T3 App, the libraries it uses, and how<br>                to deploy it.<br>              &lt;/div&gt;<br>            &lt;/Link&gt;<br>          &lt;/div&gt;<br>          &lt;div className=&quot;flex flex-col items-center gap-2&quot;&gt;<br>            &lt;p className=&quot;text-2xl text-white block mb-4&quot;&gt;<br>              {hello.data ? hello.data.greeting : &quot;Loading tRPC query...&quot;}<br>            &lt;/p&gt;<br>            &lt;AuthShowcase /&gt;<br>          &lt;/div&gt;<br>        &lt;/div&gt;<br>      &lt;/main&gt;<br>    &lt;/&gt;<br>  );<br>};<br><br>// Exports<br>// ========================================================<br>export default Home;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qoYjq8TUdgcsZOxDzo0ANA.png" /><figcaption>Create T3 App Wallet Connection</figcaption></figure><p>This looks like a good start until you hard refresh the site and see an error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s9FtTNmlb_BUZXIYR9_YEw.png" /><figcaption>Create T3 App NextJS Hydration Error</figcaption></figure><p>We’re going to implement a quick fix for this, but if you want to know more about this issue, check out my other article <a href="https://codingwithmanny.medium.com/understanding-hydration-errors-in-nextjs-13-with-a-web3-wallet-connection-8155c340fbd5"><strong><em>Understanding Hydration Errors In NextJS 13 With A Web3 Wallet Connection</em></strong></a>.</p><p><strong>File:</strong> ./src/pages/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { type NextPage } from &quot;next&quot;;<br>import Head from &quot;next/head&quot;;<br>import Link from &quot;next/link&quot;;<br>import { useEffect, useState } from &quot;react&quot;;<br>// (Will add back)<br>// import { signIn, signOut, useSession } from &quot;next-auth/react&quot;;<br>import { api } from &quot;~/utils/api&quot;;<br>// SIWE Integration<br>import { useAccount, useConnect, useDisconnect } from &quot;wagmi&quot;;<br>import { InjectedConnector } from &#39;wagmi/connectors/injected&#39;;<br><br>// Auth Component<br>// ========================================================<br>const AuthShowcase: React.FC = () =&gt; {<br>  // Hooks<br>  // (Will add back)<br>  // const { data: sessionData } = useSession();<br>  // const { data: secretMessage } = api.example.getSecretMessage.useQuery(<br>  //   undefined, // no input<br>  //   { enabled: sessionData?.user !== undefined },<br>  // );<br>  // State<br>  const [showConnection, setShowConnection] = useState(false);<br><br>  // Wagmi Hooks<br>  const { address, isConnected } = useAccount();<br>  const { connect } = useConnect({<br>    connector: new InjectedConnector(),<br>  });<br>  const { disconnect } = useDisconnect();<br><br>  // Hooks<br>  /**<br>   * Handles hydration issue<br>   * only show after the window has finished loading<br>   */<br>  useEffect(() =&gt; {<br>    setShowConnection(true);<br>  }, []);<br><br>  // Render<br>  return (<br>    &lt;div className=&quot;flex flex-col items-center justify-center gap-4&quot;&gt;<br>      {showConnection<br>        ? &lt;div className=&quot;text-center&quot;&gt;<br>          {address<br>            ? &lt;p className=&quot;mb-4&quot;&gt;<br>              &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{address}&lt;/code&gt;<br>            &lt;/p&gt;<br>            : null<br>          }<br>          &lt;button<br>            className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>            onClick={() =&gt; !isConnected ? connect() : disconnect()}<br>          &gt;<br>            {!isConnected ? &#39;Connect Wallet&#39; : &#39;Disconnect&#39;}<br>          &lt;/button&gt;<br>        &lt;/div&gt;<br>        : null}<br>    &lt;/div&gt;<br>  );<br>};<br><br>// Page Component<br>// ========================================================<br>// ...</pre><h4>SIWE Next Auth Provider Configuration</h4><p>For this next part, we need to setup the correct Authentication Provider for NextAuthJS (AuthJS), but in order for things to work, we need to make sure we have a specific version of next-auth installed, as there is <a href="https://github.com/nextauthjs/next-auth/issues/7166"><strong><em>currently an issue</em></strong></a>.</p><pre># FROM: ./t3-siwe<br><br>pnpm add next-auth@4.20.1;</pre><p>Once we have that installed, we’re going to configure our authOptions for the Authentication Provider. This is the main configuration for NextAuth that we’re going to leverage with its existing convention for configuration.</p><p><strong>File:</strong> ./src/server/auth.ts</p><pre>// Imports<br>// ========================================================<br>import { type GetServerSidePropsContext } from &quot;next&quot;;<br>import { getServerSession, type NextAuthOptions, type DefaultSession } from &quot;next-auth&quot;;<br>// (Will add back)<br>// import { prisma } from &quot;~/server/db&quot;;<br>// SIWE Integration<br>import type { CtxOrReq } from &quot;next-auth/client/_utils&quot;;<br>import CredentialsProvider from &quot;next-auth/providers/credentials&quot;;<br>import { SiweMessage } from &quot;siwe&quot;;<br>import { getCsrfToken } from &quot;next-auth/react&quot;;<br><br>// Types<br>// ========================================================<br>/**<br> * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`<br> * object and keep type safety.<br> *<br> * @see https://next-auth.js.org/getting-started/typescript#module-augmentation<br> */<br>declare module &quot;next-auth&quot; {<br>  interface Session extends DefaultSession {<br>    user: {<br>      id: string;<br>      // ...other properties<br>      // role: UserRole;<br>    } &amp; DefaultSession[&quot;user&quot;];<br>  }<br><br>  // interface User {<br>  //   // ...other properties<br>  //   // role: UserRole;<br>  // }<br>}<br><br>// Auth Options<br>// ========================================================<br>/**<br> * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.<br> *<br> * @see https://next-auth.js.org/configuration/options<br> */<br>export const authOptions: (ctxReq: CtxOrReq) =&gt; NextAuthOptions = ({ req }) =&gt; ({<br>  callbacks: {<br>    // token.sub will refer to the id of the wallet address<br>    session: ({ session, token }) =&gt; ({<br>      ...session,<br>      user: {<br>        ...session.user,<br>        id: token.sub,<br>      },<br>    } as Session &amp; { user: { id: string; }}),<br>    // OTHER CALLBACKS to take advantage of but not needed<br>    // signIn: async (params: { // Used to control if a user is allowed to sign in<br>    //   user: User | AdapterUser<br>    //   account: Account | null<br>    //   // Not used for credentials<br>    //   profile?: Profile<br>    //   // Not user<br>    //   email?: {<br>    //     verificationRequest?: boolean<br>    //   }<br>    //   /** If Credentials provider is used, it contains the user credentials */<br>    //   credentials?: Record&lt;string, CredentialInput&gt;<br>    // }) =&gt; { return true; },<br>    // redirect: async (params: { // Used for a callback url but not used with credentials<br>    //   /** URL provided as callback URL by the client */<br>    //   url: string<br>    //   /** Default base URL of site (can be used as fallback) */<br>    //   baseUrl: string<br>    // }) =&gt; {<br>    //    return params.baseUrl;<br>    // },<br>    // jwt: async ( // Callback whenever JWT created (i.e. at sign in)<br>    //   params: {<br>    //     token: JWT<br>    //     user: User | AdapterUser<br>    //     account: Account | null<br>    //     profile?: Profile<br>    //     trigger?: &quot;signIn&quot; | &quot;signUp&quot; | &quot;update&quot;<br>    //     /** @deprecated use `trigger === &quot;signUp&quot;` instead */<br>    //     isNewUser?: boolean<br>    //     session?: any<br>    //   }<br>    // ) =&gt; {<br>    //   return params.token;<br>    // }<br>  },<br>  // OTHER OPTIONS (not needed)<br>  // secret: process.env.NEXTAUTH_SECRET, // in case you want pass this along for other functionality<br>  // adapter: PrismaAdapter(prisma), // Not meant for type &#39;credentials&#39; (used for db sessions)<br>  // jwt: { // Custom functionlaity for jwt encoding/decoding<br>  //   encode: async ({ token, secret, maxAge }: JWTEncodeParams) =&gt; {<br>  //     return encode({<br>  //       token,<br>  //       secret,<br>  //       maxAge,<br>  //     })<br>  //   },<br>  //   decode: async ({ token, secret }: JWTDecodeParams) =&gt; {<br>  //     return decode({ token, secret })<br>  //   }<br>  // },<br>  // session: { // Credentials defaults to this strategy<br>  //   strategy: &#39;jwt&#39;,<br>  //   maxAge: 2592000,<br>  //   updateAge: 86400,<br>  //   generateSessionToken: () =&gt; &#39;SomeValue&#39;<br>  // },<br>  // events: { // Callback events<br>  //   signIn: async (message: {<br>  //     user: User<br>  //     account: Account | null<br>  //     profile?: Profile<br>  //     isNewUser?: boolean<br>  //   }) =&gt; {},<br>  //   signOut: async (message: { session: Session; token: JWT }) =&gt; {},<br>  //   createUser:  async (message: { user: User }) =&gt; {},<br>  //   updateUser:  async (message: { user: User }) =&gt; {},<br>  //   linkAccount: async (message: {<br>  //     user: User | AdapterUser<br>  //     account: Account<br>  //     profile: User | AdapterUser<br>  //   }) =&gt; {},<br>  //   session: async (message: { session: Session; token: JWT }) =&gt; {}<br>  // },<br>  providers: [<br>    CredentialsProvider({<br>      // ! Don&#39;t add this<br>      // - it will assume more than one auth provider <br>      // - and redirect to a sign-in page meant for oauth<br>      // - id: &#39;siwe&#39;, <br>      name: &quot;Ethereum&quot;,<br>      type: &quot;credentials&quot;, // default for Credentials<br>      // Default values if it was a form<br>      credentials: {<br>        message: {<br>          label: &quot;Message&quot;,<br>          type: &quot;text&quot;,<br>          placeholder: &quot;0x0&quot;,<br>        },<br>        signature: {<br>          label: &quot;Signature&quot;,<br>          type: &quot;text&quot;,<br>          placeholder: &quot;0x0&quot;,<br>        },<br>      },<br>      authorize: async (credentials) =&gt; {<br>        try {<br>          const siwe = new SiweMessage(JSON.parse(credentials?.message as string ?? &quot;{}&quot;) as Partial&lt;SiweMessage&gt;);<br>          const nonce = await getCsrfToken({ req });<br>          const fields = await siwe.validate(credentials?.signature || &quot;&quot;)<br><br>          if (fields.nonce !== nonce) {<br>            return null;<br>          }<br>          return {<br>            id: fields.address<br>          };<br>        } catch (error) {<br>          // Uncomment or add logging if needed<br>          console.error({ error });<br>          return null;<br>        }<br>      },<br>    })<br>    /**<br>     * ...add more providers here.<br>     *<br>     * Most other providers require a bit more work than the Discord provider. For example, the<br>     * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account<br>     * model. Refer to the NextAuth.js docs for the provider you want to use. Example:<br>     *<br>     * @see https://next-auth.js.org/providers/github<br>     */<br>  ],<br>});<br><br>// Auth Session<br>// ========================================================<br>/**<br> * Wrapper for `getServerSession` so that you don&#39;t need to import the `authOptions` in every file.<br> *<br> * @see https://next-auth.js.org/configuration/nextjs<br> */<br>export const getServerAuthSession = async (ctx: {<br>  req: GetServerSidePropsContext[&quot;req&quot;];<br>  res: GetServerSidePropsContext[&quot;res&quot;];<br>}) =&gt; {<br>  // Changed from authOptions to authOption(ctx)<br>  // This allows use to retrieve the csrf token to verify as the nonce<br>  return getServerSession(ctx.req, ctx.res, authOptions(ctx));<br>};</pre><p>Now that we have our authOptions configured, we need to configure nextAuth to work with this as it differs from what was before because we need to pass it a request.</p><p><strong>File:</strong> ./src/pages/api/auth/[...nextauth].ts</p><pre>// Imports<br>// ========================================================<br>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;<br>import NextAuth from &quot;next-auth&quot;;<br>import { authOptions } from &quot;~/server/auth&quot;;<br><br>// Auth<br>// ========================================================<br>const Auth = (req: NextApiRequest, res: NextApiResponse) =&gt; {<br>    const authOpts = authOptions({ req });<br>    return NextAuth(req, res, authOpts);<br>};<br><br>// Exports<br>// ========================================================<br>export default Auth;</pre><p>We have our NextAuth configured, except what you’ll notice is if we go to /api/auth/signin we have a configured Sign in with Ethereum button, but this won’t work with the app, because this is configured to work as a form submission, and the wallet configuration works different.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3rJtuWt3Fr0-A1JrhyfmQg.png" /><figcaption><a href="http://localhost:3000/api/auth/signin">http://localhost:3000/api/auth/signin</a></figcaption></figure><p>To fix this, we’re just going to account for this by catch if this is the signin page and removing the Credentials Auth Provider.</p><p><strong>File:</strong> ./src/pages/api/auth/[...nextauth].ts</p><pre>// Imports<br>// ========================================================<br>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;<br>import NextAuth from &quot;next-auth&quot;;<br>import { authOptions } from &quot;~/server/auth&quot;;<br><br>// Auth<br>// ========================================================<br>const Auth = (req: NextApiRequest, res: NextApiResponse) =&gt; {<br>    const authOpts = authOptions({ req });<br><br>    const isDefaultSigninPage = req.method === &quot;GET&quot; &amp;&amp; req?.query?.nextauth?.includes(&quot;signin&quot;);<br><br>    // Hide Sign-In with Ethereum from default sign page<br>    if (isDefaultSigninPage) {<br>        // Removes from the authOptions.providers array<br>        authOpts.providers.pop();<br>    }<br><br>    return NextAuth(req, res, authOpts);<br>};<br><br>// Exports<br>// ========================================================<br>export default Auth;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m7rTR8Qc8SKNXsI6VsIveA.png" /><figcaption><a href="http://localhost:3000/api/auth/signin">http://localhost:3000/api/auth/signin</a></figcaption></figure><p>There is probably a more elegant solution for this, but ideally we just discourage the user from going to this link for any sort of authentication flow.</p><h4>SIWE Authentication &amp; Session Verification</h4><p>Now that we have the configuration set up, we just need to alter our main home page to allow the user to sign a message which will be verified on our backend and create a session.</p><p><strong>File:</strong> ./src/pages/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { type NextPage } from &quot;next&quot;;<br>import Head from &quot;next/head&quot;;<br>import Link from &quot;next/link&quot;;<br>import { useEffect, useState } from &quot;react&quot;;<br>import { getCsrfToken, signIn, signOut, useSession } from &quot;next-auth/react&quot;;<br>import { api } from &quot;~/utils/api&quot;;<br>// SIWE Integration<br>import { SiweMessage } from &quot;siwe&quot;;<br>import { useAccount, useConnect, useDisconnect, useSignMessage, useNetwork } from &quot;wagmi&quot;;<br>import { InjectedConnector } from &#39;wagmi/connectors/injected&#39;;<br><br>// Auth Component<br>// ========================================================<br>const AuthShowcase: React.FC = () =&gt; {<br>  // Hooks<br>  const { data: sessionData } = useSession();<br>  const { data: secretMessage } = api.example.getSecretMessage.useQuery(<br>    undefined, // no input<br>    { enabled: sessionData?.user !== undefined },<br>  );<br>  // State<br>  const [showConnection, setShowConnection] = useState(false);<br><br>  // Wagmi Hooks<br>  const { signMessageAsync } = useSignMessage();<br>  const { address, isConnected } = useAccount();<br>  const { connect } = useConnect({<br>    connector: new InjectedConnector(),<br>  });<br>  const { disconnect } = useDisconnect();<br>  const { chain } = useNetwork();<br><br>  // Functions<br>  /**<br>   * Attempts SIWE and establish session<br>   */<br>  const onClickSignIn = async () =&gt; {<br>    try {<br>      const message = new SiweMessage({<br>        domain: window.location.host,<br>        address: address,<br>        statement: &quot;Sign in with Ethereum to the app.&quot;,<br>        uri: window.location.origin,<br>        version: &quot;1&quot;,<br>        chainId: chain?.id,<br>        // nonce is used from CSRF token<br>        nonce: await getCsrfToken(),<br>      })<br>      const signature = await signMessageAsync({<br>        message: message.prepareMessage(),<br>      })<br>      signIn(&quot;credentials&quot;, {<br>        message: JSON.stringify(message),<br>        redirect: false,<br>        signature,<br>      })<br>    } catch (error) {<br>      window.alert(error);<br>    }<br>  };<br><br>  /**<br>   * Sign user out<br>   */<br>  const onClickSignOut = async () =&gt; {<br>    await signOut();<br>  };<br><br>  // Hooks<br>  /**<br>   * Handles hydration issue<br>   * only show after the window has finished loading<br>   */<br>  useEffect(() =&gt; {<br>    setShowConnection(true);<br>  }, []);<br><br>  // Render<br>  return (<br>    &lt;div className=&quot;flex flex-col items-center justify-center gap-4&quot;&gt;<br>        {sessionData<br>          ? &lt;div className=&quot;mb-4 text-center&quot;&gt;<br>            {sessionData ? &lt;div className=&quot;mb-4&quot;&gt;<br>              &lt;label className=&quot;block text-white/80 mb-2&quot;&gt;Logged in as&lt;/label&gt;<br>              &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{JSON.stringify(sessionData)}&lt;/code&gt;<br>            &lt;/div&gt;: null}<br>            {secretMessage ? &lt;p className=&quot;mb-4&quot;&gt;<br>              &lt;label className=&quot;block text-white/80 mb-2&quot;&gt;Secret Message&lt;/label&gt;<br>              &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{secretMessage}&lt;/code&gt;<br>            &lt;/p&gt;: null}<br><br>            &lt;button<br>              className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>              onClick={onClickSignOut as () =&gt; void}<br>            &gt;<br>              Sign Out<br>            &lt;/button&gt;<br>          &lt;/div&gt;<br>          : showConnection<br>            ? &lt;div className=&quot;mb-4&quot;&gt;<br>              {isConnected<br>                ? &lt;button<br>                  className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>                  onClick={onClickSignIn as () =&gt; void}<br>                &gt;<br>                  Sign In<br>                &lt;/button&gt;<br>                : null<br>              }<br>            &lt;/div&gt;<br>            : null<br>        }<br>      {showConnection<br>        ? &lt;div className=&quot;text-center&quot;&gt;<br>          {address<br>            ? &lt;p className=&quot;mb-4&quot;&gt;<br>              &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{address}&lt;/code&gt;<br>            &lt;/p&gt;<br>            : null<br>          }<br>          &lt;button<br>            className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>            onClick={() =&gt; !isConnected ? connect() : disconnect()}<br>          &gt;<br>            {!isConnected ? &#39;Connect Wallet&#39; : &#39;Disconnect&#39;}<br>          &lt;/button&gt;<br>        &lt;/div&gt;<br>        : null}<br>    &lt;/div&gt;<br>  );<br>};<br><br>// Page Component<br>// ========================================================<br>// ...</pre><p>If we try the app, we can see we can connect our wallet, get a prompt for SIWE, and then successfully sign in to see the user session data.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RH4x7MixhD4wRo74KL-3bA.png" /><figcaption>Create T3 App SIWE Prompt</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rwhkiYyRP1BauzCcYsmWEQ.png" /><figcaption>Create T3 App SIWE Session Data Shown</figcaption></figure><p>There are just two issues. If we refresh the page, we’ll see that our session is gone, and even when we are signed in, we aren’t seeing our secretMessage which shows when there is session data established.</p><p>To fix this, we just need to modify our .env file to add a NEXTAUTH_SECRET.</p><p><strong>File:</strong> ./.env</p><pre># When adding additional environment variables, the schema in &quot;/src/env.mjs&quot;<br># should be updated accordingly.<br><br># Prisma<br># https://www.prisma.io/docs/reference/database-reference/connection-urls#env<br>DATABASE_URL=&quot;file:./db.sqlite&quot;<br><br># Next Auth<br># You can generate a new secret on the command line with:<br># openssl rand -base64 32<br># https://next-auth.js.org/configuration/options#secret<br>NEXTAUTH_SECRET=&quot;A-REALLY-LONG-SECRET-PASSWORD-32&quot;<br>NEXTAUTH_URL=&quot;http://localhost:3000&quot;<br><br># Next Auth Discord Provider<br>DISCORD_CLIENT_ID=&quot;&quot;<br>DISCORD_CLIENT_SECRET=&quot;&quot;</pre><p>If we restart our server, and go through the authentication process, we should the secret message now. We should also see our session persist if we refresh the page as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6H2bugwaiPw5LNqxNXqKrQ.png" /><figcaption>Create T3 App SIWE Successful Session Established</figcaption></figure><p>If you’re happy with establishing a session with Sign-In With Ethereum, then we got everything covered, but I’d like to take it a step further and get things working on a protected page and configuring our database to store our user’s data.</p><h4>Modifying Our Database For SIWE</h4><p>The next thing I want to do is modify the templated database to allow for a wallet address.</p><p>If we look at our schema.prisma file we’ll notice we’re not really use the tables for VerificationToken, Session, and Example. These are there mainly to work with database sessions, which we aren’t using.</p><p>I’m going to remove some tables, rewrite the Example as a new Todo table, and just add a new address field for the User table.</p><p><strong>File:</strong> ./prisma/schema.prisma</p><pre>// This is your Prisma schema file,<br>// learn more about it in the docs: https://pris.ly/d/prisma-schema<br><br>generator client {<br>    provider = &quot;prisma-client-js&quot;<br>}<br><br>datasource db {<br>    provider = &quot;sqlite&quot;<br>    // NOTE: When using postgresql, mysql or sqlserver, uncomment the @db.Text annotations in model Account below<br>    // Further reading:<br>    // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema<br>    // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string<br>    url      = env(&quot;DATABASE_URL&quot;)<br>}<br><br>// Revised Example table<br>model Todo {<br>    id        String   @id @default(cuid())<br>    task    String<br>    userId            String<br>    completed   Boolean @default(false)<br>    createdAt DateTime @default(now())<br>    updatedAt DateTime @updatedAt<br>    user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)<br>}<br><br>// Necessary for Next auth<br>model Account {<br>    id                String  @id @default(cuid())<br>    userId            String<br>    type              String<br>    provider          String<br>    providerAccountId String<br>    refresh_token     String? // @db.Text<br>    access_token      String? // @db.Text<br>    expires_at        Int?<br>    token_type        String?<br>    scope             String?<br>    id_token          String? // @db.Text<br>    session_state     String?<br>    user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)<br><br>    @@unique([provider, providerAccountId])<br>}<br>model User {<br>    id            String    @id @default(cuid())<br>    name          String?<br>    // New address<br>    address         String?   @unique<br>    email         String?   @unique<br>    emailVerified DateTime?<br>    image         String?<br>    accounts      Account[]<br>    todos   Todo[]<br>}</pre><p>With that modification done, let’s run a migration.</p><pre># FROM: ./t3-siwe<br><br>npx prisma migrate dev;<br><br># Expected Output:<br># ✔ Are you sure you want create and apply this migration? … yes<br># ✔ Enter a name for the new migration: … create_siwe_structure<br># Applying migration `20230409205305_create_siwe_structure`<br>#<br># The following migration(s) have been created and applied from new schema changes:<br># <br># migrations/<br>#   └─ 20230409205305_create_siwe_structure/<br>#     └─ migration.sql<br>#<br># Your database is now in sync with your schema.<br># <br># ✔ Generated Prisma Client (4.11.0 | library) to <br># ./node_modules/.pnpm/@prisma+client@4.11.0_prisma@4.11<br># .0/node_modules/@prisma/client in 71ms</pre><p>Let’s also check on our prisma studio to see the changes.</p><pre># FROM: ./t3-siwe<br><br>npx prisma studio;<br><br># Expected Output:<br># Environment variables loaded from .env<br># Prisma schema loaded from prisma/schema.prisma<br># Prisma Studio is up on http://localhost:5555</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*21O0WUqgcgTbmNtkMylL6w.png" /><figcaption>Prisma Studio <a href="http://localhost:5555">http://localhost:5555</a> Updated With Database Changes</figcaption></figure><h4>New User &amp; Account Creation On NextAuth Authentication</h4><p>Now that we have the database configured, let’s get our nextOptions configured so that the callback will create a new user if needed, or retrieve an existing user. For the changes, see the authorize section along side adding back the prisma import at the top.</p><p><strong>File:</strong> ./src/server/auth.ts</p><pre>// Imports<br>// ========================================================<br>import { type GetServerSidePropsContext } from &quot;next&quot;;<br>import { getServerSession, type NextAuthOptions, type DefaultSession } from &quot;next-auth&quot;;<br>import { prisma } from &quot;~/server/db&quot;;<br>// SIWE Integration<br>import type { CtxOrReq } from &quot;next-auth/client/_utils&quot;;<br>import CredentialsProvider from &quot;next-auth/providers/credentials&quot;;<br>import { SiweMessage } from &quot;siwe&quot;;<br>import { getCsrfToken } from &quot;next-auth/react&quot;;<br><br>// Types<br>// ========================================================<br>/**<br> * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`<br> * object and keep type safety.<br> *<br> * @see https://next-auth.js.org/getting-started/typescript#module-augmentation<br> */<br>declare module &quot;next-auth&quot; {<br>  interface Session extends DefaultSession {<br>    user: {<br>      id: string;<br>      // ...other properties<br>      // role: UserRole;<br>    } &amp; DefaultSession[&quot;user&quot;];<br>  }<br><br>  // interface User {<br>  //   // ...other properties<br>  //   // role: UserRole;<br>  // }<br>}<br><br>// Auth Options<br>// ========================================================<br>/**<br> * Options for NextAuth.js used to configure adapters, providers, callbacks, etc.<br> *<br> * @see https://next-auth.js.org/configuration/options<br> */<br>export const authOptions: (ctxReq: CtxOrReq) =&gt; NextAuthOptions = ({ req }) =&gt; ({<br>  callbacks: {<br>    // token.sub will refer to the id of the wallet address<br>    session: ({ session, token }) =&gt; ({<br>      ...session,<br>      user: {<br>        ...session.user,<br>        id: token.sub,<br>      },<br>    } as Session &amp; { user: { id: string; }}),<br>    // OTHER CALLBACKS to take advantage of but not needed<br>    // signIn: async (params: { // Used to control if a user is allowed to sign in<br>    //   user: User | AdapterUser<br>    //   account: Account | null<br>    //   // Not used for credentials<br>    //   profile?: Profile<br>    //   // Not user<br>    //   email?: {<br>    //     verificationRequest?: boolean<br>    //   }<br>    //   /** If Credentials provider is used, it contains the user credentials */<br>    //   credentials?: Record&lt;string, CredentialInput&gt;<br>    // }) =&gt; { return true; },<br>    // redirect: async (params: { // Used for a callback url but not used with credentials<br>    //   /** URL provided as callback URL by the client */<br>    //   url: string<br>    //   /** Default base URL of site (can be used as fallback) */<br>    //   baseUrl: string<br>    // }) =&gt; {<br>    //    return params.baseUrl;<br>    // },<br>    // jwt: async ( // Callback whenever JWT created (i.e. at sign in)<br>    //   params: {<br>    //     token: JWT<br>    //     user: User | AdapterUser<br>    //     account: Account | null<br>    //     profile?: Profile<br>    //     trigger?: &quot;signIn&quot; | &quot;signUp&quot; | &quot;update&quot;<br>    //     /** @deprecated use `trigger === &quot;signUp&quot;` instead */<br>    //     isNewUser?: boolean<br>    //     session?: any<br>    //   }<br>    // ) =&gt; {<br>    //   return params.token;<br>    // }<br>  },<br>  // OTHER OPTIONS (not needed)<br>  // secret: process.env.NEXTAUTH_SECRET, // in case you want pass this along for other functionality<br>  // adapter: PrismaAdapter(prisma), // Not meant for type &#39;credentials&#39; (used for db sessions)<br>  // jwt: { // Custom functionlaity for jwt encoding/decoding<br>  //   encode: async ({ token, secret, maxAge }: JWTEncodeParams) =&gt; {<br>  //     return encode({<br>  //       token,<br>  //       secret,<br>  //       maxAge,<br>  //     })<br>  //   },<br>  //   decode: async ({ token, secret }: JWTDecodeParams) =&gt; {<br>  //     return decode({ token, secret })<br>  //   }<br>  // },<br>  // session: { // Credentials defaults to this strategy<br>  //   strategy: &#39;jwt&#39;,<br>  //   maxAge: 2592000,<br>  //   updateAge: 86400,<br>  //   generateSessionToken: () =&gt; &#39;SomeValue&#39;<br>  // },<br>  // events: { // Callback events<br>  //   signIn: async (message: {<br>  //     user: User<br>  //     account: Account | null<br>  //     profile?: Profile<br>  //     isNewUser?: boolean<br>  //   }) =&gt; {},<br>  //   signOut: async (message: { session: Session; token: JWT }) =&gt; {},<br>  //   createUser:  async (message: { user: User }) =&gt; {},<br>  //   updateUser:  async (message: { user: User }) =&gt; {},<br>  //   linkAccount: async (message: {<br>  //     user: User | AdapterUser<br>  //     account: Account<br>  //     profile: User | AdapterUser<br>  //   }) =&gt; {},<br>  //   session: async (message: { session: Session; token: JWT }) =&gt; {}<br>  // },<br>  providers: [<br>    CredentialsProvider({<br>      // ! Don&#39;t add this<br>      // - it will assume more than one auth provider <br>      // - and redirect to a sign-in page meant for oauth<br>      // - id: &#39;siwe&#39;, <br>      name: &quot;Ethereum&quot;,<br>      type: &quot;credentials&quot;, // default for Credentials<br>      // Default values if it was a form<br>      credentials: {<br>        message: {<br>          label: &quot;Message&quot;,<br>          type: &quot;text&quot;,<br>          placeholder: &quot;0x0&quot;,<br>        },<br>        signature: {<br>          label: &quot;Signature&quot;,<br>          type: &quot;text&quot;,<br>          placeholder: &quot;0x0&quot;,<br>        },<br>      },<br>      authorize: async (credentials) =&gt; {<br>        try {<br>          const siwe = new SiweMessage(JSON.parse(credentials?.message as string ?? &quot;{}&quot;) as Partial&lt;SiweMessage&gt;);<br>          const nonce = await getCsrfToken({ req });<br>          const fields = await siwe.validate(credentials?.signature || &quot;&quot;)<br><br>          if (fields.nonce !== nonce) {<br>            return null;<br>          }<br><br>          // Check if user exists<br>          let user = await prisma.user.findUnique({<br>            where: {<br>              address: fields.address<br>            }<br>          });<br>          // Create new user if doesn&#39;t exist<br>          if (!user) {<br>            user = await prisma.user.create({<br>              data: {<br>                address: fields.address<br>              }<br>            });<br>            // create account<br>            await prisma.account.create({<br>              data: {<br>                userId: user.id,<br>                type: &quot;credentials&quot;,<br>                provider: &quot;Ethereum&quot;,<br>                providerAccountId: fields.address<br>              }<br>            });<br>          }<br><br>          return {<br>            // Pass user id instead of address<br>            // id: fields.address<br>            id: user.id<br>          };<br>        } catch (error) {<br>          // Uncomment or add logging if needed<br>          console.error({ error });<br>          return null;<br>        }<br>      },<br>    })<br>    /**<br>     * ...add more providers here.<br>     *<br>     * Most other providers require a bit more work than the Discord provider. For example, the<br>     * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account<br>     * model. Refer to the NextAuth.js docs for the provider you want to use. Example:<br>     *<br>     * @see https://next-auth.js.org/providers/github<br>     */<br>  ],<br>});<br><br>// Auth Session<br>// ========================================================<br>// ...</pre><p>Restart the server, sign out, and sign back in. We should now see the user id in the session data, instead of our wallet address.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JLL5jW91NMiX_JM_HX4CzA.png" /><figcaption>Create T3 App Session User Id</figcaption></figure><p>We should also see in our database that the new user has been created.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O7HTc0__yqaba19q1pbmDQ.png" /><figcaption>Prisma Studio New User Created</figcaption></figure><h4>Creating Todos TRPC Requests</h4><p>In order to support the creationg and modification of our new Todo table, we’ll create a todosRouter for trpc that has basic CRUD as all, add, remove, and update.</p><p><strong>File:</strong> ./src/server/api/routers/todos.ts</p><pre>// Imports<br>// ========================================================<br>import { z } from &quot;zod&quot;;<br>import { createTRPCRouter, protectedProcedure } from &quot;~/server/api/trpc&quot;;<br><br>// Router<br>// ========================================================<br>export const todosRouter = createTRPCRouter({<br>  /**<br>   * All todos belonging to the session user<br>   */<br>  all: protectedProcedure.query(async ({ ctx }) =&gt; {<br>    const todos = await ctx.prisma.todo.findMany({<br>      where: {<br>        userId: ctx.session.user.id<br>      }<br>    });<br>    return todos;<br>  }),<br>  /**<br>   * Add todo belonging to the session user<br>   */<br>  add: protectedProcedure<br>    .input(z.object({ task: z.string() }))<br>    .mutation(async ({ input, ctx }) =&gt; {<br>      return await ctx.prisma.todo.create({<br>        data: {<br>          task: input.task,<br>          userId: ctx.session.user.id<br>        }<br>      })<br>    }),<br>  /**<br>   * Remove todo belonging to the session user* <br>   */<br>  remove: protectedProcedure<br>    .input(z.object({ id: z.string() }))<br>    .mutation(async ({ input, ctx }) =&gt; {<br>      return await ctx.prisma.todo.deleteMany({<br>        where: {<br>          id: input.id,<br>          userId: ctx.session.user.id<br>        }<br>      })<br>    }),<br>  /**<br>   * Update todo belonging to the session user<br>   */<br>  update: protectedProcedure<br>    .input(z.object({ <br>      id: z.string(), <br>      task: z.string().optional(),<br>      completed: z.boolean().optional()<br>    }))<br>    .mutation(async ({ input, ctx }) =&gt; {<br>      console.log({ input });<br>      const data: { task?: string, completed?: boolean } = {};<br>      if (input.task) {<br>        data.task = input.task;<br>      }<br>      if (typeof input.completed !== &quot;undefined&quot;) {<br>        data.completed = input.completed;<br>      }<br><br>      return await ctx.prisma.todo.updateMany({<br>        data,<br>        where: {<br>          id: input.id,<br>          userId: ctx.session.user.id<br>        }<br>      });<br>    })<br>});</pre><p>Next I want to modify our root file to add the router to trpc.</p><p><strong>File:</strong> ./src/server/api/root.ts</p><pre>import { createTRPCRouter } from &quot;~/server/api/trpc&quot;;<br>import { exampleRouter } from &quot;~/server/api/routers/example&quot;;<br>import { todosRouter } from &quot;~/server/api/routers/todos&quot;;<br><br>/**<br> * This is the primary router for your server.<br> *<br> * All routers added in /api/routers should be manually added here.<br> */<br>export const appRouter = createTRPCRouter({<br>  example: exampleRouter,<br>  todos: todosRouter<br>});<br><br>// export type definition of API<br>export type AppRouter = typeof appRouter;</pre><p>We also need to make a modification to our previous example.ts file as we changed our Example table to Todo now.</p><p><strong>File:</strong> ./src/server/api/routers/example.ts</p><pre>// Imports<br>// ========================================================<br>import { z } from &quot;zod&quot;;<br>import {<br>  createTRPCRouter,<br>  publicProcedure,<br>  protectedProcedure,<br>} from &quot;~/server/api/trpc&quot;;<br><br>// Router<br>// ========================================================<br>export const exampleRouter = createTRPCRouter({<br>  hello: publicProcedure<br>    .input(z.object({ text: z.string() }))<br>    .query(({ input }) =&gt; {<br>      return {<br>        greeting: `Hello ${input.text}`,<br>      };<br>    }),<br>  getSecretMessage: protectedProcedure.query(() =&gt; {<br>    return &quot;you can now see this secret message!&quot;;<br>  }),<br>});</pre><h4>New Protected Todo Page With Session Support</h4><p>With the trpc request built and the user data being stored in the database, we can now create a page that is protected if the user isn’t signed in, and shows their todos if they are.</p><pre># FROM: ./t3-siwe<br><br>touch ./src/pages/todos.tsx;</pre><p>If we paste the following code, we should see the functionality for the session access required and the user todo trpc requests.</p><p><strong>File:</strong> ./src/pages/todos.tsx</p><pre>// Imports<br>// ========================================================<br>import { type NextPage } from &quot;next&quot;;<br>import Link from &quot;next/link&quot;;<br>import { useSession } from &quot;next-auth/react&quot;;<br>import { api } from &quot;~/utils/api&quot;;<br>import { useEffect, useState } from &quot;react&quot;;<br><br>// Page Component<br>// ========================================================<br>const Todos: NextPage = () =&gt; {<br>  // State / Props / Hooks<br>  const { data: sessionData } = useSession();<br>  const [todos, setTodos] = useState&lt;{ task: string; id: string; completed: boolean; }[]&gt;([]);<br>  const [newTodo, setNewTodo] = useState(&#39;&#39;);<br><br>  // Requests<br>  // - All<br>  const {<br>    data: todosAllData,<br>    isLoading: todosAllIsLoading,<br>    refetch: todosAllRefetch<br>  } = api.todos.all.useQuery(<br>    undefined, // no input<br>    {<br>      // Disable request if no session data<br>      enabled: sessionData?.user !== undefined,<br>      onSuccess: () =&gt; {<br>        setNewTodo(&#39;&#39;); // reset input form<br>      }<br>    },<br>  );<br>  // - Add<br>  const {<br>    mutate: todosAddMutate,<br>    isLoading: todosAddIsLoading,<br>  } = api.todos.add.useMutation({<br>    onSuccess: async () =&gt; {<br>      await todosAllRefetch();<br>    }<br>  });<br>  // - Remove<br>  const {<br>    mutate: todosRemoveMutate,<br>    isLoading: todosRemoveIsLoading,<br>  } = api.todos.remove.useMutation({<br>    onSuccess: async () =&gt; {<br>      await todosAllRefetch();<br>    }<br>  });<br>  // - Update<br>  const {<br>    mutate: todosUpdateMutate,<br>    isLoading: todosUpdateIsLoading,<br>  } = api.todos.update.useMutation({<br>    onSuccess: async () =&gt; {<br>      await todosAllRefetch();<br>    }<br>  });<br>  // Handle loading for all requests to disable buttons and inputs<br>  const isRequestLoading = todosAllIsLoading || todosAddIsLoading || todosRemoveIsLoading || todosUpdateIsLoading;<br><br>  // Functions<br>  /**<br>   * <br>   * @param event <br>   */<br>  const onSubmitForm = (event: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {<br>    console.group(&#39;onSubmitForm&#39;);<br>    event.preventDefault();<br><br>    const formData = new FormData(event.currentTarget);<br>    const todoValue = formData.get(&#39;todo&#39;) as string || &#39;&#39;;<br>    console.log({ todoValue });<br><br>    todosAddMutate({ task: todoValue });<br>    console.groupEnd();<br>  };<br><br>  /**<br>   * <br>   * @param id <br>   * @returns <br>   */<br>  const onClickToggleDone = (id: string) =&gt; () =&gt; {<br>    console.group(&#39;onClickToggleDone&#39;);<br>    console.log({ id });<br><br>    todosUpdateMutate({<br>      id,<br>      completed: !todos.find(i =&gt; i.id === id)?.completed<br>    })<br>    console.groupEnd();<br>  };<br><br>  /**<br>   * <br>   * @param id <br>   * @returns <br>   */<br>  const onClickDelete = (id: string) =&gt; () =&gt; {<br>    console.group(&#39;onClickDelete&#39;);<br>    console.log({ id });<br><br>    todosRemoveMutate({<br>      id<br>    });<br>    console.groupEnd();<br>  };<br><br>  // Hooks<br>  useEffect(() =&gt; {<br>    console.log({ todosAllData });<br>    if (todosAllIsLoading) return;<br>    setTodos(todosAllData || []);<br>  // eslint-disable-next-line react-hooks/exhaustive-deps<br>  }, [todosAllData]);<br><br>  // When rendering client side don&#39;t display anything until loading is complete<br>  // if (typeof window !== &quot;undefined&quot; &amp;&amp; loading) return null<br>  // If no session exists, display access denied message<br>  return (<br>    &lt;&gt;<br>      &lt;main className=&quot;flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]&quot;&gt;<br>        &lt;div className=&quot;container flex flex-col items-center justify-center gap-12 px-4 py-16&quot;&gt;<br>          &lt;h1 className=&quot;text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]&quot;&gt;<br>            {!sessionData ? &#39;Access Denied&#39; : &#39;Todos&#39;}<br>          &lt;/h1&gt;<br>        &lt;/div&gt;<br>        &lt;div className=&quot;block mb-4 h-10&quot;&gt;<br>          &lt;Link className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot; href=&quot;/&quot;&gt;Home Page&lt;/Link&gt;<br>        &lt;/div&gt;<br>        {sessionData ? &lt;div className=&quot;w-full flex justify-center items-center flex-col&quot;&gt;<br>          &lt;form onSubmit={onSubmitForm} className=&quot;w-full max-w-md block mb-4&quot;&gt;<br>            &lt;div className=&quot;mb-4&quot;&gt;<br>              &lt;label className=&quot;block text-white/50 mb-2&quot;&gt;New Todo&lt;/label&gt;<br>              &lt;input disabled={isRequestLoading} onChange={(e) =&gt; setNewTodo(e.target.value)} className=&quot;disabled:opacity-30 h-10 bg-white rounded px-4 w-full&quot; type=&quot;text&quot; name=&quot;todo&quot; value={newTodo} /&gt;<br>            &lt;/div&gt;<br>            &lt;div className=&quot;mb-4&quot;&gt;<br>              &lt;button disabled={isRequestLoading} type=&quot;submit&quot; className=&quot;disabled:opacity-30 disabled:hover:bg-white/10 mr-4 w-full rounded-full bg-white/10 px-6 font-semibold text-white no-underline transition hover:bg-white/20 h-10&quot;&gt;Add&lt;/button&gt;<br>            &lt;/div&gt;<br>          &lt;/form&gt;<br>          {todos.length === 0<br>            ? &lt;p className=&quot;text-white&quot;&gt;(No todos yet!)&lt;/p&gt;<br>            : &lt;ul className=&quot;w-full max-w-md block&quot;&gt;<br>              {todos.map((todo, key) =&gt; &lt;li key={`todo-${key}-${todo.id}`} className={`flex justify-between items-center ${key % 2 === 0 ? &#39;bg-white/5 hover:bg-white/10&#39; : &#39;bg-white/10 hover:bg-white/20&#39;} transition-colors ease-in-out duration-200 rounded-tl rounded-tr p-4 border-b border-b-white/50`}&gt;<br>                &lt;span className={`text-white font-semibold ${todo.completed ? &#39;line-through&#39; : &#39;&#39;}`}&gt;{todo.task}&lt;/span&gt;<br>                &lt;span&gt;<br>                  &lt;button disabled={isRequestLoading} onClick={onClickDelete(todo.id)} className=&quot;disabled:opacity-30 disabled:hover:bg-white/10 mr-4 rounded-full bg-white/10 px-6 font-semibold text-white no-underline transition hover:bg-white/20 h-10&quot;&gt;Delete&lt;/button&gt;<br>                  &lt;button disabled={isRequestLoading} onClick={onClickToggleDone(todo.id)} className=&quot;disabled:opacity-30 disabled:hover:bg-white/10 rounded-full bg-white/10 px-6 font-semibold text-white no-underline transition hover:bg-white/20 h-10&quot;&gt;{todo.completed ? &#39;Undo&#39; : &#39;Done&#39;}&lt;/button&gt;<br>                &lt;/span&gt;<br>              &lt;/li&gt;)}<br>            &lt;/ul&gt;<br>          }<br>        &lt;/div&gt; : null}<br>      &lt;/main&gt;<br>    &lt;/&gt;<br>  );<br>};<br><br>// Exports<br>// ========================================================<br>export default Todos;</pre><p>We’re also going to make one small modification to our index.tsx to give it a link to the todos page.</p><p><strong>File:</strong> ./src/pages/index.tsx</p><pre>// ...<br><br>// Page Component<br>// ========================================================<br>const Home: NextPage = () =&gt; {<br>  // Requests<br>  const hello = api.example.hello.useQuery({ text: &quot;from tRPC&quot; });<br><br>  // Render<br>  return (<br>    &lt;&gt;<br>      &lt;Head&gt;<br>        &lt;title&gt;Create T3 App SIWE&lt;/title&gt;<br>        &lt;meta name=&quot;description&quot; content=&quot;Generated by create-t3-app&quot; /&gt;<br>        &lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&gt;<br>      &lt;/Head&gt;<br>      &lt;main className=&quot;flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]&quot;&gt;<br>        &lt;div className=&quot;container flex flex-col items-center justify-center gap-12 px-4 py-16 &quot;&gt;<br>          &lt;h1 className=&quot;text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]&quot;&gt;<br>            Create &lt;span className=&quot;text-[hsl(280,100%,70%)]&quot;&gt;T3&lt;/span&gt; App SIWE<br>          &lt;/h1&gt;<br>          &lt;div className=&quot;grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8&quot;&gt;<br>            &lt;Link<br>              className=&quot;flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20&quot;<br>              href=&quot;https://create.t3.gg/en/usage/first-steps&quot;<br>              target=&quot;_blank&quot;<br>            &gt;<br>              &lt;h3 className=&quot;text-2xl font-bold&quot;&gt;First Steps →&lt;/h3&gt;<br>              &lt;div className=&quot;text-lg&quot;&gt;<br>                Just the basics - Everything you need to know to set up your<br>                database and authentication.<br>              &lt;/div&gt;<br>            &lt;/Link&gt;<br>            &lt;Link<br>              className=&quot;flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20&quot;<br>              href=&quot;https://create.t3.gg/en/introduction&quot;<br>              target=&quot;_blank&quot;<br>            &gt;<br>              &lt;h3 className=&quot;text-2xl font-bold&quot;&gt;Documentation →&lt;/h3&gt;<br>              &lt;div className=&quot;text-lg&quot;&gt;<br>                Learn more about Create T3 App, the libraries it uses, and how<br>                to deploy it.<br>              &lt;/div&gt;<br>            &lt;/Link&gt;<br>          &lt;/div&gt;<br>          &lt;div className=&quot;flex flex-col items-center gap-2&quot;&gt;<br>            &lt;div className=&quot;block mb-4 h-10&quot;&gt;<br>              {/* HERE - new todo link */}<br>              &lt;Link className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot; href=&quot;/todos&quot;&gt;(Protected) Todos Page&lt;/Link&gt;<br>            &lt;/div&gt;<br>            &lt;p className=&quot;text-2xl text-white block mb-4&quot;&gt;<br>              {hello.data ? hello.data.greeting : &quot;Loading tRPC query...&quot;}<br>            &lt;/p&gt;<br>            &lt;AuthShowcase /&gt;<br>          &lt;/div&gt;<br>        &lt;/div&gt;<br>      &lt;/main&gt;<br>    &lt;/&gt;<br>  );<br>};<br><br>// Exports<br>// ========================================================<br>export default Home;</pre><p>Now if we take a look at our full app we should see the home page, access denied if no session to the todo page, and managing todos stored to the database.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4YcR-kREqvhg3dq7hZETiQ.png" /><figcaption>Create T3 App With SIWE Not Logged In</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A16NBOqaG5vSLaX0dw4n5A.png" /><figcaption>Create T3 App With SIWE Todo Page Access Denied With No Session</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JLL5jW91NMiX_JM_HX4CzA.png" /><figcaption>Create T3 App With SIWE Logged In</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*peKPlOyyGsOW-MQPULb4eQ.png" /><figcaption>Create T3 App With SIWE Todo Page With Todos Stored To The Database</figcaption></figure><h4>Bonus Blockies Image</h4><p>One last thing, I’m going to add is support for a <a href="http://github.com/codingwithmanny/blockies"><strong><em>blockies</em></strong></a> image generated by the user’s wallet address.</p><pre># FROM: ./t3-siwe<br><br>pnpm add @codingwithmanny/blockies; # npm install @codingwithmanny/blockies;</pre><p><strong>File:</strong> ./src/pages/index.tsx</p><pre>// Imports<br>// ========================================================<br>import { type NextPage } from &quot;next&quot;;<br>import Head from &quot;next/head&quot;;<br>import Link from &quot;next/link&quot;;<br>// Add this new package<br>import Image from &quot;next/image&quot;;<br>import { useEffect, useState } from &quot;react&quot;;<br>import { getCsrfToken, signIn, signOut, useSession } from &quot;next-auth/react&quot;;<br>import { api } from &quot;~/utils/api&quot;;<br>// Add this new package<br>import { renderDataURI } from &quot;@codingwithmanny/blockies&quot;;<br>// SIWE Integration<br>import { SiweMessage } from &quot;siwe&quot;;<br>import { useAccount, useConnect, useDisconnect, useSignMessage, useNetwork } from &quot;wagmi&quot;;<br>import { InjectedConnector } from &#39;wagmi/connectors/injected&#39;;<br><br>// Auth Component<br>// ========================================================<br>const AuthShowcase: React.FC = () =&gt; {<br>  // Hooks<br>  const { data: sessionData } = useSession();<br>  const { data: secretMessage } = api.example.getSecretMessage.useQuery(<br>    undefined, // no input<br>    { enabled: sessionData?.user !== undefined },<br>  );<br>  // State<br>  const [showConnection, setShowConnection] = useState(false);<br><br>  // Wagmi Hooks<br>  const { signMessageAsync } = useSignMessage();<br>  const { address, isConnected } = useAccount();<br>  const { connect } = useConnect({<br>    connector: new InjectedConnector(),<br>  });<br>  const { disconnect } = useDisconnect();<br>  const { chain } = useNetwork();<br><br>  // Functions<br>  /**<br>   * Attempts SIWE and establish session<br>   */<br>  const onClickSignIn = async () =&gt; {<br>    try {<br>      const message = new SiweMessage({<br>        domain: window.location.host,<br>        address: address,<br>        statement: &quot;Sign in with Ethereum to the app.&quot;,<br>        uri: window.location.origin,<br>        version: &quot;1&quot;,<br>        chainId: chain?.id,<br>        // nonce is used from CSRF token<br>        nonce: await getCsrfToken(),<br>      })<br>      const signature = await signMessageAsync({<br>        message: message.prepareMessage(),<br>      })<br>      await signIn(&quot;credentials&quot;, {<br>        message: JSON.stringify(message),<br>        redirect: false,<br>        signature,<br>      });<br>    } catch (error) {<br>      window.alert(error);<br>    }<br>  };<br><br>  /**<br>   * Sign user out<br>   */<br>  const onClickSignOut = async () =&gt; {<br>    await signOut();<br>  };<br><br>  // Hooks<br>  /**<br>   * Handles hydration issue<br>   * only show after the window has finished loading<br>   */<br>  useEffect(() =&gt; {<br>    setShowConnection(true);<br>  }, []);<br><br>  // Render<br>  return (<br>    &lt;div className=&quot;flex flex-col items-center justify-center gap-4&quot;&gt;<br>      {sessionData<br>        ? &lt;div className=&quot;mb-4 text-center&quot;&gt;<br>          {sessionData ? &lt;div className=&quot;mb-4&quot;&gt;<br>            &lt;label className=&quot;block text-white/80 mb-2&quot;&gt;Logged in as&lt;/label&gt;<br>            {/* Add this new line */}<br>            {sessionData?.user?.id ? &lt;Image width={&quot;80&quot;} height={&quot;80&quot;} alt={`${sessionData.user.id}`} className=&quot;mx-auto my-4 border-8 border-white/30&quot; src={`${renderDataURI({ seed: sessionData.user.id, size: 10, scale: 8 })}`} /&gt; : null}<br>            &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{JSON.stringify(sessionData)}&lt;/code&gt;<br>          &lt;/div&gt; : null}<br>          {secretMessage ? &lt;p className=&quot;mb-4&quot;&gt;<br>            &lt;label className=&quot;block text-white/80 mb-2&quot;&gt;Secret Message&lt;/label&gt;<br>            &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{secretMessage}&lt;/code&gt;<br>          &lt;/p&gt; : null}<br><br>          &lt;button<br>            className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>            onClick={onClickSignOut as () =&gt; void}<br>          &gt;<br>            Sign Out<br>          &lt;/button&gt;<br>        &lt;/div&gt;<br>        : showConnection<br>          ? &lt;div className=&quot;mb-4&quot;&gt;<br>            {isConnected<br>              ? &lt;button<br>                className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>                onClick={onClickSignIn as () =&gt; void}<br>              &gt;<br>                Sign In<br>              &lt;/button&gt;<br>              : null<br>            }<br>          &lt;/div&gt;<br>          : null<br>      }<br>      {showConnection<br>        ? &lt;div className=&quot;text-center&quot;&gt;<br>          {address<br>            ? &lt;p className=&quot;mb-4&quot;&gt;<br>              &lt;code className=&quot;block p-4 text-white bg-black/20 rounded&quot;&gt;{address}&lt;/code&gt;<br>            &lt;/p&gt;<br>            : null<br>          }<br>          &lt;button<br>            className=&quot;rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20&quot;<br>            onClick={() =&gt; !isConnected ? connect() : disconnect()}<br>          &gt;<br>            {!isConnected ? &#39;Connect Wallet&#39; : &#39;Disconnect&#39;}<br>          &lt;/button&gt;<br>        &lt;/div&gt;<br>        : null}<br>    &lt;/div&gt;<br>  );<br>};<br><br>// Page Component<br>// ========================================================<br>// ...</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wHznJTjgDxJkTyPaqOy0Ig.png" /><figcaption>Create T3 App With SIWE Implementation &amp; Blockies Image</figcaption></figure><p>We’ve successfully completed the entire process of adding SIWE with the T3 Stack.</p><h3>Full Code Repository</h3><p>If you want to see the final product for the full code repository, check out <a href="https://github.com/codingwithmanny/t3-app-siwe"><strong><em>t3-app-siwe</em></strong></a>.</p><p><a href="https://github.com/codingwithmanny/t3-app-siwe">GitHub - codingwithmanny/t3-app-siwe: Implementation of Sign-In With Ethereum with Create-T3-Stack including Prisma, tRPC, NextAuth, Tailwind, and TypeScript</a></p><h3>What’s Next?</h3><p>This was a first attempt in trying to get more adoption for developers to start using web3 tools within more traditional web3 stacks, libraries, and frameworks to make it easier to get started.</p><p>The goal would be either to build a Auth Provider that works slightly different for wallets and signing messages like Sign-In With Ethereum, and just overall get better adoption for web2 and web3 tools working together.</p><p>If you got value from this, please also follow me on twitter (where I’m quite active) <a href="http://twitter.com/codingwithmanny">@codingwithmanny</a> and instagram at <a href="https://instagram.com/codingwithmanny">@codingwithmanny</a>.</p><p>Oh! I’m also on YouTube now as <a href="https://www.youtube.com/@codingwithmanny"><strong><em>@codingwithmanny</em></strong></a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8f54604caeeb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Ultimate ERC1155 Mythic Card NFT Contract Walkthrough On Polygon zkEVM]]></title>
            <link>https://codingwithmanny.medium.com/the-ultimate-erc1155-mythic-card-nft-contract-walkthrough-on-polygon-zkevm-96a7cdd1ef20?source=rss-da45729a0220------2</link>
            <guid isPermaLink="false">https://medium.com/p/96a7cdd1ef20</guid>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[solidity]]></category>
            <category><![CDATA[nft]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[zkevm]]></category>
            <dc:creator><![CDATA[Manny]]></dc:creator>
            <pubDate>Sun, 02 Apr 2023 14:07:46 GMT</pubDate>
            <atom:updated>2023-08-31T16:48:32.412Z</atom:updated>
            <content:encoded><![CDATA[<h4>Using Midjourney &amp; ChatGPT — How I Created A Mythic Cards NFT Collection On Polygon zkEVM</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6KhoHG_mIF3i_V6NrhrpFg.png" /><figcaption>Build A Mythic NFT Card Collection With ERC1155 On Polygon zkEVM</figcaption></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FhqEYYREoTxs%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DhqEYYREoTxs&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FhqEYYREoTxs%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" width="854" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/85efee0139dca9b7ebed87e72a23110b/href">https://medium.com/media/85efee0139dca9b7ebed87e72a23110b/href</a></iframe><h3>What Is Polygon zkEVM</h3><p><a href="https://wiki.polygon.technology/docs/zkEVM/develop"><strong><em>Polygon zkEVM </em></strong></a>is the latest network released from Polygon that helps scale Ethereum transactions, at a cheaper cost, and while leveraging the security of Ethereum itself.</p><p>What this means for NFT creators is that this is an interesting way to save on costs for transactions, the ability to bridge tokens back and forth (NFTs bridging coming), and taking advantage of native ETH to handle payments through bridging.</p><p>Cheaper, faster, and build like you would on Ethereum.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lcLddBd4H3sMPIcy-kFXrg.png" /><figcaption><a href="https://polygon.technology/polygon-zkevm">https://polygon.technology/polygon-zkevm</a></figcaption></figure><h3>What Is An ERC1155 Contract</h3><p>An <a href="https://ethereum.org/en/developers/docs/standards/tokens/erc-1155/"><strong><em>ERC1155</em></strong></a> contract is an NFT token that takes the best of ERC20 and ERC721 and allows you to manage multiple tokens that are fungible or non-fungible in one contract.</p><p>This functionality is ideal for games that want to integrate some sort of ownership or NFT component for different items that may have multiple owners, for example like have 10 broad swords weapons that have the same attributes but will belong to different people.</p><h3>Creating Design Assets For Mythic Cards</h3><p>This article will show you how I created the design graphics with <a href="https://www.midjourney.com/home/?callbackUrl=%2Fapp%2F"><strong><em>Midjourney</em></strong></a>, the details and description with <a href="http://chat.openai.com"><strong><em>ChatGPT</em></strong></a>, and the card animations. In true NFT fashion I also show you how I uploaded the files to Arweave to make the files more permanent.</p><p>If you want to skip the design aspect and go straight into the contract, see the Building An ERC1155 NFT Collection On Polygon zkEVM section.</p><h4>Midjourney Card Design Prompts</h4><p>I took a lot of inspiration from the Blizzard game <a href="https://hearthstone.blizzard.com/en-us"><strong><em>Hearstone</em></strong></a> to design these items. With Midjourney, I knew that I needed both the items themselves, the surrounding decorative graphic that would make them look consistent, and a standard design for the back of the cards.</p><p><strong>Midjourney Item Prompts</strong></p><p>I tried a few prompts but the one that seem to work the best was the following:</p><pre>// an axe with no characters in the photo and just the item iteself, in the style of blizzard heartstone<br><br>// OR<br><br>// purple fiery dagger with no characters in the photo and just the item itself without any other items, in the style of blizzard heartstone</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oOENmX-Mndq3_aOleH8Kdw.png" /><figcaption>Midjourney Generating Sword Graphic</figcaption></figure><p>Sword, axe, and skull I got to generate had no problem, but Midjourney had an issue with the word Bow (even tried Archery Bow) for a bow and arrow.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r8DyJZRjtVFBwIQjLIZXUg.png" /><figcaption>Midjourney Difficulty Generating A Bow &amp; Arrow</figcaption></figure><p>With the sword, axe, skull, and a heart looking item, I landed on these four items.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TX7hoRol-dIiFwM-ecAlPQ.png" /><figcaption>Mythic Card Items Generated With Midjourney</figcaption></figure><p><strong>Generating Decorations &amp; Card Back</strong></p><p>Once I had all four items, I made sure to ask it to generate some random rock formations that I knew I could mold to cover the items. I also made a prompt for some nice wooden doors for the back of the cards.</p><pre>// a decorative rock background that takes up the entire photo, with no characters in the photo, just the item itself, and without any other items, in the style of blizzard heartstone</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f5R7C0yZOWYYpIZ9pVZadw.png" /><figcaption>Midjourney Generating Rock Formation Image</figcaption></figure><pre>// a decorative wooden background with no characters in the photo and just the item itself without any other items, facing forward, in the style of blizzard heartstone</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*goXX3vP4E0PJ2uS-nRHXPQ.png" /><figcaption>Midjourney Generating Wooden Door Image For Back Of Card</figcaption></figure><h4>Editing Images In Photoshop</h4><p>The cards weren’t complete, because I still needed to edit them a bit in Photoshop to increase the height I was looking for, creating the decorative border, and adding some slight glows to them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gJp0v2UxJQjU_RUfkxXFfA.gif" /><figcaption>[GIF] Editing Cards With Photoshop</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wpG5oIek1wRzdK6ToF6G0g.png" /><figcaption>Final Card Graphics Edited In Photoshop</figcaption></figure><h4>Creating SVG Card Animation</h4><p>This took me a bit longer to do because there is this css property called backface-visibility, which unfortunately doesn’t work within SVGs.</p><p>In order to get the card animation I wanted, I needed to create a rotating rectangle (later switched for an image), that would rotate up to the point where it was sideways, go completely transparent, and then reappear to complete the full rotation cycle. With two of these rectangles, I could mimic the backface-visibility and make it appear as if the rotation was smoothly transitioning back and forth between the front and back of the card.</p><p><strong>Card Animation Iteration 1 — Basic Rotation</strong></p><p>Getting the one side of the card to rotate in the middle and go invisible when rotated on its side.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fcard-animation-iteration-01-dofe95%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2Fdofe95&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2Fdofe95%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/535759cb3be9113ec58088f7f309ffca/href">https://medium.com/media/535759cb3be9113ec58088f7f309ffca/href</a></iframe><p><strong>Card Animation Iteration 2 — Alternating</strong></p><p>Getting both the front end back of the card to iterate back and forth between showing and hiding their respective sides.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fcard-animation-iteration-02-v4zz5c%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2Fv4zz5c&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2Fv4zz5c%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/46aafb13f2d141becdc05edd2bfb46d3/href">https://medium.com/media/46aafb13f2d141becdc05edd2bfb46d3/href</a></iframe><p><strong>Card Animation Iteration 3— Supporting Graphics</strong></p><p>Taking advantage of being able to use base64 encoding for image data, we can replace the rect tags with image tags with a href attribute starting with the following:</p><pre>&lt;image href=&quot;data:image/jpeg;base64,...&quot;</pre><p>Using <a href="https://base64.guru"><strong><em>base64.guru</em></strong></a> we can upload a file and get it to generate the base64 encoding we need for the image.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Hd9uu9Viis2oFGWy_7WZ4w.png" /><figcaption><a href="https://base64.guru/converter/encode/image">https://base64.guru/converter/encode/image</a></figcaption></figure><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fcard-animation-iteration-03-2qeluq%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fcard-animation-iteration-03-2qeluq%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2F2qeluq%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/14557b591a205a39c76f4c43b5acbccb/href">https://medium.com/media/14557b591a205a39c76f4c43b5acbccb/href</a></iframe><p><strong>Card Animation Iteration 4— Styling</strong></p><p>The last step is to get the round edges applied and give it a radial gradient background. Unfortunately there is no easy way to do a rounded border radius with the image tag, so we’ll need to use a clipPath rect applied to both image tags.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcodesandbox.io%2Fembed%2Fsuspicious-tom-kcnijt%3Ffontsize%3D14%26hidenavigation%3D1%26theme%3Ddark&amp;display_name=CodeSandbox&amp;url=https%3A%2F%2Fcodesandbox.io%2Fs%2Fkcnijt&amp;image=https%3A%2F%2Fcodesandbox.io%2Fapi%2Fv1%2Fsandboxes%2Fkcnijt%2Fscreenshot.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=codesandbox" width="1000" height="500" frameborder="0" scrolling="no"><a href="https://medium.com/media/7aeb97767cf56ed13e6afea8f562fb14/href">https://medium.com/media/7aeb97767cf56ed13e6afea8f562fb14/href</a></iframe><p>With the animation and format done, I can save all 4 items as separate SVG images. We’ll put all four images into an SVG folder.</p><pre># ./zkevm-erc1155<br># svg<br># ├── axe.svg<br># ├── heart.svg<br># ├── skull.svg<br># └── sword.svg</pre><h3>Uploading Our Graphics &amp; Metadata To Arweave Permaweb</h3><p>Now that we have the images, we need to store these SVG images in a place where we can reference them later for the metadata JSON file that we need for OpenSea and other NFT readers to download and display it correctly.</p><p><strong>NOTE:</strong> For these next steps, in order to upload we’ll need either real Ethereum ETH or Polygon MATIC in order to upload to Arweave.</p><h4>Dependency Install &amp; Setup</h4><pre># FROM: ./zkevm-erc1155<br><br>npm init -y;<br>npm install dotenv @bundlr-network/client bignumber.js; <br>npm install -D @types/node ts-node typescript;<br>./node_modules/.bin/tsc --init;<br>mkdir upload; # Folder storing our file upload scripts<br>touch .env;</pre><p>In our .env file we’ll add the private key of the wallet which has MATIC to pay for the upload.</p><p><strong>File:</strong> ./.env</p><pre>WALLET_PRIVATE_KEY=&quot;&lt;YOUR-WALLET-PRIVATE-KEY&gt;&quot;</pre><h4>Uploading Files To Arweave</h4><p>In our new upload folder, we’ll create an file uploader that will read the svg folder’s files, estimate their cost, fund a <a href="https://bundlr.network"><strong><em>Bundlr</em></strong></a> node, and upload them to Arweave.</p><p><strong>File:</strong> ./upload/arweaveFiles.ts</p><pre>// Imports<br>// ========================================================<br>import Bundlr from &quot;@bundlr-network/client&quot;;<br>import fs from &quot;fs&quot;;<br>import path from &#39;path&#39;;<br>import dotenv from &#39;dotenv&#39;;<br>import BigNumber from &quot;bignumber.js&quot;;<br><br>// Config<br>// ========================================================<br>dotenv.config();<br>const ARWEAVE_TX_URL = &quot;https://arweave.net/&quot;;<br>const privateKey = process.env.WALLET_PRIVATE_KEY;<br>const bundlr = new Bundlr(&quot;http://node1.bundlr.network&quot;, &quot;matic&quot;, privateKey); // NOTE: You need matic in your wallet to upload<br><br>// Main Upload Script<br>// ========================================================<br>(async () =&gt; {<br>    try {<br>        // Retrieve current balance<br>        console.group(&#39;Current Balance&#39;);<br>        const atomicBalance = await bundlr.getLoadedBalance();<br>        console.log({ atomicBalance });<br>        console.log({ atomicBalance: atomicBalance.toString() });<br>        const convertedBalance = bundlr.utils.unitConverter(atomicBalance);<br>        console.log({ convertedBalance });<br>        console.log({ convertedBalance: convertedBalance.toString() });<br>        console.groupEnd();<br><br>        // Get all files<br>        console.group(&#39;Files&#39;);<br>        const folderPath = path.join(__dirname, &#39;..&#39;, &#39;svg&#39;);<br>        const files = await fs.readdirSync(folderPath);<br>        console.log({ files });<br>        const svgFiles = files.filter(i =&gt; i.includes(&#39;.svg&#39;));<br>        console.log({ svgFiles });<br>        console.groupEnd();<br><br>        // Get total file size for all files<br>        console.group(&#39;Funding Node&#39;);<br>        let size = 0;<br>        for (let i = 0; i &lt; svgFiles.length; i++) {<br>            const fileToUpload = path.join(__dirname, &#39;..&#39;, &#39;svg&#39;, svgFiles[i]);<br>            const fileSize = await fs.statSync(fileToUpload);<br>            size += fileSize.size;<br>        }<br>        console.log({ size });<br>        const price = await (await bundlr.getPrice(size)).toNumber() / 1000000000000000000;<br>        console.log({ price });<br>        <br>        // Fund if needed<br>        if (price &gt; parseFloat(convertedBalance.toString())) {<br>            console.log(&#39;Funding...&#39;);<br>            const fundAmountParsed = BigNumber(price as any).multipliedBy(bundlr.currencyConfig.base[1]);<br>            console.log({ fundAmountParsed: fundAmountParsed.toString() });<br>            await bundlr.fund(fundAmountParsed.toString());<br>            const convertedBalance = bundlr.utils.unitConverter(atomicBalance);<br>            console.log({ convertedBalance: convertedBalance.toString() });<br>        }<br>        console.groupEnd();<br><br>        console.group(&#39;Uploading...&#39;);<br>        for (let i = 0; i &lt; svgFiles.length; i++) {<br>            const fileToUpload = path.join(__dirname, &#39;..&#39;, &#39;svg&#39;, svgFiles[i]);<br>            const response = await bundlr.uploadFile(fileToUpload);<br>            console.log(`${ARWEAVE_TX_URL}${response.id}`);<br>        }<br>        console.groupEnd();<br>    } catch (e) {<br>        console.error(&quot;Error uploading file &quot;, e);<br>    }<br>})();</pre><p>We’ll make it slightly easier on ourselves to add the upload command to our package.json.</p><p><strong>File:</strong> ./package.json</p><pre>// ...<br>  &quot;scripts&quot;: {<br>    &quot;uploadFiles&quot;: &quot;./node_modules/.bin/ts-node upload/arweaveFiles.ts&quot;<br>  },<br>// ...</pre><p>If we run uploadFiles we should get the following:</p><pre># FROM ./zkevm-erc1155<br><br>npm run uploadFiles;<br><br># Expected Output:<br># Current Balance<br>#   { atomicBalance: BigNumber { s: 1, e: 15, c: [ 15, 71544535461003 ] } }<br>#   { atomicBalance: &#39;1571544535461003&#39; }<br>#   {<br>#     convertedBalance: BigNumber { s: 1, e: -3, c: [ 157154453546, 10030000000000 ] }<br>#   }<br>#   { convertedBalance: &#39;0.001571544535461003&#39; }<br># Files<br>#   { files: [ &#39;axe.svg&#39;, &#39;heart.svg&#39;, &#39;skull.svg&#39;, &#39;sword.svg&#39; ] }<br>#   { svgFiles: [ &#39;axe.svg&#39;, &#39;heart.svg&#39;, &#39;skull.svg&#39;, &#39;sword.svg&#39; ] }<br># Funding Node<br>#   { size: 2550214 }<br>#   { price: 0.007514900904697801 }<br>#   Funding...<br>#   { fundAmountParsed: &#39;7514900904697801&#39; }<br>#   { convertedBalance: &#39;0.001571544535461003&#39; }<br># Uploading...<br>#   https://arweave.net/JtA8psYtbOP9i3QqxBAnkWVSVbNtL7-F_x-I3JMadE4<br>#   https://arweave.net/CcNM3Jaq5qAyfmAlUVPFqBEFz51ikZrGYw6cRsqQius<br>#   https://arweave.net/jFtwyxLxhzbzP94LYl0HcBuODwJsGT7JLdsWxWXhZK8<br>#   https://arweave.net/mXQ0uptvLKnMCc2LxqGxMwp6He2upfz_AeFV6MEgv8g</pre><p><strong>NOTE:</strong> If the upload fails, there is a good chance that the node for Bundlr hasn’t been fully funded and you may need to adjust the funds that need to be sent in order to pay for the upload.</p><p>If we take one of the Arweave links, we can see that our image has been successfully uploaded.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SVkDvWpNTcKXP8iPcm_iBg.png" /><figcaption><a href="https://arweave.net/JtA8psYtbOP9i3QqxBAnkWVSVbNtL7-F_x-I3JMadE4">https://arweave.net/JtA8psYtbOP9i3QqxBAnkWVSVbNtL7-F_x-I3JMadE4</a></figcaption></figure><h4>Creating NFT Metadata</h4><p>Now that we have the image urls, we need to create Metadata for each item as a separate JSON file that references the Arweave image. We also need to make sure that each JSON file is stored as the token ID (as an integer) for the filename <em>(Ex: 1.json, 2.json, …).</em></p><p>We’ll store all these JSON files into a folder called manifest and we’ll use ChatGPT to create some descriptions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WiT3hb1XvRz3ZxxVwpSOjw.png" /><figcaption>ChatGTP Item Description</figcaption></figure><p>Do this for all items, and create a JSON file for each in the following JSON format:</p><p><strong>File:</strong> ./manifest/1.json</p><pre>{<br>    &quot;description&quot;: &quot;The Blade of Souls is a legendary weapon steeped in mystery and lore. It is said to have been created by the gods themselves, imbued with the power to vanquish even the most malevolent of beings.&quot;, <br>    &quot;image&quot;: &quot;https://arweave.net/h3kkGN_QRfhYWbZlQb5N2bSaqj6HNB4_pGxLfsdhWuo&quot;, <br>    &quot;name&quot;: &quot;Blade Of Souls&quot;,<br>    &quot;attributes&quot;: [<br>        {<br>            &quot;trait_type&quot;: &quot;Damage&quot;, <br>            &quot;value&quot;: 12<br>        }<br>    ]<br>}</pre><p>In that same folder, we’ll also add a contract.json file with metadata for the contract itself.</p><p><strong>File:</strong> ./manifest/contract.json</p><pre>{<br>    &quot;name&quot;: &quot;zkMythicCards&quot;,<br>    &quot;description&quot;: &quot;zkMythicCards is a mystical card game played by the gods themselves, where players use their elemental powers to summon creatures and cast spells to defeat their opponents and claim ultimate victory&quot;<br>}</pre><h4>Uploading Folder To Arweave</h4><p>With JSON files 1–4 created and referencing their respective items and descriptions, we can now use Arweave to upload the folder so that it references an integer which we can use from our token ID in our ERC1155 contract.</p><p>In our upload folder, we’ll create a new file called uploadFolder.ts.</p><p><strong>File:</strong> ./upload/arweaveFolder.ts</p><pre>// Imports<br>// ========================================================<br>import Bundlr from &quot;@bundlr-network/client&quot;;<br>import fs from &quot;fs&quot;;<br>import path from &#39;path&#39;;<br>import dotenv from &#39;dotenv&#39;;<br><br>// Config<br>// ========================================================<br>dotenv.config();<br>const ARWEAVE_TX_URL = &quot;https://arweave.net/&quot;;<br>const privateKey = process.env.WALLET_PRIVATE_KEY;<br>const bundlr = new Bundlr(&quot;http://node1.bundlr.network&quot;, &quot;matic&quot;, privateKey);<br><br>// Main Upload Script<br>// ========================================================<br>(async () =&gt; {<br>    console.group(&#39;Uploading folder...&#39;);<br>    try {<br>        const folderPath = path.join(__dirname, &#39;..&#39;, &#39;manifest&#39;);<br>        const folder = fs.existsSync(folderPath);<br>        console.log({ folderExists: folder });<br>        if (!folder) {<br>            throw new Error(&#39;Folder doesn\&#39;t exist&#39;);<br>        }<br>        const response = await bundlr.uploadFolder(folderPath, {<br>            indexFile: &quot;&quot;, // optional index file (file the user will load when accessing the manifest)<br>            batchSize: 4, //number of items to upload at once<br>            keepDeleted: false, // whether to keep now deleted items from previous uploads<br>        }); //returns the manifest ID<br>    <br>        console.log({ response });<br>        console.log({ URL: `${ARWEAVE_TX_URL}${response?.id}`});<br>    } catch (e) {<br>        console.error(&quot;Error uploading file &quot;, e);<br>    }<br>    console.groupEnd();<br>})();</pre><p>We’ll easier on ourselves again by adding the upload command to our package.json.</p><p><strong>File:</strong> ./package.json</p><pre>// ...<br>  &quot;scripts&quot;: {<br>    &quot;uploadFiles&quot;: &quot;./node_modules/.bin/ts-node upload/arweaveFiles.ts&quot;,<br>    &quot;uploadFolder&quot;: &quot;./node_modules/.bin/ts-node upload/arweaveFolder.ts&quot;,<br>  },<br>// ...</pre><p>Now if we run uploadFolder, we should get the following result:</p><pre># FROM ./zkevm-erc1155<br><br>npm run uploadFolder;<br><br># Expected Output:<br># Uploading folder...<br>#   { folderExists: true }<br>#   {<br>#     response: {<br>#       id: &#39;l9LANRyWrOOOgnaDrOG8QKBRbxTNf7mMeANw781y4bs&#39;,<br>#       timestamp: 1679943617898<br>#     }<br>#   }<br>#   {<br>#     URL: &#39;https://arweave.net/l9LANRyWrOOOgnaDrOG8QKBRbxTNf7mMeANw781y4bs&#39;<br>#   }</pre><p>Using the URL, paste it into your browser and append any of the numbers from 1 to 4 or contract as /1.json to it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MNhyNG3u5skbp7m2R7KZ2g.png" /><figcaption><a href="https://arweave.net/l9LANRyWrOOOgnaDrOG8QKBRbxTNf7mMeANw781y4bs/4.json">https://arweave.net/l9LANRyWrOOOgnaDrOG8QKBRbxTNf7mMeANw781y4bs/4.json</a></figcaption></figure><p>With the URL we’ll also add it to our .env file as the following:</p><p><strong>File:</strong> ./env</p><pre>WALLET_PRIVATE_KEY=&quot;&lt;YOUR-WALLET-PRIVATE-KEY&gt;&quot;<br>BASE_URL=&quot;&lt;YOUR-UNIQUE-BASE-URL-FOR-HOLDING-JSON-FILES-WITH-SLASH-AT-THE-END&gt;&quot;</pre><p>We now have all our design assets and files ready to create the NFT project.</p><h3>Building An ERC1155 NFT Collection On Polygon zkEVM</h3><p>The next step is to create our ERC1155 contract with Hardhat, make some custom modifications, and configure it for zkEVM Testnet deployment.</p><h4>Setting Up Hardhat With ERC1155</h4><p>Because we’re using npx hardhat to create the template base, we’ll need to remove our previous package.json and add those dependencies and some additional ones again.</p><pre># FROM: ./zkevm-erc1155<br><br>rm package.json;<br>rm tsconfig.json;<br>npx hardhat;<br><br># Expeceted Output:<br># ✔ What do you want to do? · Create a TypeScript project<br># ✔ Hardhat project root: · /Users/username/path/to/zkevm-erc1155<br># ✔ Do you want to add a .gitignore? (Y/n) · y<br># ✔ Do you want to install this sample project&#39;s dependencies with npm (hardhat @nomicfoundation/hardhat-toolbox)? (Y/n) · y<br><br>npm install dotenv @bundlr-network/client bignumber.js @openzeppelin/contracts; <br>npm install -D @types/node ts-node typescript;</pre><p><strong>File:</strong> ./package.json</p><pre>// ...<br>  &quot;scripts&quot;: {<br>    &quot;uploadFiles&quot;: &quot;./node_modules/.bin/ts-node upload/arweaveFiles.ts&quot;,<br>    &quot;uploadFolder&quot;: &quot;./node_modules/.bin/ts-node upload/arweaveFolder.ts&quot;,<br>  },<br>// ...</pre><p>We’ll rename the template solidity file generated from Hardhat and remove the test folder because they won’t apply to this new contract.</p><pre># FROM: ./zkevm-erc1155<br><br>mv contracts/Lock.sol contracts/ZkMythicCards.sol;<br>rm -rf test;</pre><h4>Creating Our ERC1155 Contract</h4><p>Now that we have what we need, we’ll create our contract with some additional functionality for random number generations (VRF isn’t supported yet for zkEVM) and getting the contract URI for the contract metadata.</p><p><strong>File:</strong> ./contracts/ZkMythicCards.sol</p><pre>// SPDX-License-Identifier: MIT<br>pragma solidity ^0.8.9;<br><br>// Imports<br>// ========================================================<br>import &quot;@openzeppelin/contracts/token/ERC1155/ERC1155.sol&quot;;<br>import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;<br>import &quot;@openzeppelin/contracts/security/Pausable.sol&quot;;<br>import &quot;@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol&quot;;<br>import &quot;@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol&quot;;<br>import &quot;@openzeppelin/contracts/utils/Strings.sol&quot;;<br><br>// Contract<br>// ========================================================<br>contract ZkMythicCards is ERC1155, Ownable, Pausable, ERC1155Burnable, ERC1155Supply {<br>    // Extending functionality<br>    using Strings for uint256;<br>    // Used to keep track of a random number generated for selecting one of the four items<br>    uint256 public randomNumber;<br><br>    /**<br>     * Main constructor seting the the baseURI<br>     * newuri - sets the base url for where we are storing our manifest JSON files<br>     */<br>    constructor(string memory newuri)<br>        ERC1155(newuri)<br>    {}<br><br>    /**<br>     * @dev Sets a new URI for all token types, by relying on the token type ID<br>     */<br>    function setURI(string memory newuri) public onlyOwner {<br>        _setURI(newuri);<br>    }<br><br>    /**<br>     * @dev Triggers stopped state.<br>     */<br>    function pause() public onlyOwner {<br>        _pause();<br>    }<br><br>    /**<br>     * @dev Returns to normal state.<br>     */<br>    function unpause() public onlyOwner {<br>        _unpause();<br>    }<br><br>    /**<br>     * @dev Creates random number based on number of loops to generate<br>     * (This is before VRF is supported on zkEVM)<br>     */<br>    function generateRandomNumber(uint256 loopCount) public {<br>        for (uint256 i = 0; i &lt; loopCount; i++) {<br>            uint256 blockValue = uint256(blockhash(block.number - i));<br>            randomNumber = uint256(keccak256(abi.encodePacked(blockValue, randomNumber, i)));<br>        }<br>    }<br><br>    /**<br>     * @dev Creates `amount` tokens of token type `id`, and assigns them to `msg.sender`.<br>     */<br>    function mint(uint256 amount)<br>        public<br>    {<br>        for (uint i = 0; i &lt; amount; i++) {<br>            // generateRandomNumber creates a new number everytime just before minting<br>            generateRandomNumber(i);<br>            // 4 is the max number and 1 is the minimum number<br>            // _mint(to, tokenId, amount, data)<br>            _mint(msg.sender, randomNumber % 4 + 1, 1, &quot;&quot;);<br>        }<br>    }<br><br>    /**<br>     * @dev Hook that is called before any token transfer. This includes minting<br>     * and burning, as well as batched variants.<br>     */<br>    function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)<br>        internal<br>        whenNotPaused<br>        override(ERC1155, ERC1155Supply)<br>    {<br>        super._beforeTokenTransfer(operator, from, to, ids, amounts, data);<br>    }<br><br>    /**<br>     * @dev See {IERC1155MetadataURI-uri}.<br>     * Metadata displayed for NFT tokenID - Ex: 1.json<br>     */<br>    function uri(uint256 _tokenId) override public view returns (string memory) {<br>        if(!exists(_tokenId)) {<br>            revert(&quot;URI: nonexistent token&quot;);<br>        }<br>        return string(abi.encodePacked(super.uri(_tokenId), Strings.toString(_tokenId), &quot;.json&quot;));<br>    }<br><br>    /**<br>     * @dev Contract-level metadata<br>     */<br>    function contractURI() public view returns (string memory) {<br>        return string(abi.encodePacked(super.uri(0), &quot;contract.json&quot;));<br>    }<br>}</pre><h4>Deploying ERC1155 Card Collection To zkEVM Testnet</h4><p>With out contract done, we’ll modify our deploy.ts file.</p><p><strong>File:</strong> ./scripts/deploy.ts</p><pre>// Imports<br>// ========================================================<br>import { ethers } from &quot;hardhat&quot;;<br>import dotenv from &quot;dotenv&quot;;<br><br>// Config<br>// ========================================================<br>dotenv.config();<br><br>// Imports<br>// ========================================================<br>async function main() {<br>  const Contract = await ethers.getContractFactory(`${process.env.CONTRACT_NAME}`);<br>  const contract = await Contract.deploy(`${process.env.BASE_URL}`);<br><br>  await contract.deployed();<br><br>  console.log(<br>    `${`${process.env.CONTRACT_NAME}`} deployed to ${contract.address}`<br>  );<br>}<br><br>// We recommend this pattern to be able to use async/await everywhere<br>// and properly handle errors.<br>main().catch((error) =&gt; {<br>  console.error(error);<br>  process.exitCode = 1;<br>});</pre><p>We’ll update our .env file to reflect the CONTRACT_NAME, add our zkEVM RPC, and specify how many NFT types exist.</p><p><strong>File:</strong> ./env</p><pre>CONTRACT_NAME=&quot;ZkMythicCards&quot;<br>NUMBER_NFT_TYPES=&quot;4&quot;<br>RPC_ZKEVM_URL=&quot;https://rpc.public.zkevm-test.net&quot;<br>WALLET_PRIVATE_KEY=&quot;&lt;YOUR-WALLET-PRIVATE-KEY&gt;&quot;<br>BASE_URL=&quot;&lt;YOUR-UNIQUE-BASE-URL-FOR-HOLDING-JSON-FILES&gt;&quot;</pre><p>With the new environment variables, we’ll modify our hardhat.config.ts to support the RPC settings.</p><p><strong>File:</strong> ./hardhat.config.ts</p><pre>// Imports<br>// ========================================================<br>import { HardhatUserConfig } from &quot;hardhat/config&quot;;<br>import &quot;@nomicfoundation/hardhat-toolbox&quot;;<br>import dotenv from &quot;dotenv&quot;;<br><br>// Config<br>// ========================================================<br>dotenv.config();<br><br>// Hardhat Config<br>// ========================================================<br>const config: HardhatUserConfig = {<br>  solidity: {<br>    version: &quot;0.8.18&quot;,<br>    settings: {<br>      optimizer: {<br>        enabled: true,<br>        runs: 200,<br>      }<br>    }<br>  },<br>  networks: {<br>    zkevmTestnet: {<br>      url: `${process.env.RPC_ZKEVM_URL || &#39;&#39;}`,<br>      accounts: process.env.WALLET_PRIVATE_KEY<br>        ? [`0x${process.env.WALLET_PRIVATE_KEY}`]<br>        : [],<br>    }<br>  },<br>};<br><br>// Exports<br>// ========================================================<br>export default config;</pre><p>With everything configured, let’s deploy our contract.</p><p><strong>NOTE:</strong> Make sure you have zkEVM Testnet tokens. You can bridge Goerli to zkEVM through <a href="https://wallet.polygon.technology"><strong><em>https://wallet.polygon.technology</em></strong></a><strong><em>.</em></strong></p><pre># FROM: ./zkevm-erc1155<br><br>npx hardhat run scripts/deploy.ts --network zkevmTestnet;<br><br># Expected Output:<br># Compiled 1 Solidity file successfully<br># ZkMythicCards deployed to 0x914B74867e49cd2a9cfb5928d14618B98Aa4FB13</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AvP7-lblHM_RB0EviNtk0g.png" /><figcaption>Polygonscan zkEVM Testnet Contract Deployed — <a href="https://testnet-zkevm.polygonscan.com/address/0x914B74867e49cd2a9cfb5928d14618B98Aa4FB13">https://testnet-zkevm.polygonscan.com/address/0x914B74867e49cd2a9cfb5928d14618B98Aa4FB13</a></figcaption></figure><h4>zkEVM Testnet Contract Verification</h4><p><strong>NOTE:</strong> Unfortunately Polygonscan is having some issues with verification and for now we’ll be using <a href="https://explorer.public.zkevm-test.net"><strong><em>https://explorer.public.zkevm-test.net</em></strong></a> instead.</p><p><strong>Creating Standard-JSON-Input</strong></p><p>In your compiled artifacts build-info folder, copy the entire input section and save it as a file called verify.json.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EQnKC87TQAVs2QaErDxXSA.png" /><figcaption>Copy Input JSON From build-info</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x3Wl8IOZ31Tc_LOKPx5hcw.png" /><figcaption>Create new ./verify.json and paste the input details inside the JSON file</figcaption></figure><p><strong>Verifying Contract On zkEVM Testnet Public Explorer</strong></p><p>Go to your deployed contract at the following URL, select the Standard JSON Input method for verification, and upload the verify.json file.</p><pre># https://explorer.public.zkevm-test.net/address/0xDeployedContractAddress</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*es8lyIHyooIb7sqwSWpOPg.png" /><figcaption>zkEVM Testnet Public Explorer — Verify &amp; Publish Contract</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SJwRpg_OlFUZbPx20NwM3A.png" /><figcaption>zkEVM Testnet Public Explorer — Select Standard Input JSON</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VifJIcb4rosXUzY-SHHcuw.png" /><figcaption>zkEVM Testnet Public Explorer — Configure Verification</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ArgtrzQzkrP8gY41llurjw.png" /><figcaption>zkEVM Testnet Public Explorer — Successful Verification</figcaption></figure><h4>Minting zkEVM ERC1155 NFTs</h4><p>Making sure that our wallet is set to the zkEVM Testnet network, connect our wallet to the site, mint 5 NFTs, and then read one of the token IDs to see the result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UwkCvBqKlsNxHjDYuyFCkQ.png" /><figcaption>zkEVM Testnet Public Explorer — Ensure On zkEVM Testnet Network</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SPlA5x4mIGLj5cyIfZr0Ng.png" /><figcaption>zkEVM Testnet Public Explorer — Mint 5 NFTs &amp; Confirm</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GiCkieof9z_DH__aBnq0LA.png" /><figcaption>zkEVM Testnet Public Explorer — Successful Minting</figcaption></figure><p>Once the mint was successful, go to the Read Contract section and input one of the four token IDs in the uri section.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*b6Z_xDGffspdjqU7WJLH3w.png" /><figcaption>zkEVM Testnet Public Explorer — Read Contract &amp; Query Token ID</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dUF_KHIOaUMwpAlX5Q8znw.png" /><figcaption>Arweave NFT Metadata</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nu8C8T6bVK9cgAPFA_cl1A.png" /><figcaption>Arweave NFT Image From Metadata</figcaption></figure><p>Although OpenSea isn’t supporting zkEVM yet, if we deployed this contract to Polygon Mumbai, we can see that the NFT items can have multiple owners.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P-ZqQFQ8o2Fjd2r0yNwQuw.png" /><figcaption>OpenSea Demonstrating ERC1155 With Multiple Owners</figcaption></figure><h3>Full Code Git Repository</h3><p>If you want to look at the full code repository on GitHub, check out <a href="https://github.com/codingwithmanny/zkevm-erc1155"><strong><em>https://github.com/codingwithmanny/zkevm-erc1155</em></strong></a><strong><em>.</em></strong></p><p><a href="https://github.com/codingwithmanny/zkevm-erc1155">GitHub - codingwithmanny/zkevm-erc1155: How to deploy an ERC1155 Mythic Card Collection contract to zkEVM and arweave permaweb files</a></p><h3>What’s Next?</h3><p>Some next steps could be to add additional attributes to make this a fully functioning card game where people could create matches from deployed contracts.</p><p>If you want to learn how to deploy an <a href="https://codingwithmanny.medium.com/deploy-an-animated-nft-to-zkevm-with-hardhat-1c580e4a4465"><strong><em>Animated ERC721 Contract To zkEVM</em></strong></a>, make sure to check that article out.</p><p>If you got value from this, please give it some love, and please also follow me on twitter (where I’m quite active) <a href="http://twitter.com/codingwithmanny">@codingwithmanny</a> and instagram at <a href="https://instagram.com/codingwithmanny">@codingwithmanny</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=96a7cdd1ef20" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>