<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Steve Simkins</title><description>DX Engineer | Defending the Open Web</description><link>https://stevedylan.dev/</link><item><title>2026, the Future of this Website, and the Web Itself</title><link>https://stevedylan.dev/posts/2026-site-plans/</link><guid isPermaLink="true">https://stevedylan.dev/posts/2026-site-plans/</guid><description>A small reflection and set of plans for making a ripple in a big lake</description><pubDate>Thu, 01 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/other/surf-the-web.png&quot; alt=&quot;surf&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I just finished reading &lt;a href=&quot;https://henry.codes/writing/a-website-to-destroy-all-websites/&quot;&gt;A website to destroy all websites&lt;/a&gt; (if you haven’t already you really should), and I’m inspired to put a breath of fresh air into this website which I call home. I’ve always believed in the importance of having a personal site, and while I have added my own small pieces of flair, I think there’s a lot more I can do to help put myself on the page. Currently my website is utilitarian, built to deliver information in a clean way. While I do enjoy the minimal and clean aesthetic, a lot of my own personality gets washed out. Not sure what the changes look like yet, but they will come, and I will have fun :)&lt;/p&gt;
&lt;p&gt;Some other things I’m looking to add / change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Look into webmentions&lt;/li&gt;
&lt;li&gt;Create a &lt;a href=&quot;https://nownownow.com/about&quot;&gt;“now page”&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Move my &lt;a href=&quot;https://bearblog.stevedylan.dev&quot;&gt;bear blog&lt;/a&gt; style content to something like &lt;code&gt;/updates&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While something like a personal site is small and probably doesn’t matter much in the grand scheme of the internet, it does matter to me. I’ve spent plenty of time beating the point that the web is what you make of it and how you engage it. A few months ago I dumped most of my social media accounts and my mental state has improved significantly. Writing regularly on this blog and my bear blog have introduced me to so many wonderful people via &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;email&lt;/a&gt; that I now keep up with via RSS. I’ve taken small explorations into &lt;a href=&quot;https://geminiprotocol.net&quot;&gt;Gemini&lt;/a&gt; and even have an iOS client in the works just for my own fun.&lt;/p&gt;
&lt;p&gt;Whether its HTML, CSS, or just plain text, &lt;em&gt;you&lt;/em&gt; can build, create, express, and share with real people. It doesn’t have to be mind numbing feeds that get you riled up over pointless issues, or a bunch of really crappy AI slop videos. The internet can be wonderful, and it starts with us.&lt;/p&gt;
&lt;p&gt;Make a personal website, make a blog, and if you have questions or have no idea where to start, I’m totally willing to &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;help&lt;/a&gt;. My time and my talents are small, but I firmly believe the we can make something great.&lt;/p&gt;
&lt;p&gt;See you around the web 🏄&lt;/p&gt;</content:encoded></item><item><title>How to Create a Dynamic App NFT Resume</title><link>https://stevedylan.dev/posts/3d-nft-resume/</link><guid isPermaLink="true">https://stevedylan.dev/posts/3d-nft-resume/</guid><description>Level up your job search with a dynamic app NFT resume that will wow any employer.</description><pubDate>Tue, 10 Jan 2023 05:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://www.pinata.cloud/blog/resume-app-nft&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/pinnie.D249WNQr_Z1XiysU.webp&quot; alt=&quot;Link to Pinata&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Pinata &lt;/a&gt;
 
&lt;p&gt;Despite the current market conditions, web3 jobs are still on the rise. But competition is at an all-time high. Sure you can build a portfolio of projects to help display your skills, but that’s what everyone else is doing too. What can you do to stand out among the crowds of developers out there like yourself? How are you thinking outside the box?&lt;/p&gt;
&lt;p&gt;Rather than another LinkedIn resume or eye-glazing PDF, what if a potential employer opened up their crypto wallet and saw an interactive resume NFT with your name on it? It would demonstrate not only your ability to make NFTs, but &lt;a href=&quot;https://medium.com/pinata/how-to-build-an-app-nft-7c57b51698e7&quot;&gt;App NFTs&lt;/a&gt;—this concept that a full blown and functional application can be an NFT. You think that might crank things up a bit?&lt;/p&gt;
&lt;p&gt;This dynamic resume app NFT would show an ability to make anything an NFT, like an interactive comic book or even a small game. And you’d better believe it would get that employer intrigued enough to set up some time to chat with the power of simply dropping your resume into their wallet.&lt;/p&gt;
&lt;p&gt;Sound interesting? Let’s talk about how you can create your own dynamic resume app NFT.&lt;/p&gt;
&lt;h2&gt;Building your Resume App NFT&lt;/h2&gt;
&lt;p&gt;Here we’ll give a general walkthrough of how to create this Resume App NFT, but the ultimate tutorial (and what is primarily referenced while building this) is &lt;a href=&quot;https://medium.com/pinata/how-to-build-an-app-nft-7c57b51698e7&quot;&gt;Justin’s guide on how to create App NFTs using Pinata&lt;/a&gt;, Polygon and Opensea.&lt;/p&gt;
&lt;p&gt;Let’s get into it.&lt;/p&gt;
&lt;p&gt;First things first, you need a vision of what your resume could look like. As the primary builder, our Head of Community Steve Simkins envisioned something more dynamic and interactive, taking advantage of all the fun web dev tools out there. It was important to keep in mind what this would look like from an NFT marketplace like Opensea or a crypto wallet, making sure it’s size would not cause problems.&lt;/p&gt;
&lt;p&gt;Steve spent sometime tinkering in Figma and ended up with a card like design that could have multiple screens with different pieces of the resume. The reader could hover over the card and get a cool 3D effect, and click on the forward and back buttons to read through his skills.&lt;/p&gt;
&lt;p&gt;After the design was finished, it was time to build the app. Steve followed Justin’s tutorial by making a simple React application and made sure to use &lt;strong&gt;“homepage”: “.”&lt;/strong&gt; in the &lt;strong&gt;package.json&lt;/strong&gt; file; very important to make sure it runs correctly on Pinata and IPFS. From there Steve used some standard issue web design methods and created a static “website” or app that was his resume. Of course for this step you can use just about any kind of javascript framework or tools, not just React. Once the development was complete, Steve ran the build command and uploaded the build folder to Pinata.&lt;/p&gt;
&lt;p&gt;As said in Justin’s tutorial, you can use a free Pinata account to do all of this, but to truly harness the full user experience of this app, using a paid account with a Dedicated Gateway will make this app much faster than if you used a public gateway or depended on external sources. Here is an example of the application through Steve’s Dedicated Gateway:&lt;/p&gt;

&lt;img src=&quot;/blog-images/other/63bd95503c654e14fc1b3b00_Slide-16-9-3.png&quot; alt=&quot;Screenshot of web app&quot; height=&quot;1080&quot; width=&quot;1920&quot; /&gt;
&lt;p&gt;Awesome! Resume app is done, time to turn it into an NFT. Following the guide, Steve created a simple smart contract to deploy the App NFT in a way that he could keep updating it with new versions, very handy if you ever have an update in experience or education. Also keep in mind you can do your own smart contract customization here to do cool stuff, like mass airdrop your NFT to a list of wallets or ENS addresses!&lt;/p&gt;
&lt;p&gt;With the smart contract tested and ready to go, it was time to prep the metadata for the NFT. Steve kept it pretty simple with a name, description, image link for thumbnails, and used his Dedicated Gateway link in the &lt;strong&gt;animation_url&lt;/strong&gt; for performance.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Steve&apos;s App NFT Resume&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;A dynamic NFT resume by Steve&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;image&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ipfs://QmTa46bKHxcQCBoNt887X2zNJwAHpAZ93hTXDi9KeJeM4W&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;animation_url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the metadata.json file complete, Steve uploaded that file to Pinata as well and used the CID as the token URI like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;URI&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;ipfs://QmU85vmit8ShrUpnJFg3wEAMA61GcQB2X5KcgabchDV1kt&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That’s it! After Steve ran the deployment command with Hardhat, the NFT had been minted in his wallet where we could see it on OpenSea! &lt;a href=&quot;https://testnets.opensea.io/assets/goerli/0x45602432657d8100119e8633b677043b9022c22b/1&quot;&gt;Check it out&lt;/a&gt; 😎&lt;/p&gt;
&lt;p&gt;Of course this app is a fun proof of concept, but with some more refining and perhaps a better framework, you can make efficient applications that can be owned as NFTs. This is just one of the many possibilities, all of which could either make your portfolio impressive, or even launch your own company 👀 What will you build?&lt;/p&gt;</content:encoded></item><item><title>How to Run Your Own Public IPFS Gateway</title><link>https://stevedylan.dev/posts/how-to-run-your-own-ipfs-gateway/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-to-run-your-own-ipfs-gateway/</guid><description>Learn how to run a public IPFS gateway with a custom domain using Digital Ocean</description><pubDate>Tue, 10 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;IPFS has proven to be the decentralized storage protocol of choice by many blockchain developers, and one of the crucial tools used to access content on IPFS are &lt;a href=&quot;https://www.pinata.cloud/blog/what-is-an-ipfs-gateway&quot;&gt;Gateways&lt;/a&gt;. IPFS Gateways are like bridges between the IPFS protocol and the HTTP protocol that we use everyday to browse websites. There are lots of different options to choose from when it comes to IPFS Gateways, and in this post we’ll show you how to host and build your own!&lt;/p&gt;
&lt;aside&gt;&lt;p&gt;⚠️ Warning! This guide will show you how to make a public IPFS Gateway that can access any CID on IPFS, which means it has the potential to be abused. Please be cautious and look into ways you can secure your gateway.&lt;/p&gt;&lt;/aside&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;In order to follow this guide you’ll need a few things. First and foremost you’ll need a decent amount of experience using Linux servers and navigating around in the terminal, things like creating daemons or editing text files with vi or nano. You’ll also need a cloud server provider, and there’s plenty to choose from. In this guide we’ll use Digital Ocean and get a simple droplet. Also if you want to have a custom domain instead of using an IP address you can get something through a domain provider like Namecheap.&lt;/p&gt;
&lt;h2&gt;Setting Up the Server&lt;/h2&gt;
&lt;p&gt;Before we rent a server for our IPFS node, you’ll want to create an SSH key to login with. This is the preferred secure way to SSH into your server versus a user name and password. You can check out &lt;a href=&quot;https://docs.digitalocean.com/products/droplets/how-to/add-ssh-keys/&quot;&gt;this guide&lt;/a&gt; on how to create them.&lt;/p&gt;
&lt;p&gt;It will prompt you to put in a passphrase to access the key so choose something secure, and once created it will make a file located in &lt;code&gt;~/.ssh/rsa.pub&lt;/code&gt;. We’ll use the contents in just a moment when we setup the server.&lt;/p&gt;
&lt;p&gt;Since we’ll be using DigitalOcean you can head over there to create an account and buy a Droplet. You definitely don’t need anything crazy, I just got the following:&lt;/p&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;digital ocean droplet creation&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;
&lt;p&gt;For the authorization select SSH Keys, then copy and paste the contents of &lt;code&gt;~/.ssh/rsa.pub&lt;/code&gt; and paste it in as a new key.&lt;/p&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;digital ocean ssh key creation&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;
&lt;p&gt;After the droplet has been created, you will actually want to turn it off, go to the Network settings, and enable IPV6. Once enabled turn it back on and try to SSH into it with the following command with the IPV4 address of the server:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ssh root@ipv4address&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It should prompt you to enter in the passphrase for you SSH key, and after entering it you should be in!&lt;/p&gt;
&lt;p&gt;While we are signed in, we are currently logged in as root, which is not the most secure practice. This next step is optional, but highly recommend. First we’ll create a new user with the command &lt;code&gt;adduser steve&lt;/code&gt;, and of course you can use whatever username you want to. It will prompt you to make a new password and for some other information you can leave blank. Next we need to give the user the permissions necessary to run the IPFS node with &lt;code&gt;sudo usermod -aG sudo steve&lt;/code&gt; (and of course from this point on replace &lt;code&gt;steve&lt;/code&gt; with the username you chose).&lt;/p&gt;
&lt;p&gt;Next we’ll need to run the following commands to create an &lt;code&gt;.ssh&lt;/code&gt; directory for our new user and give proper permissions so we can edit it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir /home/steve/.ssh&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;touch /home/steve/.ssh/authorized_keys&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sudo chown -R steve:steve /home/steve/.ssh&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sudo chmod 700 /home/steve/.ssh&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;sudo chmod 600 /home/steve/.ssh/authorized_keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With those commands completed you can now login as your user with &lt;code&gt;su steve&lt;/code&gt; and then edit the SSH keys files to paste in your own that we used earlier with either &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;nano&lt;/code&gt; then &lt;code&gt;/.ssh/authorized_keys&lt;/code&gt;. After pasting in your key you can run &lt;code&gt;exit&lt;/code&gt; to log out of the user, then again to leave the SSH session. Now you should be able to SSH in with the new user &lt;code&gt;ssh steve@ipv4address&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Install IPFS&lt;/h2&gt;
&lt;p&gt;Once you’re in your server you can run &lt;code&gt;sudo apt update&lt;/code&gt; just to make sure all your packages are up to date. Then you will want to visit the &lt;a href=&quot;https://github.com/ipfs/kubo/releases&quot;&gt;release page for IPFS Kubo&lt;/a&gt;, the Go implementation of an IPFS node used pretty much everywhere. On that page you can choose the latest stable release, then locate the correct distribution for your OS. In my particular case it ended up being &lt;code&gt;kubo_v0.22.0_linux-amd64.tar.gz&lt;/code&gt;. Copy the link to that file, then back in your terminal for the droplet run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;wget&lt;/span&gt;&lt;span&gt; &quot;https://github.com/ipfs/kubo/releases/download/v0.22.0/kubo_v0.22.0_linux-amd64.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will download the Kubo zip file to your home directory. You can unzip it with &lt;code&gt;tar -xf kubo_v0.22.0_linux-amd64.tar.gz&lt;/code&gt; and then you should see a folder just called “kubo.” &lt;code&gt;cd&lt;/code&gt; into that folder then run &lt;code&gt;sudo ./install.sh&lt;/code&gt; and that will move the binary from the folder into your &lt;code&gt;/usr/local/bin&lt;/code&gt; folder. To make sure it worked, try running &lt;code&gt;ipfs --version&lt;/code&gt; , it should show the version number if successful.&lt;/p&gt;
&lt;p&gt;With IPFS installed on our server the next thing we need to do is create a &lt;code&gt;systemd&lt;/code&gt; service aka a daemon. This will make sure that IPFS is always running and will start up automatically if we ever reboot the server. To do this you will want to either use &lt;code&gt;sudo&lt;/code&gt; with either &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;nano&lt;/code&gt; and create a file called &lt;code&gt;ipfs.service&lt;/code&gt; under &lt;code&gt;/etc/systemd/user/&lt;/code&gt; , so altogether would look something like &lt;code&gt;sudo vim /etc/systemd/user/ipfs.service&lt;/code&gt;. Once the editor is open you can paste in the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Description&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;InterPlanetary File System (IPFS) daemon&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Documentation&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;https://docs.ipfs.io/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;After&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;network.target&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;notify&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ExecStart&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/usr/local/bin/ipfs daemon --enable-gc=true --migrate=true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ExecStop&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;/usr/local/bin/ipfs shutdown&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Restart&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;on-failure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;KillSignal&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;SIGINT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;WantedBy&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;default.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save the file and exit the editor, then run the following commands to start up the daemon and make it persist between logins:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;ipfs&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; --profile=server&lt;/span&gt;&lt;span&gt; --empty-repo&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;systemctl&lt;/span&gt;&lt;span&gt; --user&lt;/span&gt;&lt;span&gt; enable&lt;/span&gt;&lt;span&gt; ipfs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;systemctl&lt;/span&gt;&lt;span&gt; --user&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;span&gt; ipfs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;systemctl&lt;/span&gt;&lt;span&gt; --user&lt;/span&gt;&lt;span&gt; status&lt;/span&gt;&lt;span&gt; ipfs&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;loginctl&lt;/span&gt;&lt;span&gt; enable-linger&lt;/span&gt;&lt;span&gt; $USER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All of this together should have the IPFS node running, and you can test it out by running the following command&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; -L&lt;/span&gt;&lt;span&gt; http://localhost:8080/ipfs/QmPyCYfL5oF79cfXjbt5cyr5hAZcyNrPNV9ytvUPdk8KT9&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;aside&gt;&lt;p&gt;💡 Keep in mind that with a fresh node like this with zero configuration might be slow and take a while to pull content and be connected with other major IPFS networks&lt;/p&gt;&lt;/aside&gt;
&lt;h2&gt;Setting Up Custom Domain&lt;/h2&gt;
&lt;p&gt;Now that your IPFS node is setup and we can use the gateway, these next steps will help you assign a domain to the gateway and make it public. First you will need to acquire a domain name which you can get from multiple providers like Namecheap. For this tutorial we’ll use the example &lt;code&gt;[domain.com](http://domain.com)&lt;/code&gt; (very original). After purchasing the domain you will want to go into the advance DNS settings through your domain provider, and there we’ll add some records so we can use &lt;code&gt;[ipfs.domain.com](http://ipfs.domain.com)&lt;/code&gt; as our gateway domain. You can get the IPV4 and IPV6 addresses from your Digital Ocean console.&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Host&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;th&gt;TTL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;A&lt;/td&gt;&lt;td&gt;ipfs&lt;/td&gt;&lt;td&gt;IPV4 Address&lt;/td&gt;&lt;td&gt;Automatic&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;A&lt;/td&gt;&lt;td&gt;*.ipfs&lt;/td&gt;&lt;td&gt;IPV4 Address&lt;/td&gt;&lt;td&gt;Automatic&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AAAA&lt;/td&gt;&lt;td&gt;ipfs&lt;/td&gt;&lt;td&gt;IPV6 Address&lt;/td&gt;&lt;td&gt;Automatic&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AAAA&lt;/td&gt;&lt;td&gt;*.ipfs&lt;/td&gt;&lt;td&gt;IPV6 Address&lt;/td&gt;&lt;td&gt;Automatic&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;You can use a DNS checker for &lt;code&gt;[ipfs.domain.com](http://ipfs.domain.com)&lt;/code&gt; to make sure everything is propagating but it can take some time depending on your provider.&lt;/p&gt;
&lt;p&gt;After assigning the domain to the IP addresses of our droplet, we need to go in and edit our IPFS config.&lt;/p&gt;
&lt;aside&gt;&lt;p&gt;⚠️ WARNING: The following command will open your gateway and IPFS node for anyone to use, do so with caution and do further research before releasing it to the while.&lt;/p&gt;&lt;/aside&gt;
&lt;p&gt;You can paste &lt;code&gt;ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8080&lt;/code&gt; into the terminal and it will change the IP from your local network to the outside network, allowing external traffic to use it. We stress caution here because IPFS gateways are known to be abused which we’ll get into later. After changing that setting on the IPFS config we need to make one additional edit with &lt;code&gt;vim ~/.ipfs.config&lt;/code&gt;. Once the file is open navigate under the following and add your specific domain and configs for &lt;code&gt;UseSubdomains&lt;/code&gt; and &lt;code&gt;Paths&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;Gateway&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;PublicGateways&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;domain.com&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                &quot;UseSubdomains&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                &quot;Paths&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                        &quot;/ipfs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After we have written and saved those changes we’ll need to restart the IPFS daemon with &lt;code&gt;systemctl --user restart ipfs&lt;/code&gt;. Then we can test if our custom domain works in our own browser with a link like this: &lt;code&gt;http://ipfs.domain.com:8080/ipfs/QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng&lt;/code&gt;. Now keep in mind that we did not use &lt;code&gt;https&lt;/code&gt; as that will come later, and you’ll also notice we had to include that nasty port number which is not very smooth. So let’s fix that!&lt;/p&gt;
&lt;p&gt;We’ll use nginx to help with some re-routing on our server so we can just leave out the port number in our urls. Run &lt;code&gt;sudo apt install nginx&lt;/code&gt; to get started. Once installed we will want to rename the default configuration as a backup with &lt;code&gt;sudo mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default-back&lt;/code&gt; then create a new one by running &lt;code&gt;sudo vim /etc/nginx/sites-available/default&lt;/code&gt;. In that file you can paste the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;server {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	listen 80;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	server_name ipfs.domain.com;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	location / {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    proxy_pass http://127.0.0.1:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    proxy_set_header Host $host;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    proxy_set_header X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    proxy_set_header X-Forwarded-Proto $scheme;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Write and save that file, then run &lt;code&gt;systemctl restart nginx&lt;/code&gt;. If successful we can now use a url like this: &lt;code&gt;[http://ipfs.domain.com/ipfs/QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng](http://ipfs.domain.com/ipfs/QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Setting up SSL (HTTPS)&lt;/h2&gt;
&lt;p&gt;As you saw in the last url we used, we’re still using http which is a no go in today’s standards. In order to fix that we need to get an SSL certificate for our domain. Thankfully its pretty straight forward with a package called certbot. You can install it with &lt;code&gt;sudo nnap install certbot --classic&lt;/code&gt; then run the command &lt;code&gt;sudo certbot --nginx -d [ipfs.domain.com](http://ipfs.domain.com)&lt;/code&gt;. It should walk you though some questions you can answer, then it should issue a certificate for your domain. Last step is to go back to your domain provider and add this DNS record:&lt;/p&gt;















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Type&lt;/th&gt;&lt;th&gt;Host&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;CAA&lt;/td&gt;&lt;td&gt;ipfs&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;http://letsencrypt.org/&quot;&gt;http://letsencrypt.org/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Now you can test it out with &lt;code&gt;[https://ipfs.domain.com/ipfs/QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng](https://ipfs.domain.com/ipfs/QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng)&lt;/code&gt;. Congrats!! You just setup your own public IPFS gateway. But, something isn’t quite right: its super slow isn’t it? Let’s talk about that.&lt;/p&gt;
&lt;h2&gt;Further Steps&lt;/h2&gt;
&lt;p&gt;You have your public gateway setup and it sorta works, but its also super slow. There are some things you can do to help relieve this. One of those things is setting up a cache layer or CDN to help make fetching files a second time much faster. You can also look into peering your gateway with IPFS pinning services to tap into their network and get faster speeds, or configure your IPFS node to work with the Distributed Hash Table (DHT) to assist with finding files. Even with all those things, it can be tough to maintain good speeds.&lt;/p&gt;
&lt;p&gt;Another thing you have to consider when hosting an IPFS gateway yourself is abuse. The unfortunately piece of a decentralized network is that there is plenty of people out there who want to abuse public gateways by hammering them with requests for files or use your gateway for phishing content. When that happens you have to keep up with a list of CIDs to block from your gateway or risk it have it taken down by domain registrars. You can check out a list of CIDs to block by IPFS &lt;a href=&quot;https://github.com/ipfs/infra/blob/master/ipfs/gateway/denylist.conf&quot;&gt;here&lt;/a&gt;, however it is no longer being maintained making it even more difficult to keep up yourself.&lt;/p&gt;
&lt;h2&gt;Another Option: Pinata Dedicated IPFS Gateways&lt;/h2&gt;
&lt;p&gt;With any open source software endeavor, you have to ask yourself an important question: “Is this worth my time?” There are plenty of things you can do yourself when it comes to networking, like setting up your own custom email server, and doing those things will help you learn a lot. However if you’re trying to heavily use and depend on IPFS as a service for your decentralized applications, then it becomes a different question. Even if you got the speeds of your gateway up to a decent level, would it be worth the expense and upkeep to keep running it yourself and hope it does not get abused?&lt;/p&gt;
&lt;p&gt;This is why IPFS pinning services like Pinata exist: to make IPFS easy and simple for developers. With Pinata you can not only upload files easily, but with Pinata Dedicated Gateways you get unmatched speeds thanks to a built in 200 location edge cache CDN. You get the benefits of being hooked up to a large network of nodes instead of just your solitary node. Plus, you get Gateway Access Controls so you can access content on IPFS with protection from spam and abuse. Setting up this public gateway was fun, but in a production environment, I’m thankful to have Pinata :) Happy Pinning!&lt;/p&gt;</content:encoded></item><item><title>48 Hours Disconnected</title><link>https://stevedylan.dev/posts/48-hours-disconnected/</link><guid isPermaLink="true">https://stevedylan.dev/posts/48-hours-disconnected/</guid><description>I spent two days with zero access to my cell phone or computer, and this is what I discovered</description><pubDate>Tue, 02 May 2023 04:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://open.substack.com/pub/stevedsimkins/p/48-hours-disconnected?r=1iyrw0&amp;amp;utm_campaign=post&amp;amp;utm_medium=web&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/substack.BOlF8wp1_1siaG2.webp&quot; alt=&quot;Link to Substack&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Substack &lt;/a&gt;
 
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1683045399/misc/D59779D7-75D0-4EE8-8C39-6C3EDA5D5CFC_1_105_c_hbikqe.jpg&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It was a Thursday afternoon at the chiropractor. I was still on my paternity leave, helping my wife take our kids so our youngest could get some adjustments. Like most dads I sat around, waited, and scrolled on my phone. Halfway through I could tell my wife was miffed, and I thought it was due to our appointment being fifteen minutes late. In reality, my son had been trying to get my attention for several minutes, I ignored him, and he gave up. When she told me later that day I could vividly see it happen, like I was there, but I wasn’t.&lt;/p&gt;
&lt;p&gt;This was not the first time. I had a problem, like the majority of society, having self control over my phone. There were countless other instances like this one where I really screwed up and gave my kids a mental image of what it means to be an absentminded father. It was time to stop, so I made the decision to lock my phone and computer away all weekend. What happened next has honestly become a pivotal moment in my personal life. Here’s a few things I learned along the way that I hope will resonate with others.&lt;/p&gt;
&lt;h2&gt;I spend a lot of time checking my phone&lt;/h2&gt;
&lt;p&gt;Probably a no-brainer for most, especially if you just look at the Screen Time section of your iPhone. I was one of them, thinking “yeah I spend a good bit of time on my phone and could probably do a better job with that,” but it’s one thing to see a number of hours on a chart, and something completely different to experience it. During my two days I would feel the urge to check my phone at completely unnecessary moments, like in the middle of dinner or waiting for coffee to finish brewing.&lt;/p&gt;
&lt;p&gt;These were instances that I really didn’t need to check my phone but wanted to simply fill a gap; I felt a need to stay busy and occupied. It’s only when you’re forced into that scenario where you realize how little you can sit and do nothing. From this I also saw how much more time I had without my phone. The little moments of time between tasks or living that I filled with my phone piled up, and ultimately resulted in a large amount of time. I suppose that’s where the number of hours on Screen Time come into play.&lt;/p&gt;
&lt;p&gt;With all these spare pockets of time, I felt the need to fill them with something, and so my reading habits skyrocketed. I can’t remember the last time I read books just to read, and that is something truly valuable. While I do a lot of reading through my phone or computer, it’s completely different to find yourself alone with one author and their thoughts. The temptation to switch from a partially finished blog post, to a garbage Tweet, to a video of something silly is completely removed. Your attention span is forced to stick with just one thing. Writing this feels ridiculous because I grew up reading books, lots of them; this concept isn’t revolutionary. However, the self-realization of how far we have come is nothing short of shocking when you experience it yourself.&lt;/p&gt;
&lt;h2&gt;My mental health is directly affected&lt;/h2&gt;
&lt;p&gt;A natural effect of not having a phone or a computer is not checking any social media of any kind. As you would expect, mental health improves. Twitter has become my bread and butter for social media intake, and while I have carefully curated my feed to be as positive as possible, I can’t deny how much less negative I was during the two days. Truthfully, it was the happiest I had been in a while. For me at least, it was enough to convince me that any kind of social media will have negative affects on my mental health. Previously I always told myself social media is fine, that how you use it defines how it affects you, but I’m not sure I can say that anymore. Of course I’m sure there are others reading this that have achieved social media nirvana, but I would challenge them to just take two days and see for themselves if that’s true.&lt;/p&gt;
&lt;p&gt;Taking these two days also showed me how busyness was infecting my mental health. By always scrolling, always reading, always tinkering, I was more busy than productive. My hands and mind were always tending to something, but the distractions made them switch too often. Eliminating the distractions helped me realize how the clarity improved my mental state. How I go back to working on a computer every day is still something I’m working out, but I’m hoping the answer lies in closing everything but the task at hand, documenting thoughts for future ideas and projects, or just disconnecting entirely.&lt;/p&gt;
&lt;p&gt;The final area of social media that died was self image. Typically when I post some content, I’m always tempted to check the notifications to see if people like it or not. It’s a gauge of self worth and esteem that really shouldn’t be there, and it was refreshing to have it ripped out. I just created for the sake of creating, without thinking if someone would like it or not.&lt;/p&gt;
&lt;h2&gt;I am so much more aware without my phone&lt;/h2&gt;
&lt;p&gt;As I found myself in those moments where nothing in particular was happening, twiddling my thumbs in boredom or sitting with someone, I realized how aware of my surroundings I truly was. It makes sense, if you’re looking down at your phone then you’re not looking up ¯_(ツ)_/¯ There would be moments where I would be trying to talk to my wife and she would be on her phone, and it would take her a second to even notice I was talking. I can say this because in our marriage I have absolutely been worse in this area, more than I want to admit. It gave me an understanding of what it’s like to be on the other side of that fence, and I needed to feel that.&lt;/p&gt;
&lt;p&gt;Apart from human interaction, my awareness in the outdoors and nature was also heightened. We took a day to explore a river gulf we hadn’t been to yet, and there were multiple moments while walking that I caught myself feeling the urge to check my phone. If I had my phone, I would have missed the smile on my son’s face as he saw the river running next to us, or the butterfly that landed at our feet. Those are the moments I have missed before because my habits convinced me that I was bored and needed entertainment, and that’s deeply saddening. What other precious moments have I missed?&lt;/p&gt;
&lt;h2&gt;My Apple Watch can handle almost all my needs&lt;/h2&gt;
&lt;p&gt;I made the decision that I would keep my Apple Watch during the two days away, mostly because I knew it would be limited without the iPhone paired as it’s just the wifi model. This caused me to rely on it more for daily uses, and to my surprise, it did really well! Anytime I needed the time or to set a timer, it had me covered. I could still listen to music and podcasts while doing dishes, I could use my compass and waypoints while hiking, I could check the weather while using my wifi, I could send and receive iMessages, and more.&lt;/p&gt;
&lt;p&gt;This proved something important: when I go back to having a phone and computer, I could depend on my watch even more. If I have my phone turned on and somewhere in the house, I’ll get notifications if something urgent pops up. I didn’t have to worry about missing out, because everything else that is lower priority would still be there when I got back. I think this is the reason we get Apple Watches to begin with — to see a notification to know if you need to pull your phone out for the next thirty minutes — but actually practicing that effectively becomes rare. Having a day or two without a phone can quickly change that.&lt;/p&gt;
&lt;h2&gt;Now what?&lt;/h2&gt;
&lt;p&gt;The thought of going back to the world of overstimulating, entertainment saturated, and attention kidnapping was daunting. It was only two days, but I immediately could understand how I had changed for the better, and I didn’t want to lose that. Yet there is no good way to both free yourself of the internet and still be productive, engaging, and helpful to the people in our digital spaces.&lt;/p&gt;
&lt;p&gt;To be honest I’m still figuring it out, but the first step for me was to physically remove my phone as much as possible. Leave it in another room, have my Apple Watch on me, and just live as much as I can without it. When I go out of the house I now try to keep my phone in my pocket as much as possible, being aware that I can pull it out if I need to. More times than not, though, it can just stay there. I now try to designate time to work, to be focused in on a phone or computer, and separate that time and space from the time I spend with my family.&lt;/p&gt;
&lt;p&gt;My paternity leave will end in a few weeks, so how I handle being plugged in will probably change soon, but the feeling of these forty-eight hours will not. I will never forget how such a simple task of giving up my phone and computer gave me so much happiness, freedom, and clarity that I desperately needed. I still enjoy technology, the people building it, and how it ultimately improves our daily lives. Technology is neither good nor evil, it is neutral. However, how humans use it will always be in a balance of good and evil. Each day we have the personal choice of how we will use the tools at our disposal and how they will affect the people around us, and sometimes it’s as simple as leaving your phone in another room.&lt;/p&gt;</content:encoded></item><item><title>How to Create 3D NFTs on Solana</title><link>https://stevedylan.dev/posts/3d-solana-nfts/</link><guid isPermaLink="true">https://stevedylan.dev/posts/3d-solana-nfts/</guid><description>Learn how to scan and mint real life objects on Solana</description><pubDate>Wed, 18 May 2022 04:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://medium.com/pinata/how-to-scan-and-create-1-1-3d-nfts-on-solana-using-polycam-and-pinata-df513dd87937&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/medium.CI909Xjl_oL8pb.webp&quot; alt=&quot;Link to Medium&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Medium &lt;/a&gt;
&lt;p&gt;The growth and evolution of NFTs has come a long way from the early days. Some of the early NFT projects were simple .png files or a link to a YouTube video, but now they are an entire industry that consumes brand, utility, even augmented reality. Metaverses, 3D objects, and other virtual reality experiences are all the rage, and I firmly believe we will see more of this in the near future.&lt;/p&gt;
&lt;p&gt;One unique tool that I stumbled upon was Polycam. Polycam is a mobile iOS app that utilizes the iPhone’s LiDAR scanner to map 3D objects and environments. 3D NFTs are certainly popular, but what about 3D NFTs of real life objects? Or even better: real life environments? I think this is an angle that not many people have thought about creating, and opens up a whole new realm of artistic expression in ways people have not seen before.&lt;/p&gt;
&lt;p&gt;In this tutorial we’ll cover how to use Polycam to scan and create a 3D models and environments, and then we’ll cover how to make them into NFTs using Pinata and Solana.&lt;/p&gt;
&lt;h2&gt;Scanning with Polycam&lt;/h2&gt;
&lt;p&gt;To get started you’ll want to download the Polycam app on the App Store. Be aware that this is only for iPhones, and scanning environments will be more effective if you have a LiDAR scanner on your iPhone. Having a LiDAR scanner is not necessary though! Once you download it you will need to make an account with them and start their 14 day free trial.&lt;/p&gt;
&lt;p&gt;Once you do that Polycam will take you to the “captures” page where you can see all your scans. To learn how the scan process works, I would highly recommend their well done tutorials on their website and in the app.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.poly.cam/capturing-large-spaces&quot;&gt;Using LiDAR Mode to Capture Large Spaces&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.poly.cam/building-a-studio-for-photo-mode-captures&quot;&gt;Using Photo Mode to Capture 3D Objects&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In short, you will want to use the LiDAR mode when you are trying to scan environments or large spaces, and for 3D objects you will want to use the photo mode!&lt;/p&gt;
&lt;p&gt;For this tutorial I’m going to scan my desk space using the LiDAR method, and for the 3D objects we’ll use an old book, and of course Pinnie!&lt;/p&gt;
&lt;p&gt;With the video below you can get glimpse of how the LiDAR scanner maps over surfaces. Taking it real slow and covering every angle really helps with scanning environments.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;

&lt;p&gt;After you’re done scanning there will be a processing step that will take all the data and map it into a 3D environment!&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;

&lt;p&gt;Once it’s processed you can go ahead and view the 3D model/environment! Polycam really is first class; you can create videos, you can view the model in Augmented Reality, it just has so many awesome features! My model isn’t the cleanest since I have so many small detailed objects on my desk, but for something a bit simpler this feature is amazing.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;

&lt;p&gt;Now that we got our model, we can upload it to the cloud and look at it through our web browser! Just click on the little cloud icon on the top of the screen when viewing your model and it will be uploaded to your cloud storage with Polycam.&lt;/p&gt;
&lt;p&gt;Once viewing it in the web browser, we can download the model by clicking “export.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;From there we want to select the GLTF format and start the download!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Ok so we got our 3D file for our environment, now lets do some 3D objects as well! The process is pretty similar, except for 3D objects we’ll switch to the “photo” mode instead of the LiDAR mode. The photo mode will take a bunch of different pictures and put them together to make a 3D model! In the video below you can see what the process looks like as I scan one of my prized possessions, a 1930’s edition of Moby Dick illustrated by Rockwell Kent.&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;

&lt;p&gt;Once we finish scanning the book, I like to select the higher end of the quality allowance, and I like to use the object masking to get all the fine details. These photo mode objects are a bit more work, so when you take one Polycam sends it off to a server for higher powered processing. Due to how intense the work is, you can only take 150 of these a month, but I don’t think that will be an issue for most people. Once they finish processing it, the model looks like this!&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;

&lt;p&gt;The download process looks exactly the same as we did the desk, very easy! Also while you’re still in Polycam, you can edit, adjust, crop, etc. your model all inside their app or website, so that way when the model is exported it’s ready to be turned into an NFT!&lt;/p&gt;
&lt;p&gt;Last but not least, we got a model of Pinnie too 😉&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://poly.cam/capture/FAE73483-C416-49D6-B92C-33260BF924E1&quot;&gt;Pinata Polycam Capture&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Creating 1/1 NFTs on Solana&lt;/h2&gt;
&lt;p&gt;Alright alright alright, we got our models from Polycam, now lets turn them into 1/1 NFTs on Solana!&lt;/p&gt;
&lt;p&gt;First thing you’ll want to do is get a &lt;a href=&quot;https://pinat.cloud&quot;&gt;Pinata account&lt;/a&gt;. I would recommend getting the professional account so you can use a Dedicated Gateway, you’ll see why as we go further!&lt;/p&gt;
&lt;p&gt;There’s a lot of great benefits of using IPFS for NFTs, such as &lt;a href=&quot;https://docs.ipfs.io/concepts/content-addressing/&quot;&gt;content addressability&lt;/a&gt; and &lt;a href=&quot;https://medium.com/pinata/web3-data-portability-through-ipfs-saved-hicetnunc-724e3df2948d&quot;&gt;portability&lt;/a&gt;, but especially speed using a dedicated gateway. Since Solana is still adopting IPFS, not all the wallets and marketplaces have their own gateway solutions to resolve links like “ipfs://CID.” However with our own dedicated gateway we can use a standard “https://” link and deliver it with speed. You’ll see how we’ll do this in a bit!&lt;/p&gt;
&lt;p&gt;Let’s log into Pinata and upload our file. Really simple, just click on the upload button in the top left, give it a name, and upload!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Then we just need to choose a subdomain and make sure its available. If it is, click next!&lt;/p&gt;
&lt;p&gt;If we just clicked on this file to preview it, it’s gonna take us straight to download since the browser doesn’t know what to do. That’s ok! We’re gonna format the link for our NFT metadata. It will look something like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://{your-subdomain}.mypinata.cloud/ipfs/{your-3d-file-cid}?filename={file-name-and-extension}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For our particular CID the link will look like this!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok our 3D file is on IPFS, the next step is to create a metadata file that will point to that 3D file. Open up your terminal and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; 3d-nfts&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; 3d-nfts&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;touch&lt;/span&gt;&lt;span&gt; pinnie.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open up that pinnie.json file in your text editor of choice. Since we’re using Solana we will want to use &lt;a href=&quot;https://docs.metaplex.com/token-metadata/Versions/v1.0.0/nft-standard#json-structure&quot;&gt;Metaplex Token Metadata Standard&lt;/a&gt; which you will definitely want to save as a reference, but we’ll be simplifying ours just a little bit.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;3D Pinnie&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;symbol&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;PIN&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;A 3D scan of Pinnie taken with Polycam&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;seller_fee_basis_points&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;image&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;null&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;animation_url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;external_url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://pinata.cloud&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;collection&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Pinnie&apos;s 3D NFTs&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;family&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;3D NFTs&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;properties&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;files&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;uri&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;null&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;image/png&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;uri&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;vr/glb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;category&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;3D&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;creators&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;address&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;D8KLFUfnRwGsMt6n56FzkyRYmVUQXiRnJWFV7rZYCYdd&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;share&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Couple of things you’ll want to take note here. We could use an image URL if you wanted a backup, just make sure you upload a separate file to Pinata. For our example I’ve left it as null. The key thing to notice is the “animation_url” which I have set to our link we created earlier. Under properties and files, we left the image null again, and created another one that includes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;uri&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;vr/glb&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The uri is the link to our content, and the type defines the kind of file we want to use, which in this case is virtual reality (vr) and the file format (glb). Once you get familiar with the metadata standard it’s pretty straight forward. Now that we created this metadata file, save it and upload it to Pinata as well. Once it’s uploaded take note of the CID and save the link to it, should look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://pinnieblog.mypinata.cloud/ipfs/QmYTQDE2ur5Lo4z76cNjwMAq6H3oNyDb1xuCQfdxMBnmon&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re really close to minting this NFT! &lt;a href=&quot;https://solana.com/&quot;&gt;Solana&lt;/a&gt; is an up and coming blockchain that is capable of incredible speeds and low transactions fees. Before we go any further you’ll want to make sure you have a couple of things installed on your computer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.solana.com/cli/install-solana-cli-tools&quot;&gt;Solana CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/samuelvanderwaal/metaboss&quot;&gt;Metaboss&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Be sure to follow the instructions for installing each very carefully! The Solana CLI is the tool that will let us interact with the Solana blockchain, and Metaboss is nicknamed “The Solana Metaplex NFT ‘Swiss Army Knife’” and rightly so. It is packed with features, including the minting NFTs!&lt;/p&gt;
&lt;p&gt;Once you’ve installed both, let’s make sure the Solana CLI is setup properly by running the following command.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solana&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that returns the version then we are good to go! Next thing we’ll do is create a wallet, set our network to devnet, and get some fake sol. To create a new wallet run the command below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solana-keygen&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; --outfile&lt;/span&gt;&lt;span&gt; ~/.config/solana/devnet.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a file on your computer and is the code version of a new Solana wallet! Next we want to set our Solana config to use this wallet by default.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solana&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; --keypair&lt;/span&gt;&lt;span&gt; ~/.config/solana/devnet.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last but not least, we’ll set our Solana config to use the devnet environment so we can use fake money and have room to mess around.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solana&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; set&lt;/span&gt;&lt;span&gt; --url&lt;/span&gt;&lt;span&gt; https://api.devnet.solana.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can make sure all is good by running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solana&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span&gt; get&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will return what wallet we’re using and what network we’re on! Last step is to get some test SOL, which is so easy! Just run the command below. Once it’s done it should return a balance!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solana&lt;/span&gt;&lt;span&gt; airdrop&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As far as Metaboss goes, it’s pretty simple to install after installing Solana. Once you have installed it, run the following command to make sure it’s ready to go.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;metaboss&lt;/span&gt;&lt;span&gt; --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To mint our NFT, Metaboss makes it so easy! We just gotta run this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;metaboss&lt;/span&gt;&lt;span&gt; mint&lt;/span&gt;&lt;span&gt; one&lt;/span&gt;&lt;span&gt; --external-metadata-uri&lt;/span&gt;&lt;span&gt; https://pinnieblog.mypinata.cloud/ipfs/QmYTQDE2ur5Lo4z76cNjwMAq6H3oNyDb1xuCQfdxMBnmon&lt;/span&gt;&lt;span&gt; --keypair&lt;/span&gt;&lt;span&gt; ~/.config/solana/devnet.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s break down what’s happening here. Metaboss is going to mint one token for us, and we set the “external-metadata-uri” to the url we created a little while ago that points to our uploaded metadata. Then we just set the keypair or wallet to authorize the transaction, and that’s it! If it worked you should get a result with the transaction ID and the mint account!&lt;/p&gt;
&lt;p&gt;Ok but we want to see it right? All we gotta do is open our ~/.config/solana/devnet.json file, and copy the numbers in it (this is your private key, keep it safe and do not share it!)&lt;/p&gt;
&lt;p&gt;Then using a wallet like Phantom, click on “Add / Connect wallet, select “import private key,” then paste in our keypair from earlier. Once it’s in your wallet you will want to click on the settings icon in the bottom right, scroll down to “change network” and set it to “Devnet.” Once you do that, go back to your NFTs page and you should see it!!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/medium/v2/resize:fit:716/1*V-onKvcCU830IvHxNWHbEg.gif&quot; alt=&quot;wallet gif&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Polycam really introduces a whole new way of how content can be created using modern tech. You can imagine how this might be used for journalism, where instead of just looking at a photo of a story, you can view it as a 3D object with augmented reality. Someone who explores caves can share their experience as a 3D environment! Paired with Solana as a much faster and affordable layer one solution, and &lt;a href=&quot;https://pinata.cloud&quot;&gt;Pinata&lt;/a&gt; to deliver IPFS content quickly, the possibilities are endless!&lt;/p&gt;</content:encoded></item><item><title>A Terminal Based Workflow</title><link>https://stevedylan.dev/posts/a-terminal-based-workflow/</link><guid isPermaLink="true">https://stevedylan.dev/posts/a-terminal-based-workflow/</guid><description>A deeper look at why a integrated terminal workflow is more than just using vim</description><pubDate>Wed, 06 Mar 2024 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/Qma8MhvupyHTDmBGiH3yz37acPdqu966YF7rrSk9QDXx6B.png&quot; alt=&quot;header image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a &lt;a href=&quot;https://stevedylan.dev/posts/why-i-learned-vim&quot;&gt;previous blog post&lt;/a&gt; I talked about why I learned Vim, and how it boosted my speed and productivity. In some ways it’s true, but I don’t think I had a grasp on the whole picture. After I watched &lt;a href=&quot;https://youtu.be/5wy2iLU5fs0?si=uZ2e6_EUFkrk4Vrp&quot;&gt;this video&lt;/a&gt; I realized it wasn’t necessarily just Vim/Neovim, but my terminal based workflow that was at play. I recently gave &lt;a href=&quot;https://zed.dev&quot;&gt;Zed&lt;/a&gt; another try as it has some attractive features, including &lt;a href=&quot;https://zed.dev/docs/vim&quot;&gt;a whole page in their docs dedicated to Vim.&lt;/a&gt; It is minimal with Vim keybindings enabled, as well as pretty speedy since its GPU enable and built on Rust. Nevertheless I still felt clumsy, and I realized why: the terminal. With my current terminal workflow I’m able to easily switch between different projects without having multiple code editor windows opened, and I have better access to CLI tools that compliment my developer workflow. I just can’t replicate that outside of the terminal right now.&lt;/p&gt;
&lt;p&gt;This epiphany led me to think more about the flow that I had built, and the tools I couldn’t do without. So in this post I’ll layout my workflow and programs in hopes it benefits someone else looking to take advantage of a terminal based dev environment.&lt;/p&gt;
&lt;p&gt;I will clarify that by no means do I suggest that a terminal based workflow guarantees speed. It’s just one way of creating an effective environment, and in the end I hope people find what works best for them. I’m personally open to whatever will make me more productive, and I hope this post might give insights to others.&lt;/p&gt;
&lt;h2&gt;Tmux &amp;amp; Session Management&lt;/h2&gt;
&lt;p&gt;At the core of my workflow is &lt;a href=&quot;https://github.com/tmux/tmux&quot;&gt;Tmux.&lt;/a&gt; This tool allows you to create multiple terminal sessions, windows, and panes in just one terminal emulator window. Instead of having a terminal open for every project I might be in, I can just have one. A Tmux session will look like any other terminal window, the difference is the ability to disconnect and then re-attach to that same session. So I can be working on something, leave it, then come right back to it.&lt;/p&gt;

&lt;p&gt;Additionally I can create multiple panes and windows inside a session. I generally create a session per project, and each session might have two windows (e.g. one for a client side repo, the other for a server side repo). This both helps keep projects unified yet organized.&lt;/p&gt;

&lt;p&gt;Where it gets really good is having a solid session manager, and that’s where &lt;a href=&quot;https://github.com/joshmedeski/sesh&quot;&gt;Josh Medeski’s Sesh&lt;/a&gt; comes into play. With this I can easily change between different sessions, and thus easily switch between different projects. This has become essential to my workflow as I often might be working on 2-3 different projects at a time, all with multiple windows and panes each. Each one can be unique to what I’m working on as well.&lt;/p&gt;

&lt;h2&gt;Neovim Plugins&lt;/h2&gt;
&lt;p&gt;After Tmux, of course the next import piece of my workflow is Neovim. Configuring Neovim will be different for everyone as it should be since people have different preferences and workflows. However I will share some plugins that really help my workflow.&lt;/p&gt;
&lt;p&gt;The first is &lt;a href=&quot;https://github.com/nvim-telescope/telescope.nvim&quot;&gt;Telescope&lt;/a&gt;, which is a no brainer for most people already using Neovim. It allows me to quickly jump to different files, search a string through my entire project, or sort through diagnostic issues. It’s incredibly powerful and extensible, and I would highly recommend getting familiar with all its abilities.&lt;/p&gt;

&lt;p&gt;Another one I use fairly often is &lt;a href=&quot;https://github.com/nvim-neo-tree/neo-tree.nvim&quot;&gt;Neo-tree&lt;/a&gt;. I know some people are anti-file-tree but I personally really enjoy it. It is setup to appear in the middle of my screen, and I use it to help navigate where a file might be, edit a file name, add new files, etc. Since the majority of my work is in JavaScript / Next.js, it’s helpful to distinguish which &lt;code&gt;page.tsx&lt;/code&gt; or &lt;code&gt;route.ts&lt;/code&gt; file I’m currently working on.&lt;/p&gt;

&lt;p&gt;Having an LSP (Language Server Protocol) and completions setup is also essential for a good workflow in Neovim. This is what provides hints, completions, diagnostics, or even docs for the language you’re working in. Cannot state how helpful these are when working in a typed language like Typescript or Go. I would also say it’s beneficial to learn how to set it up manually, and &lt;a href=&quot;https://youtu.be/S-xzYgTLVJE?si=xG7c-Yx0fkxRHwx0&quot;&gt;Typecraft’s video&lt;/a&gt; does a great job showing how it’s done.&lt;/p&gt;

&lt;p&gt;These two are on the smaller side but are still really great to use. The first is &lt;a href=&quot;https://github.com/christoomey/vim-tmux-navigator&quot;&gt;Tmux Navigator.&lt;/a&gt; This allows you to navigate between an open Neovim pane and a Tmux pane without using a Tmux prefix. For example, instead of navigating with &lt;code&gt;Ctrl + b - l&lt;/code&gt; I can just use &lt;code&gt;Ctrl - l&lt;/code&gt;. It’s the same mapping for switching between Neovim panes and Tmux panes, which is a huge quality of life improvement. The other small mention is &lt;a href=&quot;https://github.com/FabijanZulj/blame.nvim&quot;&gt;blame.nvim.&lt;/a&gt; With this tool I can hit &lt;code&gt;Space-b&lt;/code&gt; to see line by line who changed what when. This is great when working with other people on a project and you’re trying to find out what changed when. There’s also the LazyGit plugin for Neovim, but it deserves its own section.&lt;/p&gt;
&lt;h2&gt;Git Management&lt;/h2&gt;
&lt;p&gt;For the longest time I just used git in the command line for handling any of my git needs, but a lot of that changed when I started working on more team oriented projects. Handling git conflicts was a nightmare, as well as going through git history to see what changed at each commit. LazyGit changed all of that. This tool really simplifies things like cherry picking commits, handling conflicts, and viewing rich history. There’s so much it can do that I haven’t even touched the surface on, and I would recommend &lt;a href=&quot;https://youtu.be/CPLdltN7wgE?si=7XqMjlwpi5tmWFpA&quot;&gt;this video&lt;/a&gt; by its creator to see what’s possible (if you’re like me you’ll also learn more about git itself from it).&lt;/p&gt;

&lt;h2&gt;Bringing it All Together&lt;/h2&gt;
&lt;p&gt;Let’s do a run through of what starting and managing a project might look for me with this workflow. First &lt;code&gt;t&lt;/code&gt; to start my Tmux session manager, then navigate to my dev folder. There I’ll run a command like &lt;code&gt;npx create-next-app@latest&lt;/code&gt;. Once the repo is created I’ll &lt;code&gt;cd&lt;/code&gt; into it and run &lt;code&gt;nvim&lt;/code&gt; which will greet me with a telescope window of all the files that I can fuzzy find through. Then I might open a split pane to the right with Tmux so I can have a terminal to run commands like &lt;code&gt;npm run dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the project has multiple repos like a server/client combo or I’m referencing another repo, I’ll create a new window with the same pane setup. As I work and make changes, I’ll open LazyGit in one of my Tmux panes and run &lt;code&gt;ctrl-b + z&lt;/code&gt; to make it full screen. From there I’ll add my commits and push them up, or make a branch that I can merge main into if I’m already working on a shared project.&lt;/p&gt;

&lt;p&gt;Back in my main Next.js window I might open another split pane below the right one so while the dev server is running I can make test API calls from the terminal with httpie, maybe pipe the results into jq then into a file.&lt;/p&gt;

&lt;p&gt;This is the flexibility of a terminal based workflow that is hard to replicate on something like VSCode or Zed. It’s not even an editor issue in my opinion: it’s a development environment issue. Do code editors like VSCode take out out of that environment? Sorta, not totally, but it’s definitely not the same.&lt;/p&gt;
&lt;p&gt;In my previous post about learning Vim, I posed the question if you yourself should learn it. Again I think that’s perhaps the wrong question. Instead you should ask yourself “How well do I know my development environment?” If you’re happy with running the basic tools in your terminal and switching back and forth between a terminal window and VSCode that’s fine, do what works best for you. On the other side, you might be blown away by how productive you could be with a terminal based workflow, or at the very least what you can do with effective CLI tools. Never stop learning!&lt;/p&gt;</content:encoded></item><item><title>Arc: The Internet Computer</title><link>https://stevedylan.dev/posts/arc-internet/</link><guid isPermaLink="true">https://stevedylan.dev/posts/arc-internet/</guid><description>How the Arc web browser is paving the way for the future of consumer computers</description><pubDate>Wed, 08 Mar 2023 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1678385122/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png&quot; alt=&quot;Arc Logo&quot; /&gt;
height=1080
aspectRatio=1.7777777777777777
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;20 Years of the Same Thing&lt;/h2&gt;
&lt;p&gt;The internet has grown significantly in the last twenty years. What was once just static web pages with facts is now a bustling cyber metropolis where we write essays, share photos of food, and buy airline tickets. It’s something we use every day and take for granted. What’s interesting is that in the last twenty years of the internet’s evolution, the way we experience it has stayed mostly the same. Web browsers have certainly changed in appearance and performance, but the tab model and disconnect from the rest of the computer have stayed. The Browser Company making the Arc Browser has plans to change that and has the ambitious goal of creating an “Internet Computer.” Before we get ahead of ourselves, let’s have a brief overview of Arc and its features.&lt;/p&gt;
&lt;h2&gt;Tabs, Folders, and Spaces&lt;/h2&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;Tabs folders and spaces in arc&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;
&lt;p&gt;One of the fundamental differences between Arc and other browsers is how it handles tabs. Instead of a row of tabs at the top of a window, Arc keeps them all in a sidebar. I genuinely believe there is a strong UX decision being made here: organization is simply easier to accomplish with a side menu than it is with a top menu. Beyond that, Arc uses a “Pinned Tabs” and “Today’s Tabs” approach to organizing your website. You may often feel hesitant to close a website because you might need it later. If you do, you can move it to the pinned tabs. When you feel like you don’t need it, then you can remove it from pinned tabs. Anything that stays in today’s tabs can be automatically cleared after a certain amount of time, or you can click a button to wipe them all out. This is powerful because it makes the decision of what is important and what isn’t easy for people to make.&lt;/p&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;View of spaces in Arc&quot; width=&quot;1920&quot; height=&quot;1080&quot; /&gt;
&lt;p&gt;Of course, you may have lots of websites that you need to keep pinned, and for that, Arc has Folders and Spaces. Folders work like any other bookmark folder, where you can store as many as you want with sub-folders as well. This can be useful if you have a lot of sites that need to be referenced for a project. Folders work to a degree, but even still, you can have too many folders. That’s where spaces come in. Spaces are designed to be different workspaces for whatever you do on the internet. You could have a shopping space, an entertainment space, a developer space, anything really. You can customize the appearance, name, icon, and of course, the pinned tabs and folders for each one. Arc makes it simple to swipe between spaces and re-organize them to fit your needs.&lt;/p&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;View of favorites in Arc&quot; width=&quot;912&quot; height=&quot;528&quot; /&gt;
&lt;p&gt;Favorites are special in that they are in nice little squares are the top of the sidebar no matter what space you are in, so for instance if you use Spotify a lot then that would be an ideal favorite app or website. They also have a preview feature when you hover over them for selective sites, e.g. Google Calendar will show you a brief schedule window.&lt;/p&gt;
&lt;p&gt;These simple changes can drastically clear up and organize your internet browser if you typically have thirty Chrome tabs open, and it’s seriously good. There are even more reasons why this feature set is so important, but we’ll get back to that later.&lt;/p&gt;
&lt;h2&gt;Split View&lt;/h2&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;Split view in Arc&quot; height=&quot;1080&quot; width=&quot;1920&quot; /&gt;
&lt;p&gt;Another killer feature on Arc is split views. With a few clicks, you can easily have side-by-side panels of two different websites. You can even pin these dual tabs if you have to reference them quickly. You can actually do more than just two; depending on how big your screen is, you can go crazy! Personally, I use this all the time when reading an article while taking notes or moving information from one app to another. The Arc team recently released vertical splits as well, perhaps enough persuasion to get a vertical monitor.&lt;/p&gt;
&lt;p&gt;Managing split views is effortless, with the ability to expand tabs to full screen, delete or replace a pane, resize panes, and rearrange them. It’s such a simple feature but executed so well.&lt;/p&gt;
&lt;h2&gt;Peek View&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1678295158/arc-browser-blog-post/pcu6lczn2z48pvmuucte.gif&quot; alt=&quot;Peek view in Arc&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Peek is a new feature for Arc but perhaps one of my favorites. When you click on a link, instead of taking you to a new tab or taking you off the current site, a smaller window pops up and allows you to see the content, where you can either close it or expand it into a full tab. You could be browsing Twitter, see someone reference an article, take a quick peek, and be done! It’s a great way to reduce tab clutter, with a world-class UX.&lt;/p&gt;
&lt;h2&gt;Easels&lt;/h2&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;Easels in Arc&quot; height=&quot;1080&quot; width=&quot;1920&quot; /&gt;
&lt;p&gt;Easels are something completely unique to Arc and have some pretty impressive abilities. The concept is like internet chalkboards. You can snip pieces of websites, add them to an easel, then add other things like your images, text, shapes, etc. Arc provides a great shortcut (that can be customized), where all you have to do is hold down Command and Shift, then click &amp;amp; drag across the screen to select what you want to snip. Then it will prompt what easel you want to add the snippet to, or if you want to make a new one. Easels can also be made public and shared with other people!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1678295978/arc-browser-blog-post/dqk3g3sfyp34ooosaupn.gif&quot; alt=&quot;Live mode in Arc Easels&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The craziest part? They feature a LIVE mode. Let’s say you take a snippet of the weather of your hometown from Google and save it to your easel. At any time, you can visit the easel and press the “play” button, and Arc will update the easel in real-time! I’ve used this feature to track packages and share research with my team. Others have used it to make personalized dashboards to feature live info. You really need to try it for yourself!&lt;/p&gt;
&lt;h2&gt;Boosts&lt;/h2&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;A view of the Boosts menu in Arc&quot; height=&quot;1080&quot; width=&quot;1920&quot; /&gt;
&lt;p&gt;Arc has an affinity to bring back the excitement of the internet from the 90s, both in design and in customization. Not only does Arc feature themes for your browser to personalize your experience, they give you the ability to do “Boosts” to websites. Boosts are simply CSS or JS injections to websites so you can customize the color of a website or give it additional functionality.&lt;/p&gt;
&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;Boost being made in Arc for Tiwtter&quot; height=&quot;1080&quot; width=&quot;1920&quot; /&gt;
&lt;p&gt;As someone who is obsessed with customizing the personal computer environment, I absolutely love Boosts. &lt;a href=&quot;https://nordtheme.com/&quot;&gt;Nord&lt;/a&gt; is one of my favorite color palettes, and I use it for as many ports as I can. With Boosts, I’ve been able to customize all my regular sites to give it some flare. This comes a bit naturally for me since I have some web development experience and know how to snag CSS selectors easily, however, there are possibilities for Boosts to become a marketplace feature where non-technical users could save and use Boosts made by others.&lt;/p&gt;
&lt;h2&gt;One App to Rule Them All&lt;/h2&gt;
&lt;p&gt;A fun fact about most applications running on your computer: they’re likely Electron apps. Electron is a framework for building desktop applications, and it uses web languages like Javascript. In a lot of ways, Electron apps are just web browsers. Most apps in this format work in your web browser too, like Slack, Spotify, Discord, etc. My personal computer operated like this: I had a dozen apps downloaded for individual web applications. Why? 1. It was cumbersome to use them in my web browser, and 2. I thought it would be more performant than using the web browser (it was not). Arc solved both of those problems for me.&lt;/p&gt;
&lt;p&gt;This goes back to how Arc manages pinned tabs. Since it has the ability to treat websites like applications, it makes it much easier to access them and use them. Instead of having a bunch of tabs on the top of my browser where Slack could get lost, I can always find it in my pinned tabs on my sidebar. Within two days of adjusting to Arc, I was able to delete all of my excess Electron apps. Now instead of running ten apps at once, I run maybe two or three, with Arc being the heavy lifter. When I’m not using apps, I can simply close them out but keep it pinned in my tabs for easy access later on.&lt;/p&gt;
&lt;h2&gt;Internet Computers&lt;/h2&gt;
&lt;p&gt;That last point brings us to the concept of Internet Computers that Josh, the CEO of The Browser Company, talks about in &lt;a href=&quot;https://youtu.be/v0160IirdL4&quot;&gt;videofile_ : the internet computer&lt;/a&gt;. He essentially breaks down how most of our lives run on applications in the cloud rather than the computers we use, whether it’s our work, our photos and videos, or our entertainment; it doesn’t live locally. An Internet Computer is a fluid concept without definition, but perhaps could look like&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I can tap my finger on the device, or I can toss at a glance and whoosh: my computer comes down from the internet, comes down from the cloud, and is right there on that machine. Because all of the stuff I need, all of my tools, all of my files, all of my people, my teams, all of those things are out there on the internet too so it doesn’t matter where I access it from.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Josh talks about this as a possible reality in five to ten years, but to be honest, it feels like we could be so much closer. Arc is already an app that can do just about anything you need it to do, thanks to the power of developers building web applications. Of course, there are limitations with heavier software, and there is still a lot of work to be done, but the concept of a computer that lives in the cloud and could seamlessly travel between your phone, your computer, or your partner’s tablet, really excites me!&lt;/p&gt;
&lt;p&gt;I can also see some possibilities of Web3 being part of this future, where your crypto wallet could be used as your internet identity. Your assets and funds could be used with an Internet computer to send tokens, vote on proposals, or integrate digital collectibles. The future of crypto and blockchain is still muddy and has a lot of work before it becomes a reality, yet it’s easy to visualize something like this.&lt;/p&gt;
&lt;h2&gt;Join the Arc Side&lt;/h2&gt;
&lt;p&gt;Whatever the future might hold, Arc and its incredible features are available now! At the time of this blogpost it is only available on MacOS, but the team is currently building out a mobile app as well as a Windows version. It is also only accessed by invite only. however there are many being passed around. My best suggestion is to check out their &lt;a href=&quot;https://discord.gg/arcinternet&quot;&gt;Discord&lt;/a&gt; where they have a channel for invite codes, or if you want, you can &lt;a href=&quot;mailto:hello@stevedsimkins.dev&quot;&gt;email me&lt;/a&gt;, and I’ll give out what I can! 😁&lt;/p&gt;
&lt;p&gt;I appreciate everything The Browser Company has accomplished so far, and I wish them the best as they attempt to revolutionize the way we experience the Internet!&lt;/p&gt;</content:encoded></item><item><title>AT Protocol Primer</title><link>https://stevedylan.dev/posts/atproto-starter/</link><guid isPermaLink="true">https://stevedylan.dev/posts/atproto-starter/</guid><description>What is the AT Protocol and why does it matter in the grand scheme of the web</description><pubDate>Wed, 21 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/atprotocol.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you’ve been on the internet for the last 10-20 years then you probably have a good sense of how bad the internet has become. We experience it every day when we pick up our phones and scroll through apps that used to mean something to us. Take Instagram for example. About 10-12 years ago it was a refreshing way to create and share moments in our lives. Resurrecting the square 1:1 aspect ratio that resembled a medium format camera viewfinder, filters that gave a “I mustache you a question” while wearing a short brimmed fedora, and a new wave of photographers making their mark. I even met my wife on Instagram. It was fun. Fast forward to the present and it’s a mindless feed of videos from people you don’t know, making you emotionally angry or trying to convince you to buy something. We lost control over our data, what massive companies did with it, and what actually mattered to us in the end. I don’t think that era of the web is coming back, but I do think there is hope for a better web that is more open.&lt;/p&gt;
&lt;h2&gt;The Problem With the Web&lt;/h2&gt;
&lt;p&gt;There’s a lot that could fill in the blank here in regards to what is wrong with the web right now, but I personally think there are two major pain points.&lt;/p&gt;
&lt;h3&gt;Fragmented Data&lt;/h3&gt;
&lt;p&gt;Right now the majority of the data you create on the internet is stored on servers owned by platforms. If you make a post on Facebook, it lives on Meta’s servers. If you make a post on X, it’s stored on their servers, and so on and so forth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/old-data-model.png&quot; alt=&quot;old data model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What you end up with is a fragmented spread of data across multiple platforms that you have no control over. If X decides to nuke your account or if LinkedIn goes out of business, that data is probably gone. Perhaps you can do what my wife and I did when we deleted all our Meta accounts and download archives of that data, but it ends up being pretty useless. Trying to resurrect it and use it on a new platform would be next to impossible. It’s dead.&lt;/p&gt;
&lt;h3&gt;Fragmented Identity&lt;/h3&gt;
&lt;p&gt;Another problem with the current web model is that your online identity is composed of a bunch of small profiles spread across multiple platforms. We all love that existential dread that comes over us when we have to sign up for yet another service and we have to pick a username that may or may not be available, and then somehow keep up with the 15 different usernames across all of our stuff.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/fragmented-identity.png&quot; alt=&quot;fragmented identity&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What an absolute mess. Our identity situation is so bad that we end up creating a link tree page with our huge list of fragmented platforms to help push people in the right direction. Who is Steve online? Hard to say, you have to visit maybe five different sites to figure that out. Perhaps even more frustrating is that we have a solution for this that no platform uses: domains. You’re reading from &lt;code&gt;stevedylan.dev&lt;/code&gt;, a domain I’ve used for years now that I have control over. What if you could use a single domain for everything?&lt;/p&gt;
&lt;h2&gt;The AT Protocol&lt;/h2&gt;
&lt;p&gt;Thus enters the &lt;a href=&quot;https://atproto.com&quot;&gt;AT Protocol&lt;/a&gt; (aka ATProto for short), a new model of how our data and our identity can live on the internet. Imagine this: you pick one username, &lt;code&gt;stevedylan.dev&lt;/code&gt; for example, you can use it for any app, and your data travels with it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/pds-model.png&quot; alt=&quot;pds model&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With the AT Protocol model for data, everything lives on a single server called a PDS (personal data server). Your content is organized into neat folders that are used by each app. Let’s imagine that we were rebuilding Instagram with this model and we called it &lt;code&gt;at-gram.com&lt;/code&gt; (horrible I know but stay with me). You login to this website, the app will ask for permission to write files to your server, and once you’ve approved it then you can start posting. You post an image of a cat, and in that moment the app will create a new “folder” for this post called &lt;code&gt;com.at-gram.post.image&lt;/code&gt;. When you look inside your personal data server you can look at that data, see how it’s put together, and where it comes from: &lt;code&gt;at-gram.com&lt;/code&gt;. That content you just created doesn’t get stored on some server owned by &lt;code&gt;at-gram.com&lt;/code&gt;; it’s owned by &lt;strong&gt;you&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Everything is tied to your identity that can also be moved from server to server. Let’s say you created an account on a PDS owned by an organization and you want to move it somewhere else. The tools exist where you can make that happen, all your data sticks with your identity, and everything keeps working as normal. My ATProto identity lives on my own PDS called &lt;code&gt;andromeda.social&lt;/code&gt;, but I could just as easily move it to an organization like &lt;code&gt;selfhosted.social&lt;/code&gt;. That is the true beauty and freedom of the AT Protocol: unified identity, unified data, both portable and interoperable.&lt;/p&gt;
&lt;p&gt;I’m glazing over a lot of technical pieces that make up the AT Protocol, so if you’re a nerd like me and want to dive deeper, I would highly recommend &lt;a href=&quot;https://www.youtube.com/watch?v=F1sJW6nTP6E&quot;&gt;this video&lt;/a&gt; and check out the official &lt;a href=&quot;https://atproto.com&quot;&gt;website&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How to Get Started&lt;/h2&gt;
&lt;p&gt;There’s a few ways you could start using ATProto. Probably the easiest one is to sign up through &lt;a href=&quot;https://bsky.app&quot;&gt;Blue Sky&lt;/a&gt;. It’s a social media platform that allows you to create custom feeds and have way more control over your experience than other platforms. You can also check out &lt;a href=&quot;https://blog.vicwalker.dev.br/3lz4g6zxeic2p&quot;&gt;some other servers out there&lt;/a&gt; or join mine if you &lt;a href=&quot;mailto:contact@stevedylan.dev?subject=Request%20to%20Join%20andromeda.social&quot;&gt;ask nicely&lt;/a&gt; :). When you sign up you’ll generally get an alias like &lt;code&gt;steve.andromeda.social&lt;/code&gt; which is your handle that you can use everywhere, but if you have a custom domain like &lt;code&gt;stevedylan.dev&lt;/code&gt; that you want to use you can follow &lt;a href=&quot;https://www.bskyinfo.com/guides/advanced-features/custom-domains/&quot;&gt;this guide&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you have an ATProto account here are a few things you can check out!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://leaflet.pub&quot;&gt;Leaflet&lt;/a&gt; - Tiny interconnected social documents&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pckt.blog&quot;&gt;pckt.blog&lt;/a&gt; - Blogging made easy&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://offprint.app/&quot;&gt;offprint&lt;/a&gt; - Publishing for the open web&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://smokesignal.events/&quot;&gt;Smoke Signal&lt;/a&gt; - Events built on ATProto&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.apple.com/us/app/flashes-for-bluesky/id6741443033&quot;&gt;Flashes&lt;/a&gt; - Instagram alternative&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://grain.social/&quot;&gt;grain&lt;/a&gt; - Instagram alternative&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://margin.at/&quot;&gt;Margin&lt;/a&gt; - Write on the margins of the internet&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blento.app/&quot;&gt;Blento&lt;/a&gt; - Create your own website stored on your PDS&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://plyr.fm/&quot;&gt;plyr.fm&lt;/a&gt; - Music on ATProto&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://semble.so/&quot;&gt;Semble&lt;/a&gt; - A social knowledge network for researchers&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://skyreader.app/&quot;&gt;Skyreader&lt;/a&gt; - RSS Reader built on ATProto&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tangled.org/&quot;&gt;Tangled&lt;/a&gt; - Tightly knit social coding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s a lot out there and I’ll try to update this list over time as I find more apps that use ATProto, so save this post and revisit if that sounds interesting to you. If you’re trying to get started with ATProto and get stuck, &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;my inbox is always open&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;The web is in a rough place, but what encourages me is the number of people trying to fight for a more open web through technology like ATProto. Plenty of talented developers are trying to improve the protocol as well as build apps that people can use, but the ultimate deciding factor is us and our time. We can choose to keep using walled garden platforms and sending our data to massive companies, or we can choose a smaller web that actually puts the power back in our hands. With enough people making the same decisions to support protocols like this, we can change the course of the web for good.&lt;/p&gt;</content:encoded></item><item><title>Back to Basic</title><link>https://stevedylan.dev/posts/back-to-basic/</link><guid isPermaLink="true">https://stevedylan.dev/posts/back-to-basic/</guid><description>Wading my way through the mess that is programming today</description><pubDate>Fri, 06 Mar 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/back-to-basics.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you read &lt;a href=&quot;https://stevedylan.dev/posts/programmers-on-the-verge-of-extinction/&quot;&gt;my last post&lt;/a&gt; then you have a pretty good idea how conflicted I feel about AI and how I’m trying to find balance within programming. In that search I think I found a path I’m going to take for now.&lt;/p&gt;
&lt;p&gt;My wife got me a &lt;a href=&quot;https://www.clockworkpi.com/picocalc&quot;&gt;Picocalc&lt;/a&gt; for Christmas. It just got here this week, and man it’s a fun device. Essentially a keyboard, speaker, and screen hooked up to a Raspberry Pi Pico H. It includes multiple firmwares you can run, but the most general purpose one is for &lt;a href=&quot;https://mmbasic.com/&quot;&gt;mmbasic&lt;/a&gt;, a remake of basic for micro controllers. Back in the day when people got computers, they had to learn how to program to use them using something like basic. You would write programs that helped compute or handle tasks. What’s nice about mmbasic is that it has a few extra tricks that older computers didn’t really have.&lt;/p&gt;
&lt;p&gt;With that said, one of my goals is to return to basic (pun intended) and use this little device to solve programming challenges. Having the limitations of basic feels like a nice creative restriction. Ironically, I found a good use for AI in this setup. The manual for mmbasic is a 200 page PDF, not something I can read on this tiny device. So I had Claude dissect the manual into smaller markdown chunks I can open on the picocalc. Now I have everything I need on this chunky scientific calculator, and wherever I take it, I can build.&lt;/p&gt;
&lt;p&gt;I’m still working through the Rust book which I plan to continue alongside the picocalc, but if push comes to shove I’ll put it on pause until I have more dedicated time. I still want to learn it for the versatility, and just the lower level knowledge that comes with it.&lt;/p&gt;
&lt;p&gt;I plan to still use AI in my day to day work, and in some of my side projects, but that’s about it. There are more thoughts on the topic of AI since I discovered &lt;a href=&quot;https://radiant.computer&quot;&gt;radiant.computer&lt;/a&gt; but will save that for a longer post. For now, I’m going back to basic.&lt;/p&gt;</content:encoded></item><item><title>Scaling Your NFT Project: A Beginner’s Guide to IPFS</title><link>https://stevedylan.dev/posts/beginners-guide-to-ipfs/</link><guid isPermaLink="true">https://stevedylan.dev/posts/beginners-guide-to-ipfs/</guid><description>Storage ain’t sexy, but if web3 is gonna take a leap, it&apos;s one of the biggest problems we need to solve for.</description><pubDate>Fri, 07 Oct 2022 04:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://bueno.art/blog/pinata-ipfs-guide&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/bueno.hs0ANfkS_16MaMq.webp&quot; alt=&quot;Link to Bueno&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Bueno &lt;/a&gt;
&lt;p&gt;As an NFT creator, you want to make sure your files are safe, verifiable, and that you’re not paying an arm and a leg to keep them that way. IPFS (accessed by a pinning service like Pinata) solves all these issues, but before we talk about how, let’s paint the picture and explain how all of this applies to you as an NFT creator and collector.&lt;/p&gt;
&lt;h2&gt;The NFT Storage Problem&lt;/h2&gt;
&lt;p&gt;If you’re familiar with Ethereum, you know that it costs gas fees to mint, transfer, or buy NFTs. You’re paying the network to do all of that computing for you, and in the case of storing data on the blockchain, every character counts (and costs). Storing large amounts of data on-chain can be incredibly expensive. In the case of Ethereum, we’re talking ~$40M to store 1GB of data. Yikes.&lt;/p&gt;
&lt;p&gt;To avoid having to store all of that data on-chain, most NFTs are stored as metadata that points to an image off the blockchain where the actual file lives. It’s like when you share a Dropbox link. The link you share isn’t the actual thing, it just points you to where the actual thing is stored. That’s why most early NFT projects were actually stored on Dropbox or a similar cloud service like AWS or Google Drive. While a full PNG of an NFT might be 4 mb, its metadata is only about 800 bytes. Definitely a step in the right direction, but this solution caused another problem: people started getting rugged.&lt;/p&gt;
&lt;p&gt;Let’s say an influencer decides to launch a new NFT project called “Ceramic Cars.” Sounds cool. She uploads an image of the first drop of cars to a storage provider and each car is named “crazycar1.png.” Luckily you snag one. She put a link to that image in your NFT metadata that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://storageservice.com/friend/crazycar1.png&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks fine at first. But here’s the kicker: at any time, that influencer or the storage provider can simply remove that image. Suddenly the NFT is useless. And if she really wanted to be a jerk, she could replace it with a poop emoji with the same name. And since there is no regulation or laws around this, you would basically be screwed with no way of getting what you paid for. And that’s exactly what was happening in those early days: people were getting rugged left and right. These problems begged for a solution. That solution was &lt;a href=&quot;https://ipfs.io&quot;&gt;IPFS&lt;/a&gt; - the InterPlanetary File System.&lt;/p&gt;
&lt;h2&gt;How does IPFS solve the storage problem?&lt;/h2&gt;
&lt;p&gt;To understand how IPFS works, you need to understand how file sharing has always worked, before the days of web3.&lt;/p&gt;
&lt;p&gt;The traditional way of sharing files over the internet is through centralized servers. For example, if you write up and send a Tweet, the data is sent to Twitter’s servers. Then when other users want to see that Tweet, the servers pass that information to them. It’s a direct up and down motion.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Rather than straight up and down, IPFS is up, down, and side to side. IPFS is a decentralized network of nodes that share content with each other. If we imagine a Twitter run on IPFS, every user would have their own IPFS node that would upload data to the network, and as it is requested by other users, the data is passed from node to node. Rather than one company holding and owning that data, everyone owns that data. IPFS gives users the ability to own their data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;How does IPFS protect your NFTs?&lt;/h2&gt;
&lt;p&gt;When you share a file through IPFS, the file is run through a cryptographic algorithm that gives you something called the Content Identifier, or “CID” for short. This CID plays a huge part in how IPFS works and operates, and it looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;QmRAuxeMnsjPsbwW8LkKtk6Nh6MoqTvyKwP3zwuwJnB2yP&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every CID is determined by the content of the file, making it completely unique. If you change a picture by even one pixel, it would give you a different CID. This unique identifier makes content verifiable. In our example of an image being swapped out for a poop emoji, it wouldn’t be possible since the two images would have completely different CIDs. Combine this power with blockchain, and you get a reference to an image that is verified and cannot be changed.&lt;/p&gt;
&lt;p&gt;As an NFT creator, this is a huge benefit that you can use to reassure your audience. Any NFT that they collect from your collection will be 100% verifiable. Beyond that, the CID makes content portable and addressable.&lt;/p&gt;
&lt;p&gt;When I buy an NFT, I can get the CID for the metadata and image, then pin it to my Pinata account. That means, if the project owner for some reason stops pinning the data on their end, it can persist through my responsibility. As long as I pin those CIDs, my NFT will live on. This means you can truly OWN your data. It gives communities the ability to preserve content that they want available, much like how art is preserved today in museums by people who value it.&lt;/p&gt;
&lt;h2&gt;How Pinata Connects Creators to IPFS&lt;/h2&gt;
&lt;p&gt;As you might already know, Pinata is an IPFS pinning service that makes IPFS easy for creators. We do this in several ways. First and foremost, we help creators get their original content on IPFS through our easy-to-use web app. Once it’s uploaded through Pinata, it’s automatically pinned to IPFS and gets a unique CID. Just like that, no extra work.&lt;/p&gt;
&lt;p&gt;After uploading, the next step of course is sharing it! One way we do this is through Dedicated Gateways, which serve as hyperspeed bridges between IPFS and regular websites. With a Dedicated Gateway you can quickly share content, use a custom domain to match your brand, stream videos, and more.&lt;/p&gt;
&lt;p&gt;Additionally, Pinata has also pioneered a new feature called “Submarining,” our take on unlockable content. Content creators can give their audience access to exclusive gated content via NFT ownership, Retweets and geo-location. The possibilities are endless with this tool; exclusive content for your NFT holders, NFTs being used as movie passes for film makers, or music albums by artists!&lt;/p&gt;
&lt;p&gt;And there you have it. Hopefully this gives you an idea of the power of IPFS, and how it can help creators like you protect your content and distribute it in a way that no other platform can. We invite you to sign up for an account and discover Pinata for yourself. Our community is here to help creators become their own platform and share content their way. Happy pinning!&lt;/p&gt;</content:encoded></item><item><title>Building a Guestbook with PGlite, Clerk, and Pinata</title><link>https://stevedylan.dev/posts/building-a-guestbook-with-pglite-clerk-and-pinata/</link><guid isPermaLink="true">https://stevedylan.dev/posts/building-a-guestbook-with-pglite-clerk-and-pinata/</guid><description>A quick walkthough of how I built a guestbook for my website</description><pubDate>Tue, 24 Sep 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;When I was first getting started in web development I remember seeing someone’s website and was immediately impressed by one thing: a guestbook. You could sign in with Github and leave a message, similar to someone’s Facebook wall back in the day. I thought that was the coolest thing but had no idea how to build it. Fast forward to this weekend, I was reminded how cool that was and I decided to build it for my own website.&lt;/p&gt;
&lt;p&gt;Normally I would use Supabase DB + Auth for something like this for the ease of use, but I wanted to take a slightly different route. I’ve been playing with PGlite quite a bit in the last few weeks and decided it would be fun to see if I can host it as a server. My coworker Justin recently had a &lt;a href=&quot;https://pinata.cloud/blog/how-to-build-a-persistent-crud-app-using-sqlite-and-deno-js/&quot;&gt;post about building a CRUD app with Deno, SQLite and Pinata&lt;/a&gt; to handle backups, and it seemed like the perfect setup to pair with PGlite.&lt;/p&gt;
&lt;p&gt;With a weekend to spare I built this out and you can check it out now with &lt;a href=&quot;/guestbook&quot;&gt;this link&lt;/a&gt;! In this post I’ll show you how I built the server, integrated Clerk auth into both the server and the backend, and finally rendering it out into a UI for people to use.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;When it came to setting up this project there were several moving pieces that had to work together.&lt;/p&gt;
&lt;h3&gt;Pinata&lt;/h3&gt;
&lt;p&gt;Naturally since I work for Pinata I already have an account ready to go for this project, but if you haven’t tried it yet then you really should! Creating your account, getting your API key, and firing up the SDK takes just minutes to add file uploads to your account. It was actually the easiest piece of this project, so don’t be shy and try it out &lt;a href=&quot;https://app.pinata.cloud/register&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Clerk&lt;/h3&gt;
&lt;p&gt;Since I wasn’t going to use Supabase for this project I decided to try out &lt;a href=&quot;https://clerk.com&quot;&gt;Clerk&lt;/a&gt;. I’ve heard nothing but good things, and there’s a reason for that. This platform truly understands how to make auth easy while also giving you loads of control if you want it. Setting it up for my website and the server was super simple, just followed the quick start guide when I onboarded and it was done. Since I’m using Github as my login method I also followed &lt;a href=&quot;https://clerk.com/docs/authentication/social-connections/github&quot;&gt;this guide&lt;/a&gt; to set that piece up.&lt;/p&gt;
&lt;h3&gt;Server&lt;/h3&gt;
&lt;p&gt;There are so many options out there for building a server/API, so by all means use what feels best for your needs, but I personally love using Hono via Bun. Starting up the project was simple as the command below:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bun create hono guestbook-db&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the repo was created and initialized I installed a few other packages I would need.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bun add pinata @clerk/backend @hono/clerk-auth @electric-sql/pglite croner&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With everything installed I did some initial setup for the project. In the &lt;code&gt;index.ts&lt;/code&gt; file I imported my dependencies, setup some of the middleware like Clerk and CORS, and just have an entry point setup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Hono&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;hono&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;cors&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;hono/cors&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;PGlite&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@electric-sql/pglite&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;./pinata&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Cron&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;croner&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;clerkMiddleware&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;getAuth&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@hono/clerk-auth&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Hono&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/*&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;cors&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;clerkMiddleware&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Welcome!&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only other file I needed to add was in the same &lt;code&gt;src&lt;/code&gt; folder called &lt;code&gt;pinata.ts&lt;/code&gt; with a quick export of the Pinata SDK instance.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;pinata&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pinataJwt&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PINATA_JWT&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pinataGateway&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GATEWAY_URL&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we have a simple &lt;code&gt;.env&lt;/code&gt; to handle our secrets and vars.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;PINATA_JWT= # The primary Pinata API key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;GATEWAY_URL= # Your Pinata gateway domain e.g. example.mypinata.cloud&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;GROUP_ID= # Group ID where we will store backups&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;CLERK_PUBLISHABLE_KEY= # Clerk public key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;CLERK_SECRET_KEY= # Clerk private key&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ADMIN_KEY= # optional key for your own overrides&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Backend&lt;/h2&gt;
&lt;p&gt;With the database server setup it was time to work on adding in the database itself. PGlite is unique in that it runs on WASM, making it lightweight and operable in a browser. For this project I took advantage of the size by creating a backup and restore feature into the server. Instead of relying on a local disk setup, the server will routinely backup copies of the database to Pinata, and if at any point it needs to restart it will reboot from the last backup. Depending on how your database library works you could also add it writing to disk as well for extra security.&lt;/p&gt;
&lt;p&gt;To setup this workflow I created the following &lt;code&gt;initDb&lt;/code&gt; function and call it using an immediately invoked function expression.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async function &lt;/span&gt;&lt;span&gt;initDb&lt;/span&gt;&lt;span&gt;(): &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GROUP_ID&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;order&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;DESC&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;gateways&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; dbFile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;Blob&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			db&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PGlite&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;loadDataDir&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;created_at&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		db&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PGlite&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;./guestbook&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	       CREATE TABLE IF NOT EXISTS messages (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         id SERIAL PRIMARY KEY,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         note TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         author TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         user_id TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         pfp_url TEXT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	         username TEXT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	       );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;     `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;&quot;New DB Created&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		throw &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;(async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;initDb&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Database initialized. Snapshot:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Failed to initialize database:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This initialization function will first check if there is a backup file using Pinata. We keep the files organized by creating a &lt;code&gt;group&lt;/code&gt;, and this allows us to filter our files by said group and order them by date. This group was created beforehand, and you could use a script like the following to do so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;pinata&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;PinataSDK&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pinataJwt&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PINATA_JWT&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pinataGateway&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;example-gateway.mypinata.cloud&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;My New Group&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is a database backup in the group, then we download it as a file using the SDK once more with &lt;code&gt;pinata.gateways.get&lt;/code&gt;. Then we simply create and load up a new instance of the database using &lt;code&gt;new PGPlite({ loadDataDir: file })&lt;/code&gt;. However if there isn’t a database backup on Pinata, the function will create a new instance of DB locally and create the default table. Then it’s just a matter of calling the function as soon as the server starts!&lt;/p&gt;
&lt;p&gt;With the database setup and ready to go it was time to start building some endpoints. The first one would be a simple &lt;code&gt;GET /messages&lt;/code&gt; to fetch all current rows in the database and return them as JSON. Additionally we structured the query to reverse the feed results so the most recent would be at the top.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/messages&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;ret&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		SELECT * FROM messages ORDER BY id DESC LIMIT 50;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  `&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ret&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Restore database first&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course we’ll need an endpoint to actually add messages to the database and we’ll use that with the same route &lt;code&gt;/messages&lt;/code&gt; but as a &lt;code&gt;POST&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;interface &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	user_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	username&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/messages&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (await &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()) as &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getAuth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;clerkClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;clerk&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; typeof &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; &quot;string&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Invalid note&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;clerkClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;users&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getUser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; auth&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;INSERT INTO messages (note, author, user_id, pfp_url, username) VALUES ($1, $2, $3, $4, $5)&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				[&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;firstName&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;imageUrl&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error creating message:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to create message&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is where things start to get good. Instead of having the client send a full JSON payload, we can use Clerk to securely fetch some of that information for us as we use Github OAuth as the only authentication method. In the database we have the &lt;code&gt;note&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt; or name of the writer, &lt;code&gt;user_id&lt;/code&gt; from Clerk which we’ll get into later, and the &lt;code&gt;username&lt;/code&gt; for link to the writer’s Github profile. All we have to do is use the Clerk Hono middleware to get the &lt;code&gt;auth&lt;/code&gt; object and verify the user is logged in, otherwise we send a 401. We also make sure that the &lt;code&gt;note&lt;/code&gt; attached from the client is a string. Finally we can get a &lt;code&gt;user&lt;/code&gt; object from Clerk using the &lt;code&gt;userId&lt;/code&gt; and getting all the information we need! Then we just insert a row into the table and return it back to the client.&lt;/p&gt;
&lt;p&gt;In the event that someone mistyped something and wanted to delete their message, or if someone left something unkind, we will want a method to delete it. This time we’ll use &lt;code&gt;DELETE /messages/:id&lt;/code&gt; and get the &lt;code&gt;id&lt;/code&gt; of a message using the path param.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;delete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/messages/:id&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;param&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getAuth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;checkQuery&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;MessageRow&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;SELECT user_id FROM messages WHERE id = $1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				[&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if (&lt;/span&gt;&lt;span&gt;checkQuery&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Message not found&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;404&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;messageUserId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; checkQuery&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;user_id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if (&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; auth&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;userId&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; messageUserId&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					{ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not authorized to delete this message&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					403&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;DELETE FROM messages WHERE id = $1&quot;&lt;/span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			if (&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;affectedRows&lt;/span&gt;&lt;span&gt; ===&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Message not found&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;404&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Ok&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error deleting message:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to delete message&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this endpoint we have to check a few things. First we need to grab the &lt;code&gt;id&lt;/code&gt; of the target note. Then we need to do a general auth check to see if the requester is a user or the admin (me). If they pass that then we do a query of the messages for that note &lt;code&gt;id&lt;/code&gt;, and then we do a check if the requester is either the admin or the author of the note. If they pass then we delete the row from the table and send back an &lt;code&gt;Ok&lt;/code&gt; message.&lt;/p&gt;
&lt;p&gt;That really covers the majority of what I wanted in the guestbook, but if we wanted to we could easily add a &lt;code&gt;PUT&lt;/code&gt; message as well to enable editing old messages. There are a few things left to do though, including our &lt;code&gt;/restore&lt;/code&gt; and &lt;code&gt;/backup&lt;/code&gt; routes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/restore&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		await &lt;/span&gt;&lt;span&gt;initDb&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Ok&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error restoring database:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to restore database.&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/backup&quot;&lt;/span&gt;&lt;span&gt;, async (&lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;header&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Authorization&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;admin&lt;/span&gt;&lt;span&gt; !==&lt;/span&gt;&lt;span&gt; process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ADMIN_KEY&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				message&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;You are not logged in.&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			401&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dumpDataDir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;auto&quot;&lt;/span&gt;&lt;span&gt;)) as &lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GROUP_ID&lt;/span&gt;&lt;span&gt; ??&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error backing up database:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;c&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Failed to backup database&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are really simple routes but play an important role in our database. The &lt;code&gt;/restore&lt;/code&gt; route is a manual reset without restarting the server, where it runs our &lt;code&gt;initDb()&lt;/code&gt; function from the beginning. &lt;code&gt;/backup&lt;/code&gt; will dump our current database state as a compact zip file into our Pinata group using the best upload experience: &lt;code&gt;pinata.upload.file&lt;/code&gt; &lt;em&gt;chef’s kiss&lt;/em&gt;. We’ll use the same logic in our cron job to regularly backup the database.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;job&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Cron&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;0 0 * * *&quot;&lt;/span&gt;&lt;span&gt;, async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if (&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (await &lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dumpDataDir&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;auto&quot;&lt;/span&gt;&lt;span&gt;)) as &lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;pinata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;dbFile&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GROUP_ID&lt;/span&gt;&lt;span&gt; ??&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;upload&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like that our server is ready!&lt;/p&gt;
&lt;h2&gt;Front End&lt;/h2&gt;
&lt;p&gt;Now the fun part, pulling everything together in the app 😎 To start I needed to install and setup Clerk for Astro using &lt;a href=&quot;https://clerk.com/docs/quickstarts/astro&quot;&gt;this guide&lt;/a&gt;. To stay somewhat in my comfort zone I used the React plugin for Astro so I could make a component that handles everything, and once again Clerk &lt;a href=&quot;https://clerk.com/docs/references/astro/react&quot;&gt;made that easy too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I made a new component called &lt;code&gt;GuestbookFeed.tsx&lt;/code&gt; and slowly built out the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useStore&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@nanostores/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/client&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	UserButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignUpButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	user_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pfp_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	username&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; import.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PUBLIC_API_ENDPOINT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default function &lt;/span&gt;&lt;span&gt;GuestbookFeed&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setMessages&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;[]&amp;gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsSending&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setInputText&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setMessages&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} finally {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	function &lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInForceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInFallbackRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						forceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						mode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;modal&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						Sign&lt;/span&gt;&lt;span&gt; in &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; Github&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex items-start gap-4 w-full&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;UserButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							appearance&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								layout: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									animations: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							afterSignOutUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;p-1 bg-bgColor border-current border-2 rounded-md w-96&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;&quot;Posting...&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Post&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Loading&lt;/span&gt;&lt;span&gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			) : (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					{&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row justify-between items-start&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row gap-2 items-start&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex-shrink-0 h-7 w-7&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;h-full w-full rounded-full object-cover&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;pfp_url&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col justify-between&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;font-bold text-gray-400&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										{&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;break-words&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user_id&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () =&amp;gt; &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									x&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					))}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It’s a lot of code to look at, but when you break it down it’s pretty easy to understand, so let’s do that now. To start we have our main imports at the top.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/client&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedIn&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignedOut&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	UserButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	SignUpButton&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} from &lt;/span&gt;&lt;span&gt;&quot;@clerk/astro/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	author&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	user_id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	pfp_url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	username&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; import.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PUBLIC_API_URL&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default function &lt;/span&gt;&lt;span&gt;GuestbookFeed&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setMessages&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;[]&amp;gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsSending&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setInputText&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$sessionStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;$userStore&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we have some simple yet important pieces to our component. The first is our stores like &lt;code&gt;$sessionStore&lt;/code&gt; and &lt;code&gt;$userStore&lt;/code&gt;. These are provided by the Clerk middleware and will give us access to the logged in user’s session tokens to authorize requests. We also have some neat components from Clerk which we’ll get into shortly. Finally we have a slew of state from React to handle inputs and loading states, as well as the &lt;code&gt;useStore&lt;/code&gt; for our auth stores.&lt;/p&gt;
&lt;p&gt;Below that we have just a few primary functions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setMessages&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} finally {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	function &lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setInputText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsSending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	async function &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/messages/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;DELETE&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getToken&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			await &lt;/span&gt;&lt;span&gt;fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		fetchMessages&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the top we have our function to fetch messages from our API. We made this a public endpoint so we don’t have to authorize it at all, and we just store the array of messages from the database into our &lt;code&gt;Message&lt;/code&gt; array. Next we have our &lt;code&gt;inputHandler&lt;/code&gt; as well as our &lt;code&gt;sendMessage&lt;/code&gt; function, which again just takes the input state and sends it as an API request. What’s special here is the &lt;code&gt;Authorization&lt;/code&gt; header where we use the &lt;code&gt;await session.getToken()&lt;/code&gt; so that our API can authenticate the request. Simple, clean, and effective. If successful we’ll clear the input and refetch the messages. We also have a &lt;code&gt;deleteMessage&lt;/code&gt; function so the author can delete a note from the site if they want to, and finally we have a simple &lt;code&gt;useEffect&lt;/code&gt; to load our messages when the page loads.&lt;/p&gt;
&lt;p&gt;Now all that’s left is rendering our UI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInForceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						signInFallbackRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						forceRedirectUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						mode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;modal&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						Sign&lt;/span&gt;&lt;span&gt; in &lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt; Github&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;SignUpButton&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedOut&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex items-start gap-4 w-full&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;UserButton&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							appearance&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								layout: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									animations: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							afterSignOutUrl&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/guestbook&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;p-1 bg-bgColor border-current border-2 rounded-md w-96&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputHandeler&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;inputText&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;border-2 border-current rounded-md py-1 px-2 cursor-pointer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sendMessage&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;isSending&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;&quot;Posting...&quot;&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Post&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;SignedIn&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Loading&lt;/span&gt;&lt;span&gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			) : (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					{&lt;/span&gt;&lt;span&gt;messages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Message&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row justify-between items-start&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							key&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-row gap-2 items-start&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex-shrink-0 h-7 w-7&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;h-full w-full rounded-full object-cover&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;pfp_url&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex flex-col justify-between&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;`https://github.com/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;username&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;font-bold text-gray-400&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;										{&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;break-words&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;{note.&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							{&lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;user_id&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () =&amp;gt; &lt;/span&gt;&lt;span&gt;deleteMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;note&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;									x&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;								&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					))}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, a lot of code, but overall not too complicated. At the top we have our Clerk components that determine what a user sees based on their login state. If they’re not logged in then we have a &lt;code&gt;&amp;lt;SignUpButton /&amp;gt;&lt;/code&gt;, and once they do sign in we show them a profile button with &lt;code&gt;&amp;lt;UserButton /&amp;gt;&lt;/code&gt;, and input to put a message in, and a button to &lt;a href=&quot;https://youtu.be/RSuLFvalhnQ&quot;&gt;send it&lt;/a&gt;. Below our Clerk components we have the actual message feed which renders our &lt;code&gt;messages&lt;/code&gt; array, and includes things like their pfp, name, message, and even an &lt;code&gt;&amp;lt;a /&amp;gt;&lt;/code&gt; tag to link to their Github profile. Finally we also have a little button that will appear for that specific user if they want to delete one of their messages, but not anyone else’s.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Overall this was a really great little project to build and it touches some of the key pieces of the web: uploads/storage, databases, backend APIs, and front end UIs. It gives you a great feel and stretches your understanding of how all of these pieces work together and how the space is moving. Using tools like Pinata or Clerk really give you the best of both worlds when building tools, which is a great developer experience and a solid finished product. Be sure to &lt;a href=&quot;https://stevedylan.dev/guestbook&quot;&gt;drop a message&lt;/a&gt; there now if you haven’t already, and thanks for reading! :)&lt;/p&gt;
&lt;h3&gt;Repos&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/stevedylandev/guestbook-db&quot;&gt;Database &amp;amp; API&lt;/a&gt;
&lt;a href=&quot;https://github.com/stevedylandev/stevedsimkins-dev-astro&quot;&gt;Astro Site&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Building Snippets.so</title><link>https://stevedylan.dev/posts/building-snippets-so/</link><guid isPermaLink="true">https://stevedylan.dev/posts/building-snippets-so/</guid><description>Insights into why snippets.so was built and the tech stack behind it</description><pubDate>Fri, 02 Aug 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;“I don’t know why this isn’t working” is a question I get often, and I usually respond with “could you share your code with me?” Next thing you know I get a cell phone image of someone’s fingerprint covered laptop screen with a blur of code on it. I wish this wasn’t the first time, and I certainly hope it’s the last, but to be honest there was a problem.&lt;/p&gt;
&lt;p&gt;I continue to find myself needing to share a code snippet with a user, or needing them to share one with me. Of course there is Pastebin, perhaps the most popular, but it’s littered with ads and bloat that I hated to use or point people to. There’s also Gists, which generally are great, but only if you want them to stick to your Github profile (which I usually don’t). On the other side of the spectrum there’s &lt;a href=&quot;https://ray.so&quot;&gt;Ray.so&lt;/a&gt;, a beautiful app built by the team at Raycast that generates the best looking images of code you can get. Unfortunately that doesn’t always help me if I need to copy and paste the code somewhere.&lt;/p&gt;
&lt;p&gt;This is what led me to build &lt;a href=&quot;https://snippets.so&quot;&gt;Snippets.so&lt;/a&gt;, my take on a cleaner and more efficient way to share code. I wanted the style and ease of Ray.so with the simplicity of Pastebin, and I’m happy with the ground I found in the middle. I decided to take a few moments to share how it was built and some of the unique stack choices I made in hopes that you may find it beneficial, or perhaps use Snippets.so for your own code sharing needs.&lt;/p&gt;
&lt;h2&gt;The Stack&lt;/h2&gt;
&lt;p&gt;For this app I took a few obvious choices for the stack, but others that might puzzle the majority of developers out there. Let’s start right off the bat with the weird one ;)&lt;/p&gt;
&lt;h3&gt;IPFS&lt;/h3&gt;
&lt;p&gt;If you’re not familiar the InterPlanetary File System (IPFS), it’s a unique distribute file sharing protocol commonly used alongside blockchains. In the app I use it to store JSON files that contain everything I need for each snippet, and it’s convenient that the CID (Content Identifier) or address to the file works as a unique identifier as well which we’ll cover soon. However there are some other benefits I wanted to outline as well.&lt;/p&gt;
&lt;p&gt;One of the main reasons blockchain developers use IPFS for their offchain storage is due to immutability. Once something is on the network it cannot change, and in our case that’s very useful. People generally don’t need to update a snippet once they share it, and it makes the content ideal for a CDN for high cache hit rates. Once a snippet is loaded at least once, the other requests will be very speedy.&lt;/p&gt;
&lt;p&gt;Since CIDs are cryptographically determined by the content of the file, uploading the same content will give you the same CID. This prevents any possibility of duplicate storage and taking up unwanted space. Additionally CIDs work both as a content hash and the address to the content. It’s a publicly accessible network where anyone can take a CID and access the content through a gateway, which adds a nice layer of interoperability.&lt;/p&gt;
&lt;h3&gt;Next.js&lt;/h3&gt;
&lt;p&gt;I’ve certainly had bad history with Next, particularly with heavy caching in API routes. However it does provide a pretty nice experience when doing server side rendering, which is the majority of Snippets. I’ve also built so many projects on Next that its almost second nature to me, and I appreciate the speed that brings. If I had to pick another framework I might try Astro instead. For this app Next worked just fine.&lt;/p&gt;
&lt;h3&gt;shadcn/ui&lt;/h3&gt;
&lt;p&gt;Undoubtedly the best component library out there, I love every chance I get to use it. Anything I really need is right there in the docs, and its super easy to customize if I need to. Would highly recommend giving it a shot if you haven’t already!&lt;/p&gt;
&lt;h2&gt;Building the Editor&lt;/h2&gt;
&lt;p&gt;When it comes to enabling writing in an app beyond just a text area there are many library choices out there. For Snippets I went with &lt;code&gt;@uiw/codemirror&lt;/code&gt; for several reasons. For starters it was pretty easy to use and setup, had it running in no time.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;CodeMirror&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;@uiw/react-codemirror&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;javascript&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@codemirror/lang-javascript&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;console.log(&apos;hello world!&apos;);&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; React&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;useCallback&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;viewUpdate&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;val:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;val&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;CodeMirror&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;200px&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			extensions&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{[&lt;/span&gt;&lt;span&gt;javascript&lt;/span&gt;&lt;span&gt;({ jsx&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			onChange={onChange}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default App;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also has a huge number of supported languages for syntax highlighting. For this app I chose some popular ones and used an extension to load based on a state change.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// other imports&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;loadLanguage&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@uiw/codemirror-extensions-langs&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;languages&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@/lib/languages&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export function &lt;/span&gt;&lt;span&gt;CodeForm&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;readOnly&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt; }: &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;defaultCode&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setLang&lt;/span&gt;&lt;span&gt;]: &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;tsx&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;languageExtension&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; useMemo&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;extension&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; loadLanguage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;extension&lt;/span&gt;&lt;span&gt; ? [&lt;/span&gt;&lt;span&gt;extension&lt;/span&gt;&lt;span&gt;] : [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, [&lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;/*rest of UI*/&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;CodeMirror&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text-md opacity-75 p-2 sm:w-[600px] sm:h-[700px] w-[350px] h-[450px]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100%&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100%&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			basicSetup&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				lineNumbers: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				foldGutter: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			extensions&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;languageExtension&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;onChange&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			theme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;githubLight&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			readOnly&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;readOnly&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;/*...*/&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this library does have several themes to choose from I decided to stick with a light theme (blasphemy I know haha). A simple GitHub light theme with reduced opacity actually does a decent job. Definitely looked into trying to customize it a bit more but the way it handles syntax highlighting isn’t as good as something like shiki. This might be something I look into down the road.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;When it comes to actually uploading the content after the user has put their code in I used the built in API routes in Next.js, simply passing in the &lt;code&gt;content&lt;/code&gt; of the file, &lt;code&gt;name&lt;/code&gt; if one given, and &lt;code&gt;lang&lt;/code&gt; for the language used from the dropdown menu. As mentioned earlier I’m using Pinata/IPFS for uploading the content and there is a convenient API route for JSON objects, so its a simple plug and post operation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;NextResponse&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;next/server&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import type { &lt;/span&gt;&lt;span&gt;NextRequest&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;next/server&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;revalidate&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;type &lt;/span&gt;&lt;span&gt;PinResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	IpfsHash&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	PinSize&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	Timestamp&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	isDuplicate&lt;/span&gt;&lt;span&gt;?: &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export async function &lt;/span&gt;&lt;span&gt;POST&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NextRequest&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;request&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			pinataContent&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				lang&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			pinataMetadata&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			pinataOptions&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				cidVersion&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;https://api.pinata.cloud/pinning/pinJSONToIPFS&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				Authorization&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;`Bearer &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PINATA_JWT&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;PinResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;NextResponse&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;NextResponse&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The API request returns an &lt;code&gt;IpfsHash&lt;/code&gt; or CID that acts as both a unique identifier and the address to the content on the network. Inside the &lt;code&gt;submissionHandler&lt;/code&gt; function used for uploading I’m able to use &lt;code&gt;router.push&lt;/code&gt; to move the user from the editing page to the dynamic route where you view the snippet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;async function &lt;/span&gt;&lt;span&gt;submitHandler&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			lang&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`/api/upload`&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			headers&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setComplete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		router&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`/snip/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IpfsHash&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Building the Renderer&lt;/h2&gt;
&lt;p&gt;Now that the user has uploaded their snippet to IPFS and we have a CID representing the JSON data, we can use it as a path variable and extract the data from it. In our app we use the following file structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;app&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── api&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   ├── languages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   │   └── route.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│   └── upload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;│       └── route.ts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── favicon.ico&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── globals.css&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── layout.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;├── page.tsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── snip&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── [cid]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── page.tsx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;[cid]&lt;/code&gt; is our dynamic path variable, and with server side pages we can pull the data from IPFS and feed it into our renderer component in one fell swoop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Footer&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@/components/footer&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@/components/header&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;ReadOnlyEditor&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@/components/read-only-editor&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;async function &lt;/span&gt;&lt;span&gt;fetchData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`https://&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;GATEWAY_DOMAIN&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/ipfs/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;req&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default async function &lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt; }: { &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; } }) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; params&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetchData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;flex min-h-screen flex-col items-center justify-start sm:justify-center&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;Header&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;ReadOnlyEditor&lt;/span&gt;&lt;span&gt; content&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{data.&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{data.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{data.&lt;/span&gt;&lt;span&gt;lang&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;Footer&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This really is one of my favorite parts with Next.js App router; if you can structure the project correctly where you feed server data into client components you get the best of both worlds.&lt;/p&gt;
&lt;p&gt;With our &lt;code&gt;cid&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, and &lt;code&gt;lang&lt;/code&gt; we can rebuild what the editor saw with a “read-only” version of the same editor.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;CodeMirror&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text-md font-commitMono h-[450px] w-[350px] p-2 opacity-75 sm:h-[700px] sm:w-[600px]&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100%&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;100%&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	value&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	basicSetup&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		lineNumbers: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		foldGutter: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		rectangularSelection: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	extensions&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;languageExtension&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	theme&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;githubLight&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	readOnly&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	editable&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Along with viewing the content we can also enable some fun stuff like copying it to clipboard, downloading it as a file, or sharing the snippet with a link!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1722565319/Screenshot-Arc-08-01-2024-22-24_muxrrg.gif&quot; alt=&quot;gif of share page&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;API + CLI&lt;/h2&gt;
&lt;p&gt;Since there isn’t really any authentication in this app and anyone can upload snippets as much as they want, I figured “why not make the API accessible?” Anyone can make an API request to the app to make a snippet and use the data returned to make the link!&lt;/p&gt;
&lt;p&gt;Request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; --location&lt;/span&gt;&lt;span&gt; &apos;/blog-images/other/api/upload&apos;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          --header&lt;/span&gt;&lt;span&gt; &apos;Content-Type: application/json&apos;&lt;/span&gt;&lt;span&gt; \&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          --data&lt;/span&gt;&lt;span&gt; &apos;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;content&quot;: &quot;console.log(\&quot;hello world!\&quot;)&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;: &quot;hello.ts&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;lang&quot;: &quot;typescript&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          }&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Returns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;IpfsHash&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bafkreiccdt64k6d4wjgz5ebqee4rvmkauoiygc5egwtssl2zqq3o74zlti&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;PinSize&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;81&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;Timestamp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2024-07-10T02:25:51.052Z&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;isDuplicate&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Link:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://snippets.so/snip/bafkreiccdt64k6d4wjgz5ebqee4rvmkauoiygc5egwtssl2zqq3o74zlti&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course this led me to make a CLI in Go for the app as well, which you can download with &lt;code&gt;brew install stevedylandev/snippets-cli/snippets-cli&lt;/code&gt; or by building it yourself from &lt;a href=&quot;https://github.com/stevedylandev/snippets-cli&quot;&gt;this repo&lt;/a&gt;. To use it you can just run the command &lt;code&gt;snip&lt;/code&gt; followed by the file you want to upload.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;snip&lt;/span&gt;&lt;span&gt; helloWorld.ts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Even though this was a relatively simply project I love how it turned out. I started with the goal of making a better tool that I would use and I did just that. A smile creeps onto my face every time I have the chance to use it when helping another developer, and its from the satisfaction of programming away a problem. I truly believe that the most meaningful pieces of code we write are the ones that make our lives just a little bit better, and I can’t wait to keep doing just that.&lt;/p&gt;</content:encoded></item><item><title>A Case for IPFS on Layer 1 Blockchains</title><link>https://stevedylan.dev/posts/case-for-ipfs-on-l1-chains/</link><guid isPermaLink="true">https://stevedylan.dev/posts/case-for-ipfs-on-l1-chains/</guid><description>There are so many new blockahins appearing and they all need a solution to off-chain storage</description><pubDate>Tue, 06 Dec 2022 05:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://medium.com/pinata/a-case-for-ipfs-on-layer-1-blockchains-like-solana-aptos-and-sui-165a9732c214&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/medium.CI909Xjl_oL8pb.webp&quot; alt=&quot;Link to Medium&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Medium &lt;/a&gt;
&lt;p&gt;There has been a Cambrian explosion of new layer 1 blockchains — all with brand new technologies that make them faster and more efficient. However there is one problem that no blockchain has managed to overcome: asset storage.&lt;/p&gt;
&lt;p&gt;Blockchains are great for keeping a ledger of transactions, but storing large amounts of data on-chain is expensive and doesn’t scale well. Worse, most of the centralized off chain storage methods have a bad reputation of enabling rug pulls and bad actors in the NFT space. Even though Pinata solves these problems, giving creators and developers easy access to the heaven-sent distributed file system known as IPFS, many of these new layer 1 chains still don’t understand why IPFS is the solution to their problems. Let’s break it down.&lt;/p&gt;
&lt;h2&gt;A Brief history of NFTs (How we got here)&lt;/h2&gt;
&lt;p&gt;NFTs and web3 are still pretty bright and new, but they have already created quite the history over the last 5 to 6 years. Ethereum has been the original playground for NFTs since 2016, and with that early usage comes experience. There were a lot of problems to overcome when creating NFTs, mainly storage.&lt;/p&gt;
&lt;p&gt;When it comes to storing data on chain, every byte counts and costs gas. If you have too many bytes, it will get expensive quickly. Some NFTs took the approach of on chain SVGs which work in a pinch, but if you want to host something like a Bored Ape, it would cost a lot of money to deploy 10,000 of them on chain. Because of this, Ethereum turned to off chain storage and used a link or “token URI” to point to metadata and images not on Ethereum. This saved gas, but the solutions had their own problems. Most of the early storage solutions were simply centralized storage services or private servers, and using these came with the risk of rugs. With a server or storage provider, the link “&lt;a href=&quot;https://steve.com/api/images/1.png&quot;&gt;https://steve.com/api/images/1.png&lt;/a&gt;” could host pretty much anything. “1.png” could be an image of a cool Pinata one week, and then whoever is in control of the server could simply &lt;a href=&quot;https://medium.com/pinata/who-is-responsible-for-nft-data-99fb4e8147e4&quot;&gt;replace the image with a picture of PePe the Frog with the same name “1.png.”&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Kinda goes against the point of NFTs and having actual ownership of the content; if the content can change, what’s the point of having the reference on chain?&lt;/p&gt;
&lt;h2&gt;The Birth of IPFS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://ipfs.io&quot;&gt;IPFS&lt;/a&gt; (the Interplanetary File System) solved a lot of the problems that centralized storage had with NFTs, and still does to this day. Pinata started not too long after in 2018 at EthBerlin, creating an IPFS service that was easy to use and accessible to the average person. With that said, we’ve certainly seen it all over the years. Pinata has also witnessed the birth of many new blockchains such as Solana, Cardano, or Aptos. One of our favorite parts of IPFS is that it’s blockchain agnostic, meaning it will work with any blockchain! It’s been great to see creators build NFTs and amazing projects on these new blockchains using Pinata. With the great, we’ve also seen plenty of examples of creators make the same mistakes made by Ethereum back in the early days, using centralized storage methods and witnessing rugs right before our eyes.&lt;/p&gt;
&lt;p&gt;In this post we’ll dive into IPFS, why it’s the most ideal solution, and best practices when implementing it across NFT projects!&lt;/p&gt;
&lt;h2&gt;Why should Layer 1 Blockchains use IPFS?&lt;/h2&gt;
&lt;p&gt;Sometimes covering IPFS can be daunting as there are lots of moving pieces, but after you break down some basic concepts, it can be much easier to grasp. If you are looking for a non-technical introduction we would highly recommend our blog post on Bueno.art which you can find &lt;a href=&quot;https://www.bueno.art/blog/pinata-ipfs-guide&quot;&gt;here!&lt;/a&gt; With that said, let’s look into some reasons why IPFS should be considered for off chain storage for all blockchains.&lt;/p&gt;
&lt;h2&gt;Immutable and Verifiable&lt;/h2&gt;
&lt;p&gt;Right off the bat IPFS solves one of the biggest problems for off chain storage by introducing immutability. This means when you share a file with IPFS, the content cannot change. If you share an image, that image will stay the same; it cannot be altered in any way. The way IPFS achieves this is with the “CID” or “Content Identifier.” When you share a file on IPFS, it goes through a cryptographic process that spits out the CID, which can look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;QmeUmuQaTpoqk51uEYgrSjZvCqvtPw2ARdWnZEMb2ky25N&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each piece of that CID, every letter and number, is determined by the contents of the file. So if we shared the same file but perhaps we changed just one pixel, it would spit out a different file. Recall our example from earlier about someone swapping out a picture of Pinnie for a Pepe the frog, its just not possible in IPFS. They are two different files and will result in two different CIDs based on their content.&lt;/p&gt;
&lt;p&gt;This is incredibly powerful if you stop and think about all the ramifications beyond just images. Think of all the things we want to accomplish with smart contracts and NFTs: real estate, health documents, it just goes on. These are documents that depend on data being immutable and verifiable, and IPFS provides this. With the CID, the owner of an NFT can rest assured that they can verify content as the original when tied to a smart contract. Even in the case of smart contracts that have functions to update the token URI, that change is on chain and the user has visibility and know what they’re getting into.&lt;/p&gt;
&lt;h2&gt;Portability and Ownership&lt;/h2&gt;
&lt;p&gt;One of my favorite pieces of IPFS is the ability to truly take ownership of your content. Due to content on IPFS being immutable, and the CID serves as the unchanging reference to a file, it can be used to address the content too. With the CID, files can be shared from one owner to another. Not only can it be shared or viewed, the content can move from one owner to another.&lt;/p&gt;
&lt;p&gt;For example, if I own an NFT that has metadata (the name of the nft, description, image link, etc.) and an image hosted on IPFS, I can get the CIDs for both and pin it to my Pinata account. When a file is pinned on IPFS, it keeps it from falling off the network during garbage collection run on individual IPFS nodes. So in our scenario, if the original creator of the NFT ever stopped pinning the content on his end, the NFT would live on because I’m taking responsibility for the content and keeping it alive.&lt;/p&gt;
&lt;p&gt;We call this portability and ownership, as the content can freely be persisted by any individual or community. It makes data ownership and sharing actually possible, where in the past the data was truly owned by a larger cloud provider. This IPFS ability also helps save projects from rugs. I can’t count the number of communities I’ve helped over the past year recover their project due to someone abandoning it. Thanks to IPFS all they have to do is get the CIDs, which is usually just two, and pin by CID through their Pinata account. Just like that, the project continues to live!&lt;/p&gt;
&lt;p&gt;Our CEO Kyle Tut wrote a great piece on how IPFS portability changes the responsibility of hosting content from the creator to the owner, and how communities can help preserve data very similar to how art is preserved. You can read that &lt;a href=&quot;https://medium.com/pinata/who-is-responsible-for-nft-data-99fb4e8147e4&quot;&gt;here&lt;/a&gt; if you’re interested!&lt;/p&gt;
&lt;h2&gt;Speed and Efficiency&lt;/h2&gt;
&lt;p&gt;Believe it or not, IPFS does have the ability to be fast. If you are accessing IPFS through a local IPFS node, your experience will likely not be the best. However with the right tools, IPFS can be ideal for delivering content at blazing speeds. Since the content does not change and neither does the CID, the reference to the file, this makes an excellent use case for caching content.&lt;/p&gt;
&lt;p&gt;Pinata released &lt;a href=&quot;https://www.pinata.cloud/dedicated-gateways&quot;&gt;Dedicated Gateways&lt;/a&gt; back in 2021 that use a global CDN with 200 worldwide locations, so when content is loaded through the gateway, that content is cached. This makes every other request after that incredibly fast.&lt;/p&gt;
&lt;p&gt;In addition to speed, IPFS is also efficient with data that is no longer wanted or used. In order for content to stay on IPFS, it has to be pinned by at least one IPFS node. If it is no longer pinned on any nodes, then it will slowly fall off the network as IPFS nodes run garbage collection. Some people might see this as a downside, but in reality this gives us a much more practical way of handling data and reducing data waste. It gives users the option of data persistence, rather than being forced into a model of permanence.&lt;/p&gt;
&lt;h2&gt;Best Practices&lt;/h2&gt;
&lt;p&gt;Now that you have an itch to try out IPFS and see for yourself, you might want to consider some best practices for how you want to reference IPFS content in NFT projects and other use cases.&lt;/p&gt;
&lt;h2&gt;Protocol URIs&lt;/h2&gt;
&lt;p&gt;One of the oldest and best ways to make sure IPFS data is future proof and easy to preserve is to use the protocol URI, especially in a smart contract for NFTs. The protocol URI starts with “ipfs://”, and a full url might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;ipfs://QmeUmuQaTpoqk51uEYgrSjZvCqvtPw2ARdWnZEMb2ky25N&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the time of this article, you may not be able to simply copy and paste this URL into your browser (unless you are using a browser like Brave or have a browser extension). Why? Because these normally require you to run an IPFS node to access them. That might change overtime, but right now this is the easiest way for marketplaces and wallets to locate the CID and run it through their gateway of choice.&lt;/p&gt;
&lt;p&gt;This is a common practice on Ethereum where IPFS makes sense (BAYC is an example), however it’s not as popular on Layer 1 blockchains that expect a working HTTP url. We hope that as IPFS grows on those Layer 1 chains that fact will change, as people see the value in a data persistence model.&lt;/p&gt;
&lt;h2&gt;Public Gateways&lt;/h2&gt;
&lt;p&gt;Another way to access IPFS content is through a public gateway like this one:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://gateway.pinata.cloud/ipfs/QmeUmuQaTpoqk51uEYgrSjZvCqvtPw2ARdWnZEMb2ky25N&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gateways are simply bridges between the IPFS protocol and the HTTP protocol that works on everyone’s computers. With them we can fetch content from IPFS without a local node running, making it really easy for everyone!&lt;/p&gt;
&lt;p&gt;These are best used in testing environments or low traffic scenarios as public gateways are usually rate limited and under heavy use due to their open nature. This can also make them a bit slower.&lt;/p&gt;
&lt;h2&gt;Dedicated Gateways&lt;/h2&gt;
&lt;p&gt;If you want to take full advantage of IPFS, a Dedicated Gateway or Private IPFS Gateway is the ideal way to go! These have extra features built in to make streaming content faster and more reliable. An example of one might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedsimkins.mypinata.cloud/ipfs/QmeUmuQaTpoqk51uEYgrSjZvCqvtPw2ARdWnZEMb2ky25N&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not only do &lt;a href=&quot;https://pinaa.cloud/dedicated-gateways&quot;&gt;Pinata’s Dedicated Gateways&lt;/a&gt; include that global CDN we talked about earlier, but they can also do video streaming, and image optimizations. They’re fast enough that you can host entire applications on them to create App / Executable NFTs on them. For personal use they can be a great way to share content or display your NFT collection on your own personal website, and in the hands of a platform like OpenSea, they can be used to display tens of thousands of NFT collections with speed and reliability.&lt;/p&gt;
&lt;h2&gt;The Future&lt;/h2&gt;
&lt;p&gt;One of the best parts of my job is interacting with all the different blockchains out there and helping them use IPFS through Pinata. I also love helping communities across these chains understand the power of IPFS and how it can really save a rugged project in this space whether the founder knew it or not. The worst part is easily when there’s nothing I can do because the data sits on a centralized server that the community will never have access to.&lt;/p&gt;
&lt;p&gt;The future of the web that I want to help build is one where the responsibility of data lies with the user and the communities that find value in it. Whether it’s a moving essay, a photo of a volcano, or some picture of a goblin, IPFS is here to make it possible for data to persist beyond its original source and into the future of web3. Let’s build something great together.&lt;/p&gt;</content:encoded></item><item><title>Concerning Omarchy and Distro Philosophy</title><link>https://stevedylan.dev/posts/concerning-omarchy-and-distro-philosophy/</link><guid isPermaLink="true">https://stevedylan.dev/posts/concerning-omarchy-and-distro-philosophy/</guid><description>Some thoughts on how distros should be approached and where people should go</description><pubDate>Sun, 09 Nov 2025 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/omarchy-post-cover.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There has been quite a bit of &lt;a href=&quot;https://x.com/dhh/status/1986710414504112336&quot;&gt;controversy&lt;/a&gt; surrounding DHH’s Omarchy. If you haven’t heard of it, &lt;a href=&quot;https://omarchy.org/&quot;&gt;Omarchy&lt;/a&gt; is a pre-configured Arch Linux setup/distro using Hyprland. It’s sleek, beautiful, and comes with all the bells and whistles. The big driving factor for building this is DHH himself moved from MacOS to Linux and spent a lot of time configuring his setup, and thought it would be useful for others who want to make the switch. Personally I think it’s an admirable goal to build something that actually helps people move towards tech they otherwise would avoid. Linux is incredibly useful and powerful, especially for programmers. Omarchy has done so well that there is a massive community behind it and DHH’s company now uses it as the default for employees.&lt;/p&gt;
&lt;p&gt;Naturally as a serial tinkerer, I had to try it. I actually went down the Arch + Hyprland path a &lt;a href=&quot;https://github.com/stevedylandev/linux-dotfiles&quot;&gt;couple of years ago&lt;/a&gt; on a Thinkpad T480 (I know, pumpkin spice latte levels of basic). It was a great learning experience and I enjoyed all the ricing. Omarchy gave me the excuse to buy a BeeLink SER8 as a home server that I’ve wanted for a while now. I ran the install script (before there was an image) and quickly started playing around with Omarchy. Overall it was pretty solid. I think at the time I used the install that left out a lot of the gui apps that I didn’t want. After a while, I started finding pieces I wanted to customize, and that’s where things got a bit hairy. Even today the process of trying to customize &lt;a href=&quot;https://youtu.be/d23jFJmcaMI&quot;&gt;doesn’t seem that simple&lt;/a&gt;, and that’s where I want to dig in particularly.&lt;/p&gt;
&lt;p&gt;Distros are built to give you a starting point. Arch on its own is a Linux distro. While it does take a lot more configuration, it still puts some of the most crucial pieces together for you. Another distro like Manjaro uses Arch but adds a desktop environment and all the basic setup people expect in a desktop computer experience. Depending on how much you want to customize your experience will greatly influence what distro you pick; the more you want to customize, the harder it will be to use an opinionated distro.&lt;/p&gt;
&lt;p&gt;Neovim is very similar. You can either start a config from scratch or you can use a distro like LazyVim. It’s generally good to start with LazyVim and see what it’s like, however it’s also very opinionated. Over time the chances are high that you will find your own preferences and want to configure it differently. When that time comes, you’ll find customizing a Neovim distro is a horrible experience. It also generally ends up in a bloated complicated mess. Once you take the time to truly understand how to configure it, the manual from scratch approach is a breath of fresh air. It’s completely tailored to you and your needs, not someone else’s.&lt;/p&gt;
&lt;p&gt;For some people distros are the end game; you use it, you’re happy. For others, the distro is just the gateway to a wholly different setup. It really does depend on the context, ie Arch Vanilla vs Arch Manjaro, but the whole reason so many people use Linux is to customize it. How far that customization goes depends on the person, but I generally find there is a balance of what is easier to customize and what is not. In my personal experience, Omarchy was not easy to customize. I ended up doing a fresh installation of Arch and slowly added piece by piece as the need arose, including a window manager. The result is an incredibly lean configuration that fits my personal needs, and that is what is most important to me.&lt;/p&gt;
&lt;p&gt;By all means if you enjoy Omarchy, use it! It’s a damn cool distro to play with. However, if something doesn’t feel right, or you want to change more than seems possible, consider building your setup from scratch. It will be bumpy. You will get stuck. However the knowledge you gain and the tailored experience will be well worth the journey. Call distros for what they are, and don’t treat them as the end-all-be-all of desktop experiences.&lt;/p&gt;</content:encoded></item><item><title>How Gemini Gives me Hope for a Future Internet</title><link>https://stevedylan.dev/posts/how-gemini-gives-me-hope/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-gemini-gives-me-hope/</guid><description>A journey down a deep rabbit hole thanks to cassette tapes</description><pubDate>Thu, 18 Dec 2025 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/gemini-post.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Last weekend I was building a mini RSS reader (you know how I do), and in the process I was going through some of the blog posts in my feed. One of them stood out:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.pabloecortez.com/lost-media-konpeito-tapes/&quot;&gt;Lost Media: Konpeito Tapes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This grabbed me particularly because I’ve been getting into cassette tapes as a hobby, both collecting and creating them. If you read the post you’ll see that the tapes were originally released on something called &lt;a href=&quot;https://geminiprotocol.net&quot;&gt;Gemini&lt;/a&gt;. I didn’t know much about it but I did know I wanted to download those tapes! After a bit of searching I found a Gemini client and was able to download the zip files which had everything I needed to make replicas of the original tapes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/konpeito-tapes.jpeg&quot; alt=&quot;konpeito tapes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The tapes are fantastic, but this whole event peaked my curiosity about Gemini. The more I learned, the more I liked. It’s relatively new, only five or six years old, yet seems to have a small yet steady userbase. I found a better client to use in the terminal and I started exploring some more. Soon I found some gemlog (a micro blog on Gemini) aggregators which let me find some interesting capsules (a website on Gemini). I found one in particular that was really well done: loads of posts, cool links to other pages, and even a photo log! I had to navigate to each photo, press a key to open it, and then choose whether to download it or view it in my default app for viewing photos (Apple Preview in my case).&lt;/p&gt;
&lt;p&gt;There was something magical about viewing bits and pieces of this person and their life through text on a screen and images that didn’t load directly on the page. Who else knew this capsule existed? How many more people know Gemini exists? A small web floating on the internet with a vibrant set of people just wanting a peaceful existance. That is one of the primary reasons Gemini was created. The founders wanted a text based internet protocol that would let you link out to other pages, similar to HTML, but without CSS and JavaScript. The web had become too noisy and slowly invaded privacy down to the favicon. There were protocols like Gopher, but they had problems that went unsolved since is inception in the early 1990s. A new protocol was needed, and it became Gemini.&lt;/p&gt;
&lt;p&gt;While I have become a new fan of Gemini, and will talk more about that soon, it’s not the reason of this post. To be frank I don’t think that Gemini is a replacement for the web or that it answers every single problem. I don’t see myself ditching HTML altogether for something like Gemini. It’s not Gemini itself the reason that I write this post, but rather the hope it brings. Gemini exists because a few people said enough is enough with the current state of the internet and did something about it. There were enough people who believed in the same idea and principles that now there is a decent community living on this network. People are sharing their lives, recipes, resources, you name it. It’s not full of people who only talk about a niche topic or just talk about Gemini itself. They just use the protocol the way they envisioned the internet to be.&lt;/p&gt;
&lt;p&gt;That, is what brings me hope. We don’t have to take the abuse of our current internet sitting down. We don’t need a VC funded company to build something that “saves us” by moving us to just another mental prison. We, the people, have the power to buid whatever we want. We can control our experience the internet, not the other way around. We can still connect with other people without social media, without noise, and to much surprise, we can do it without HTML.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/jurassic-park-life-finds-a-way-gif.gif&quot; alt=&quot;life finds a way&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I hope your main takeaway isn’t some kind of lecture on how you should use the internet and what it should look like. You can do whatever you want. However, if you’re like me, and you feel tired, worn out by the current state of the web, and you want to see a change: it’s already happening. That change starts with you, deciding to do something different, no matter what anyone else thinks.&lt;/p&gt;
&lt;p&gt;I know this is a bit weird to make this post on HTML while boasting about another protocol, but I decided it was worth it to communicate the idea. If you’re interested in Gemini and want to learn more or even get started, just follow these two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;amfora&lt;/code&gt; with &lt;code&gt;brew install amfora&lt;/code&gt; or one of the many other &lt;a href=&quot;https://github.com/makew0rld/amfora?tab=readme-ov-file#installation&quot;&gt;install methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;In the terminal run &lt;code&gt;amfora gem.stevedylan.dev/gemini-quickstart&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I’ll see you in space 🫡&lt;/p&gt;
&lt;p&gt;p.s. if you thought this post was about some coding CLI, sorry :)&lt;/p&gt;</content:encoded></item><item><title>How To Create a Weekly Photo Zine</title><link>https://stevedylan.dev/posts/how-to-create-a-weekly-photo-zine/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-to-create-a-weekly-photo-zine/</guid><description>Learn how to use ChatGPT and Pinata to make a custom web photo zine with zero developer experience</description><pubDate>Thu, 08 Jun 2023 04:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://medium.com/pinata/how-to-create-a-weekly-photo-zine-9f592e7a5ae2&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/medium.CI909Xjl_oL8pb.webp&quot; alt=&quot;Link to Medium&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Medium &lt;/a&gt;
&lt;p&gt;It’s no secret that AI has revolutionized the tech space, and we’ve already seen some incredible things made with it. ChatGPT was one that immediately proved valuable to everyday people, being capable of answering any kind of question, have an ongoing conversation, and do things you never could before. Beyond that, AI is quickly becoming a tool to democratize the creation of web content.&lt;/p&gt;
&lt;p&gt;Typically, if you want a custom web experience you would either need to hire a professional agency, perhaps get half way with a website builder, or learn to code yourself. But, that may not be the case soon. With AI the average person can ask the right questions and create elementary web content for their brand. In this guide, I’ll show you how to create a simple photo zine using ChatGPT and Pinata and how to update it on a weekly basis. Or, watch the video tutorial above.&lt;/p&gt;
&lt;h2&gt;Pinata&lt;/h2&gt;
&lt;p&gt;First, you’ll want to sign up for an account with Pinata. I would recommend choosing the Picnic plan for this project as we will utilize its speed and flexibility for hosting our content. Once you have an account, you’ll want to start uploading all your images to be used for the zine. In this demo I’ve selected some of my favorite images from a trip I took a few years ago.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Once you have your content uploaded, you can click on the preview button to see your photo. Copy the URL for that image and paste it somewhere for later and repeat the process for however much content you want to put in your zine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;ChatGPT&lt;/h2&gt;
&lt;p&gt;After you have done this it’s time to visit &lt;a href=&quot;https://chat.openai.com&quot;&gt;ChatGPT&lt;/a&gt;. I would recommend starting with a prompt that tells ChatGPT who it is and what it will help you with, and ask if it has any questions. This will help set you up to make sure you have all the ground covered. Here is an example of what I said to start:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;You are a mobile web developer. You have complete understanding of being able to prompt&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;ChatGPT. You will guide me through building a webpage. What questions do you have?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will respond with several questions, so simply provide the answers like you see below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Below my last answer I went ahead and gave ChatGPT the content of text and image links, and just like that it generated the html content to start with. If it doesn’t finish generating all the code at once, you can click “continue generating” and it will keep going where it left off. Also be sure it includes all your content, make sure it knows you have zero programming knowledge and it needs to write everything on its own. Once it does this there should be a copy button at the top of the code box, so go ahead and click that:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;Replit&lt;/h2&gt;
&lt;p&gt;Now to actually paste our code and see what it does, we will be using an online code editor called &lt;a href=&quot;https://replit.com&quot;&gt;Replit&lt;/a&gt;. Just sign up for a free account, and in the top right corner you will want to click “Create Repl.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;After that you will want to select the HTML, CSS, and Javascript template and give your project a name.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Once you’re in your repl project, delete out the starter HTML and paste in your own code from ChatGPT, then hit CMD/CTR + S to save it. After that you should see on the right side your project:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;You can also change the size of your webview by clicking and dragging the pane to shrink it more towards a mobile view if that’s what you’re after.&lt;/p&gt;
&lt;p&gt;Now your project will likely not be perfect right out of the gate, and you will want to make some changes. Since ChatGPT keeps that conversation history, you can go back and forth with it for the changes you want.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;The key is to make sure you are precise in what changes you want, and that ChatGPT gives you the updated code afterwards. Once you’ve gotten your zine looking the way you want it, click on the little dropdown arrow next to the index.html file and click “Download.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;After you have download the file, go ahead and upload it to Pinata just like you did for your image files. Then just click on that file to see your zine. You can view mine with this link:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stevedylanphoto.mypinata.cloud/ipfs/QmWAUQfKhJ19kZqcJLP6nPbHFRToiaFquGJ71gJEA4cRVT&quot;&gt;A Venture to the North East&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/medium/v2/resize:fit:1124/1*3XuUbuzy-CZAB-78TR-tTQ.gif&quot; alt=&quot;Gif of zine&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Gateway tip&lt;/h2&gt;
&lt;p&gt;Now here’s the bonus info: if you wanted to make this a weekly update, Pinata provides a pretty neat way to do that. First you would of course need to give ChatGPT your code and the update content and it could swap it out for you (or you could even take your hand at changing it in Replit!), and once you have uploaded the new index.html to Pinata, you can click on the “more” button next to the file and select “Set gateway as a root”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;What this will do is redirect my base url “&lt;a href=&quot;https://stevedylanphoto.mypinata.cloud%E2%80%9D&quot;&gt;https://stevedylanphoto.mypinata.cloud”&lt;/a&gt; to the new index.html file that was uploaded. I could keep updating it with each new file every week with a new photo zine, as well as improve it over time. Of course if you don’t like the “mypinata.cloud” domain for your zine, you could very easily use your own domain like “stevephotozine.com” and it would work exactly the same. My personal photography website is stevedylanphoto.com, so I just used “zine.stevedylanphoto.com” as a dedicated domain for this zine. Check it out:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://zine.stevedylanphoto.com&quot;&gt;zine.stevedylanphoto.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We’re excited to see what you’ll make using these tools.&lt;/p&gt;
&lt;h2&gt;Pinata Links:&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pinata.cloud/pricing?utm_source=medium&amp;amp;utm_medium=referral&amp;amp;utm_campaign=photo_zine_blog&quot;&gt;Explore our plans and start building today!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/pinatacloud&quot;&gt;Twitter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/company/pinatacloud&quot;&gt;Instagram&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/c/Pinatacloud&quot;&gt;YouTube&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.pinata.cloud/&quot;&gt;Website&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://discord.gg/pinata&quot;&gt;Discord&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>How to Migrate from Neovim to VSCode</title><link>https://stevedylan.dev/posts/how-to-migrate-from-neovim-to-vscode/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-to-migrate-from-neovim-to-vscode/</guid><description>Some tips on how to eaily move to VSCode as a Neovim user (sorry Primeagen)</description><pubDate>Thu, 06 Jul 2023 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Okay, okay. I know. Before you come at me with virtual torches and pitchforks, hear this first: I love Neovim. I’ve been using it non-stop for the past two years, and honestly, I struggled to use any other kind of editor. I loved the speed, the tinkering, the ricing, etc. It’s an incredible piece of open-source software and has a special place in my heart.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The truth: use whatever tool fits you best.
The harder truth: be willing to try the other tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Several developers have switched from Neovim to VSCode, notably &lt;a href=&quot;https://www.nexxel.dev/blog/neovim-to-vscode&quot;&gt;Nexxel&lt;/a&gt; and &lt;a href=&quot;https://youtu.be/PLxpyUYvC_o&quot;&gt;Melkey&lt;/a&gt;. Both of their pieces of content resonated with me on several levels, so I thought, “Why not try it for a week?” The worst case scenario was that I would hate it and move back to Neovim. However, it turned out to be harder than I expected. The first few days were rough; nothing was smooth, everything felt clunky, it was just awful. Of course, I already had the Vim keybindings extension installed, which helped, but something was still missing. After digging into it, I realized there were two things throwing me off:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The clunky UI&lt;/li&gt;
&lt;li&gt;Vim mode specific keybindings&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let me go into more detail about how I resolved these problems, and hopefully it will help anyone who finds themselves in the same situation.&lt;/p&gt;
&lt;h2&gt;UI&lt;/h2&gt;
&lt;p&gt;In Neovim, everything is stripped down to the core editor. By default, there are no extra plugins, no file tree, not even syntax highlighting. Of course, this is by design, and, like Linux, you have to make a lot of decisions as to what you want your editor to look like and how you want it to behave. In my opinion this is a good thing, and it’s better to start out with less and slowly build more on top. Having just the minimal editor experience is a key UI component for the regular Neovim user.&lt;/p&gt;
&lt;p&gt;VSCode is the exact opposite. There’s loads of extra stuff all around the editor that’s pure distraction. I mean, be real, who is using the minimap thing? What about that extra bar on the right with a bunch of icons that you’ll never use? It’s so aggressive that I wanted to get out as soon as I opened it. However, there are solutions to this! You can simply go to the View menu, then Appearance, and from there toggle all those nasty bits off. Now you can get VSCode looking more like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1688690521/Screenshot-Code-07-06-2023-20-19_2x_gz0arj.png&quot; alt=&quot;gooey&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Keybindings&lt;/h2&gt;
&lt;p&gt;Altering the UI a bit is easy and takes a few minutes, but the keybindings were a whole other mess. Throughout my years of using Neovim, I had collected so many custom keymaps for different things that were unique to Neovim. I realized pretty quickly that I had no idea how I could use them again because they were bound to Vim modes. How do I use &lt;code&gt;Shift + H&lt;/code&gt; to switch to the previous buffer or &lt;code&gt;Shift + L&lt;/code&gt; to go to the next? How can I map that in VSCode? Thankfully, VSCodeVim has that covered, although it’s not the most intuitive.&lt;/p&gt;
&lt;p&gt;If you visit the &lt;a href=&quot;https://code.visualstudio.com/docs/editor/vim&quot;&gt;VSCodeVim&lt;/a&gt; docs, you can see they have some example configurations to remap keybindings for specific modes. For instance, I was able to add my lovely buffer switching remapping in normal mode with ease.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;vim.normalModeKeyBindings&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // switch buffers with ctrl and left and right&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;S-h&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;commands&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;:bprevious&quot;&lt;/span&gt;&lt;span&gt;]},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;S-l&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;commands&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;:bnext&quot;&lt;/span&gt;&lt;span&gt;]},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;“Before” is simply the keys you’re pressing and either “after” or “commands” (if you want to do &lt;code&gt;:enter commands&lt;/code&gt;) will be the output of that binding. What I had a lot of trouble doing was going beyond the simpler stuff. Another one I loved using was &lt;code&gt;Ctrl + H, J, K, L&lt;/code&gt; to switch between panes in the editor. Because VSCode isn’t really using the same window API that Neovim has, those go out the window. How are you supposed to do those?&lt;/p&gt;
&lt;p&gt;Here’s the sauce: discovering VSCode’s API. Something I had never done before is going to the built-in keybindings menu for VSCode and looking at what was in there. There’s so much there and it’s a bit overwhelming, but once you figure out how to find the command you’re looking for, it makes just about anything possible. For my pane switching keymap, I wanted to find which command was changing the focus. I searched “focus left” in the VSCode keybindings and sure enough, there it was: &lt;code&gt;workbench.action.focusLeftGroup&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1688690521/Screenshot-Code-07-06-2023-20-25_2x_kl790i.png&quot; alt=&quot;Keybindings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now back in our VSCodeVim config, we can add that method to the binding like so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&quot;vim.normalModeKeyBindings&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	  //... other bindings&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // better pane navigation&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;C-h&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;commands&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workbench.action.focusLeftGroup&quot;&lt;/span&gt;&lt;span&gt;]},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;C-j&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;commands&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workbench.action.focusBelowGroup&quot;&lt;/span&gt;&lt;span&gt;]},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;C-k&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;commands&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workbench.action.focusAboveGroup&quot;&lt;/span&gt;&lt;span&gt;]},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    { &lt;/span&gt;&lt;span&gt;&quot;before&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;C-l&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;commands&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workbench.action.focusRightGroup&quot;&lt;/span&gt;&lt;span&gt;]},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works like a charm. If you’re doing this yourself, something I found useful is right-clicking on the command in the VSCode keybindings menu to see the method. Another example where this came in handy was using the “Find Files” command. By default, it’s &lt;code&gt;Cmd + P&lt;/code&gt; on Mac, but I was used to something like &lt;code&gt;&amp;lt;leader&amp;gt; + F&lt;/code&gt;. When I searched “Open File…” in the keybindings, it didn’t show the method like the others did. However, after right-clicking, you can click “Copy Command ID” to grab it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/cloudinary/v1688690521/Screenshot-Code-07-06-2023-20-53_zxnjxo.gif&quot; alt=&quot;command id&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Configuring VSCode is not the most pleasant experience, but on the plus side you can save your config file and move it wherever you want, and you don’t have to mess with configuring as many plugins with Neovim. I’m still on the fence as to whether I’ll keep trying to use VSCode or go back to Neovim, but hopefully these tips help anyone out there trying to make it work for their own personal reasons.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/stevedylandev/b6bb1eccd83ea438031158c5961fd3f8&quot;&gt;Check out my VSCode config here&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>How to Mint an NFT on Sui using Pinata and the Sui JS SDK</title><link>https://stevedylan.dev/posts/how-to-mint-on-sui/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-to-mint-on-sui/</guid><description>Learn how to use the Sui SDK and Pinata to mint an NFT</description><pubDate>Mon, 27 Feb 2023 05:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://medium.com/pinata/how-to-mint-an-nft-on-sui-using-pinata-and-the-sui-js-sdk-4386655e403&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/medium.CI909Xjl_oL8pb.webp&quot; alt=&quot;Link to Medium&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Medium &lt;/a&gt;
&lt;p&gt;One of my favorite pastimes is playing around with new blockchains and seeing what they’re like, what they offer, and how easy they are to build on. Recently I stumbled upon Sui and some of its unique features, such as dynamic metadata and goals to help onboard everyday people. After playing with it myself I was impressed with its speed and smoothness, which is saying something as it’s still in development!&lt;/p&gt;
&lt;p&gt;The only thing I found lacking was some basic explanations of how to mint NFTs on Sui and some of the things you might have to tackle, so I thought I would share that experience and demonstrate this!&lt;/p&gt;
&lt;p&gt;To get started, you should only need the basics such as NodeJS and a text editor like VSCode, as well as some fundamental knowledge about Javascript.&lt;/p&gt;
&lt;h2&gt;Setting up your NFT with Pinata&lt;/h2&gt;
&lt;p&gt;While Sui features dynamic metadata, you will still want to use a service to store images, video, or any other kind of content you want to turn into an NFT! Pinata is ideal as it uses IPFS which prevents tampering with content which you can read more about here.&lt;/p&gt;
&lt;p&gt;Getting started with Pinata is easy! Just visit the signup page here and start out with a free account. Now all you have to do is upload the image you want to use. Do that by visiting the main files page and clicking “Upload” and “Select File.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;After that just follow the steps of selecting your file, give it a name, then upload! Once done it should show up in your files page as seen below, and we will want to copy the CID for later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;Code Setup with the Sui JS SDK&lt;/h2&gt;
&lt;p&gt;First let’s make a new directory where the project will live using the following terminal command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span&gt; sui-nft&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; sui-nft&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s install the Sui Javascript SDK:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; -y&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; @mysten/sui.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One small change we need to make to the package.json file is adding the module type, so make sure your package.json looks like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;sui-nft&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;module&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;description&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;main&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;index.js&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;scripts&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;test&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;echo &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Error: no test specified&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; exit 1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;keywords&quot;&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;author&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;license&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ISC&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&quot;dependencies&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;^0.26.1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we will want to make a file to run our code which we’ll call mint-nft.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;touch&lt;/span&gt;&lt;span&gt; mint-nft.js&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can get into good stuff — writing the code to mint our NFT!&lt;/p&gt;
&lt;h2&gt;Minting the NFT&lt;/h2&gt;
&lt;p&gt;Go ahead and open up your mint-nft.js file in your text editor of choice, and the first thing we’re gonna do is import the following methods at the top of the page.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All we really need are four things, and you’ll see how they play a part as we start building. Next thing we need to do is create a wallet and get the public address for that wallet, which we can do like so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0x&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; keypair&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getPublicKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toSuiAddress&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Accessing the keypair object makes it super simple to either get the public key or private key, and then turn it into usable data. The Sui address from the public key doesn’t include the leading “0x” so I’m adding that manually here. If you go into the terminal now and run “node mint-nft.js” then you should see an address like this!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;0x8f7671eedff42d6dde8c365a6b641bb9769ea02e&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have a wallet address, it’s time to connect to the Sui network and get some test Sui coin! First we’ll declare a new provider and use the JsonRpcProvider, and pass in the Network “DEVNET.”&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//Create keypair&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0x&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; keypair&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getPublicKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toSuiAddress&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//Create network connection&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DEVNET&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we’ll use the provider to request some test Sui from the Devnet faucet and use our new address as the receiver like so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//Create keypair&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0x&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; keypair&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getPublicKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toSuiAddress&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//Create network connection&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DEVNET&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Get Sui from faucet&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;fund&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;requestSuiFromFaucet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fund&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s run the node mint-nft.js command now and see what we get!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;0xf64640227ff94ba762252c15f2adbcedb6d3aaab&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  transferred_gas_objects: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      amount: 10000000,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      id: &apos;0x39c25c3885c2cccea957c26219de9c7e58a33a21&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transfer_tx_digest: &apos;4ETS2rGNzRYZ95SsLrUsQf8ckfZWQSSqTEpGi32RqKbk&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      amount: 10000000,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      id: &apos;0x3bdad5c729495d9d152cfd03b0e44e8549972d53&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transfer_tx_digest: &apos;4ETS2rGNzRYZ95SsLrUsQf8ckfZWQSSqTEpGi32RqKbk&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      amount: 10000000,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      id: &apos;0x4a748f4e928b974dd46913e2cc069a21fecaad86&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transfer_tx_digest: &apos;4ETS2rGNzRYZ95SsLrUsQf8ckfZWQSSqTEpGi32RqKbk&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      amount: 10000000,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      id: &apos;0x66d601ef1811cbdea82d2eb97a0994afdbbc888a&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transfer_tx_digest: &apos;4ETS2rGNzRYZ95SsLrUsQf8ckfZWQSSqTEpGi32RqKbk&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      amount: 10000000,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      id: &apos;0xee8668e7c2fcd60047992f170da075dafd955f48&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      transfer_tx_digest: &apos;4ETS2rGNzRYZ95SsLrUsQf8ckfZWQSSqTEpGi32RqKbk&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  error: null&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nice! Through this we can see the Sui we received which equals out to 0.05 Sui. Something you might be wondering is why we have 5 different items here with different ids, and that’s actually a very important piece of the Sui model that isn’t natural. Every coin in Sui has its own unique object id. These are all of the same type of currency which is the default Sui type, but our five pieces are in their own camp. This can cause a problem when we try to use our Sui to mint an NFT because its gonna try to pull from just one of these objects when ideally we want to pull from all of them like a normal blockchain (huge shoutout to &lt;a href=&quot;https://twitter.com/PaulFidika&quot;&gt;Paul Fidika&lt;/a&gt; for helping me understand this bit!).&lt;/p&gt;
&lt;p&gt;Think of it like this. Let’s say you have a farm with five cow fields, and each field is separated by the type of cow: short haired, long haired, etc. Each field has 10 cows so you technically have 50 cows, but in order to trade for some goats you need 20 cows. You would have to empty two of those cow fields and combine them to trade them. That’s essentially what we’re doing in Sui with coins.&lt;/p&gt;
&lt;p&gt;With that said, in order for us to mint, we need to combine some of our recently acquired Sui drop. We’re gonna do that with the following code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Merge two of the Sui coin objects&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;coin1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fund&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transferred_gas_objects&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;coin2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fund&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transferred_gas_objects&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;mergeTxn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mergeCoin&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	primaryCoin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;coin1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	coinToMerge&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;coin2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	gasBudget&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MergeCoin txn&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mergeTxn&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There’s quite a bit going on here so let’s break it down.&lt;/p&gt;
&lt;p&gt;First we declare coin1 and coin2 from the airdrop we just received by accessing those object ids from our fund result. Then we need to declare our signer! This is what lets us use our private key from our keypair to transfer or mint on the chain, and we do that by declaring a new RawSigner and passing in our previously made keypair and provider to connect. Finally we use the mergeCoin method and pass in our two coins, along with a gas budget.&lt;/p&gt;
&lt;p&gt;Before we run this, there is one small thing we’re missing. If you try to run this code now, you will likely get an error that it could not find the object id for the coin. That’s because its was just freshly created and we’re trying to access an object id that isn’t quite discoverable yet. To fix that we’re gonna make a small little helper function called “wait” like so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Pause function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;wait&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return new &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setTimeout&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			resolve&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}, &lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is really simple and just lets us pass in how many milliseconds we want to wait before continuing our function! Let’s use it after getting our airdrop and passing in 3 seconds. You code should look something like this now.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Generate a new Keypair&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0x&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; keypair&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getPublicKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toSuiAddress&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Create Network Connection and receive airdrop&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DEVNET&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Get Sui from faucet&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;fund&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;requestSuiFromFaucet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fund&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Pause function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;wait&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return new &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setTimeout&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			resolve&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}, &lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;wait&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Merge two of the Sui coin objects&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;coin1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fund&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transferred_gas_objects&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;coin2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fund&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transferred_gas_objects&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;mergeTxn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mergeCoin&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	primaryCoin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;coin1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	coinToMerge&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;coin2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	gasBudget&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MergeCoin txn&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mergeTxn&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run this code you should see the result of the airdrop, a pause before merging the coins, and the successful coin merge! Now that we have all our cows in one heard, we can mint an NFT successfully. Let’s take a look!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Call to Mint NFT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;mintTxn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;executeMoveCall&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	packageObjectId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0x2&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;devnet_nft&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	function&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mint&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	typeArguments&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	arguments&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;gm&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;A nice gm brought to you by Pinata and Sui&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;ipfs://QmZhnkimthxvL32vin2mrQvnhN8ZbWFMvKMxRqHEq7dPz3&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	gasBudget&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mint transaction:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mintTxn&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our minting function is simply accessing a pre-built smart contract called ‘devnet_nft’ and we’re using the ‘mint’ function. All we have to pass into the arguments is the name of the NFT, the description, and then the asset link!&lt;/p&gt;
&lt;p&gt;Before we mint, I’m gonna do one last console log that will let us take a look at the NFT in the browser. To do that , we need to access the results of the NFT mint and get the object id of the NFT, and then just pass that into a Sui explorer link like so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// View NFT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;nftId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; mintTxn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;effects&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;effects&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;created&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;reference&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;objectId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`View NFT: https://explorer.sui.io/object/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;nftId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?network=devnet`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s look at our full code to make sure everything is good, then run it!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@mysten/sui.js&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Generate a new Keypair&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Ed25519Keypair&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0x&quot;&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; keypair&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getPublicKey&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toSuiAddress&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Create Network Connection and receive airdrop&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;JsonRpcProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Network&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DEVNET&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Get Sui from faucet&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;fund&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;requestSuiFromFaucet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;address&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fund&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Pause function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;wait&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return new &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reject&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setTimeout&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			resolve&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}, &lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;wait&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;3000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Merge two of the Sui coin objects&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;coin1&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fund&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transferred_gas_objects&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;coin2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; fund&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transferred_gas_objects&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;RawSigner&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keypair&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;provider&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;mergeTxn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mergeCoin&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	primaryCoin&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;coin1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	coinToMerge&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;coin2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	gasBudget&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;MergeCoin txn&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mergeTxn&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// Call to Mint NFT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;mintTxn&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;signer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;executeMoveCall&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	packageObjectId&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0x2&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	module&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;devnet_nft&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	function&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mint&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	typeArguments&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	arguments&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;gm&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;A nice gm brought to you by Pinata and Sui&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;ipfs://QmZhnkimthxvL32vin2mrQvnhN8ZbWFMvKMxRqHEq7dPz3&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	gasBudget&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mint transaction:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mintTxn&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// View NFT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;nftId&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; mintTxn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;effects&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;effects&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;created&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;reference&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;objectId&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`View NFT: https://explorer.sui.io/object/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;nftId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?network=devnet`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all works as it should you’ll get a link and then you should see your final NFT!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;h2&gt;You did it!! 🎉&lt;/h2&gt;
&lt;p&gt;You successfully minted an NFT on Sui!! Of course this is only the beginning of what’s possible. Maybe a nice next step is transferring the NFT to another wallet, or maybe even trying to build a Sui marketplace 👀&lt;/p&gt;
&lt;p&gt;With whatever you’re trying to do, Pinata is here to help with tools like &lt;a href=&quot;https://docs.pinata.cloud/pinata-api?utm_source=medium&amp;amp;utm_medium=referral&quot;&gt;our API&lt;/a&gt;, &lt;a href=&quot;https://www.pinata.cloud/dedicated-gateways?utm_source=medium&amp;amp;utm_medium=referral&quot;&gt;Dedicated Gateways&lt;/a&gt; to quickly stream images or video content for a marketplace, or token gated solutions using Submarine!&lt;/p&gt;
&lt;p&gt;If you have any questions or want to learn more, feel free to join our &lt;a href=&quot;https://discord.gg/pianta&quot;&gt;Discord&lt;/a&gt; and say hi! :)&lt;/p&gt;</content:encoded></item><item><title>How to Offset Your NFT Project Carbon Emissions with Aerial</title><link>https://stevedylan.dev/posts/how-to-offset-nft-emissions/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-to-offset-nft-emissions/</guid><description>Learn how Aerial is helping make NFTs carbon neutral with their emissions API</description><pubDate>Fri, 22 Apr 2022 04:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://medium.com/pinata/how-to-offset-your-nft-project-carbon-emissions-with-aerial-b5b4b95faba0&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/medium.CI909Xjl_oL8pb.webp&quot; alt=&quot;Link to Medium&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Medium &lt;/a&gt;
&lt;p&gt;As a believer in NFTs and Web3, I am always ecstatic to see what can be done with this new technology. I get bullish over new projects, experimental ideas, and cutting edge utility (especially when it’s something we make at Pinata like &lt;a href=&quot;https://submarine.me&quot;&gt;Submarine.me&lt;/a&gt;). However, I don’t look at this Metaverse with rose colored glasses. There are many imperfections in this space, and one of the worst ones is environmental impact.&lt;/p&gt;
&lt;p&gt;While I love dooting around on my computer, I also love the outdoors. I love hiking, seeing mountains, hearing rivers, and smelling wildflowers. I love hearing birds in the morning and geese at night. I love to watch my son experience nature and get excited over a butterfly. So yeah, it kinda kills me to think of how this wonderful industry of crypto can harm this amazing planet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;The energy consumed by proof of work blockchains is staggering. It’s no secret, and if we want to enjoy this planet we need to accept it. I know I feel defensive when someone attacks something I enjoy, but it helps to take a deep breath and ask the big questions: “could I be wrong?” If crypto is putting the planet in danger, what do we do?&lt;/p&gt;
&lt;p&gt;It might sound easy to just throw Web3 out the window and call it a day, but as with most technological advances, the cat is out of the bag. There’s no putting the lid back on Pandora’s box. In my opinion, there is no easy fix to this problem. There will be a need for multiple approaches to help offset our carbon footprint, including but not limited to better blockchain mechanisms and organizations that pursue renewable energy and conservation.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href=&quot;https://aerial.is/&quot;&gt;Aerial&lt;/a&gt; steps in. Aerial is a sustainability platform that helps users track their carbon footprint and make donations to help offset their carbon footprint. They are known for their beautiful &lt;a href=&quot;https://aerial.is/download&quot;&gt;mobile application&lt;/a&gt;, but more recently they have developed an API that can track and estimate the carbon footprint of NFT projects and crypto! Per their website, “Emissions are calculated based on the energy consumption of the &lt;a href=&quot;http://ethereumenergyconsumption.com/&quot;&gt;Ethereum network&lt;/a&gt; and the average &lt;a href=&quot;https://etherscan.io/chart/gasused&quot;&gt;gas used&lt;/a&gt; per transaction.” Since &lt;a href=&quot;https://pinata.cloud/&quot;&gt;Pinata&lt;/a&gt; is the home of NFT media we want to always support initiatives like this to make NFTs more sustainable!&lt;/p&gt;
&lt;p&gt;They have some awesome and simple ways you can integrate their widget to your website that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;iframe&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://www.aerial.is/nft/embed?address=0x3e88721fa41d5e102d54b4a04e550222efdd234d&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;In this post we’ll take their API a step further and build our own custom widget. We can use it in our frontend website that displays more details about the project’s emissions with the goal of making visitors more aware!&lt;/p&gt;
&lt;p&gt;If you want to follow along for this tutorial, you’ll need to have npm installed and be familiar with a frontend framework like React. First thing we’ll do is create a new React app and CD into it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; create-react-app&lt;/span&gt;&lt;span&gt; aerial-component&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; aerial-component&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we’ll delete the CSS files and boilerplate so we are left with this in App.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;App&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s make a new component. Create a new file in the src folder called Aerial.js and fill it with this starter code&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Aerial&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s import the new component to our App.js&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;./Aerial&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;App&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run npm start you should see your basic app with “Aerial” in the top left!&lt;/p&gt;
&lt;p&gt;Now that we have our new component ready to go, we need to start making some API calls and fetching the data. If you need a reference for how to use Aerial’s API you can see their reference &lt;a href=&quot;https://www.notion.so/aerial/Aerial-API-343ebee875784ca18c244a5aae9fa7d3&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s really quite easy, all we have to do to fetch some basic data is use the following format&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;GET https://aerial.is/_nft/&amp;lt;address&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will return data in the following format&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;co2&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;emissions&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; CO&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;gas&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;gas&lt;/span&gt;&lt;span&gt; used&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;transactions&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;number&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; transactions&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;credits&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;credits&lt;/span&gt;&lt;span&gt; required&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; offset&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;cost&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;cost&lt;/span&gt;&lt;span&gt; to&lt;/span&gt;&lt;span&gt; offset&lt;/span&gt;&lt;span&gt; in&lt;/span&gt;&lt;span&gt; USD&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;credits_purchased&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;number&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; credits&lt;/span&gt;&lt;span&gt; already&lt;/span&gt;&lt;span&gt; purchased&amp;gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; &quot;transactions_offset&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&amp;lt;number&lt;/span&gt;&lt;span&gt; of&lt;/span&gt;&lt;span&gt; transactions&lt;/span&gt;&lt;span&gt; already&lt;/span&gt;&lt;span&gt; offset&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So let’s give it a shot! First thing we need to do is install Axios to help us with our API calls. Go back to the terminal and run&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; axios&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now make sure to import it into the top of our Aerial component like so&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;axios&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’re going to make a quick function that will get the data. For our example we’re going to use the NFT contract address 0x2acab3dea77832c09420663b0e1cb386031ba17b.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;getEmissionsData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To run this function we’ll use the useEffect hook to fetch the data as we load the app. To do that we simply need to import it at the top like so&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we need to run the function inside useEffect,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	getEmissionsData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}, []);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what our code will look like with everything in place:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;axios&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;getEmissionsData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		getEmissionsData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;aerial-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Aerial&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we run the app and check the dev console, we can see our data!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Now that we have the data, it’s as simple as displaying it so users on our website can see it!&lt;/p&gt;
&lt;p&gt;To store the data we’ll import the useState hook at the top of our app along with useEffect&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then right above our function to grab the data, we’ll declare our state variable as an empty array where we can push stuff in later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setEmissionsData&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;([]);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all we have to do is edit our function just a little bit to push that data into our state using the “setEmissionsData”! Here is our function now&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;getEmissionsData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setEmissionsData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next thing we’re gonna do is create two components inside our file, one as a loading indicator, and the other as the data we want to display. To do this we’ll just make two more functions like this&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;loading-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Loading&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;emissionsComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Data goes here&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To switch between the two we’ll create a new state called “isLoading” right underneath our previous state. We’ll set the default value to “false” for now&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setEmissionsData&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Back in our getEmissionsData function we need to turn the “loading” on when we start the request, and then off when we’re done.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;getEmissionsData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&quot;https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setEmissionsData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, way down at the bottom where we render the whole app, we’ll add in some conditional rendering to say “display the loading component while loading, then display the data component when not loading.”&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;aerial-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;emissionsComponent&lt;/span&gt;&lt;span&gt;()}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a quick recap this is what our component looks like at the moment&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;axios&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setEmissionsData&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;getEmissionsData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setEmissionsData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;loading-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Loading&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;emissionsComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Data goes here&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		getEmissionsData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;aerial-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;emissionsComponent&lt;/span&gt;&lt;span&gt;()}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok now the good part! Time to parse that data and display it. We’re going to go over a few of the pieces of data and how you may want to display them.&lt;/p&gt;
&lt;p&gt;The first is the CO2 emissions, which the API returns as a raw number in the unit of Kg. The best way to make this number manageable in my opinion is to round up the number, and use some javascript to add the commas for each three digits. So back in our emissionsComponent, I have declared the following variable from our saved state.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up is the gas used for this project, and for this one it returns a whole number so no need to round it up. We’ll just format it to be readable.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;gas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;gas&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aerial of course also provides their unit for donations to offset the carbon emissions they call “credits.” The API can return the total credits need to make the NFT project carbon neutral, how many are purchased so far, and how much it would cost in total to offset the project in USD. To make this data more readable, we want to display how many credits have been purchased, how many are needed to offset, and how much it would cost to completely offset the project. We just need a little math to make that happen!&lt;/p&gt;
&lt;p&gt;For the credits remaining, we just need to subtract the credits already purchased from the total credits needed to offset like so.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;creditsRemaining&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits_purchased&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course we want to display how many have already purchased and that’s pretty simple.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;creditsPurchased&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits_purchased&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the more complicated part is calculating how much the remaining cost is. The API gives us the total cost, but it does not include how much has been spent. So for us to get this number we need to divide the emissions cost with the total credits needed to offset, then multiply that against the total emissions credits minus the credits already purchased. In the end it looks like this!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits_purchased&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lastly, we want the number of total number of transactions already offset.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const transactions = new Intl.NumberFormat().format(emissionsData.transactions)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright that was a bit of work to get sorted, but now we can easily plug this into our emissionsComponent so people can see the data in a more easy to read format. We’re also going to display this in a nice grid with each data cell in a box, so our code inside emissions is going to look like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;header&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Deadfellaz Carbon Offset&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-grid&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt;} Kg&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;CO2 Emissions&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;gas&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Gas Used&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;transactions&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Transactions&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;${&lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Cost to Offset&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;creditsRemaining&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Credits needed to offset&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;creditsPurchased&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Credits Purchased so Far&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;cta-button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://aerial.is/nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			Offset&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the bottom we’ve also added a button where a user can click on it and be directed to Aerials page for that particular NFT project, where they can make purchase credits to help offset that project.&lt;/p&gt;
&lt;p&gt;To make this whole component feel clean as well as themed for the project, we took some colors and styled from DeadFellaz and added it to this page with some CSS; we also added a fun Lottie animation for the loading component instead of just a header that says loading. In the end our code looks like this!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;&quot;./Aerial.css&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;axios&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;Lottie&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;react-lottie&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;./co2.json&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setEmissionsData&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;([]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const [&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setIsLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;getEmissionsData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;axios&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&quot;https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setEmissionsData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			setIsLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		} catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;loading-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;Lottie&lt;/span&gt;&lt;span&gt; options&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{{ &lt;/span&gt;&lt;span&gt;animationData&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt; }} &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt;} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const &lt;/span&gt;&lt;span&gt;emissionsComponent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;gas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;gas&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;creditsRemaining&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits_purchased&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;creditsPurchased&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits_purchased&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;credits_purchased&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const &lt;/span&gt;&lt;span&gt;transactions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NumberFormat&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;emissionsData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transactions&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;header&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;Deadfellaz Carbon Offset&amp;lt;/&lt;/span&gt;&lt;span&gt;h1&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-grid&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;co2&lt;/span&gt;&lt;span&gt;} Kg&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;CO2 Emissions&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;gas&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Gas Used&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;transactions&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Transactions&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;${&lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Cost to Offset&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;creditsRemaining&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Credits needed to offset&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;data-cell&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;creditsPurchased&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;h2&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;Credits Purchased so Far&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;cta-button&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					target&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;_blank&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;noreferrer&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;https://aerial.is/nft/0x2acab3dea77832c09420663b0e1cb386031ba17b&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					Offset&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		getEmissionsData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;aerial-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;{&lt;/span&gt;&lt;span&gt;isLoading&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;() : &lt;/span&gt;&lt;span&gt;emissionsComponent&lt;/span&gt;&lt;span&gt;()}&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;Aerial&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is a quick preview of our final project&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/medium/max/2000/1*4tyAgdhStdoWa-aofwGgBQ.gif&quot; alt=&quot;gif&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can actually view the working component live &lt;a href=&quot;https://aerial-component.vercel.app/&quot;&gt;here&lt;/a&gt;, and you can freely download the repo on &lt;a href=&quot;https://github.com/stevedsimkins/aerial-component&quot;&gt;GitHub&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;If you wanted to take this step further you can request an API key from Aerial to use their offsetting endpoints. These let you not only fetch the data, but also provide a way to make a payment for the credits with Stripe or even Coinbase if you have merchant accounts with both of them! In the end it will always be a little easier to redirect users to Aerial.is to make those credit purchases, but I still love that Aerial has made this an option.&lt;/p&gt;
&lt;p&gt;Aerial’s API is a great way to display emissions for Ethereum projects and help offset them, however if you are still working on your NFT project there are some simple steps you can take to help offset emissions before you launch!&lt;/p&gt;
&lt;p&gt;One of the best things you can do is select a proof of stake blockchain like Solana, Tezos, Flow, or Polygon to create your NFTs on. You can read more on the difference between proof of work and proof of stake blockchains &lt;a href=&quot;https://www.coinbase.com/learn/crypto-basics/what-is-proof-of-work-or-proof-of-stake&quot;&gt;here&lt;/a&gt;, but essentially the amount of energy is significantly lower on proof of stake chains because of how they verify transactions.&lt;/p&gt;
&lt;p&gt;If you do choose Ethereum there are a lot more gas friendly smart contracts being developed such as &lt;a href=&quot;https://www.erc721a.org/&quot;&gt;ERC-721A by Azuki&lt;/a&gt; or &lt;a href=&quot;https://medium.com/donkeverse/introducing-erc1155d-the-most-efficient-non-fungible-token-contract-in-existence-c1d0a62e30f1&quot;&gt;ERC-1155D&lt;/a&gt;. You can also consider changing how many NFTs you mint for a project. 5,000 to 10,000 is a really popular number, but as Solana has proven you don’t need that many to have a successful project. If you’re doing a much smaller 1/1 project you can even consider using &lt;a href=&quot;https://medium.com/rarible-dao/nft-minting-vs-lazy-minting-mining-explained-4330dd57a4c4&quot;&gt;lazy minting&lt;/a&gt;. However, if your Ethereum contract ends up putting out emissions, you can offset them from the start with &lt;a href=&quot;https://aerial.is/nft&quot;&gt;Aerial&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;In conclusion, as NFTs progress and blockchains progress, I believe we will find better and more eco-friendly solutions for energy usage. One of the best thing we can do now is to raise awareness, and it starts with each one of us! Using initiatives like Aerial to bring the issue front and center to your NFT holds is an easy way to not only help educate the public, but also provide an avenue to help offset those emissions. At &lt;a href=&quot;https://pinata.cloud/&quot;&gt;Pinata&lt;/a&gt; we would love to see this more and more as we assist creators with their NFT media.&lt;/p&gt;
&lt;p&gt;It’s easy to assume that NFTs and Web3 aren’t going away anytime soon, but I’m not sure if we can say the same thing about our planet unless we take care of it.&lt;/p&gt;</content:encoded></item><item><title>Alcove: An RSS Reader for the Open Web</title><link>https://stevedylan.dev/posts/introducing-alcove/</link><guid isPermaLink="true">https://stevedylan.dev/posts/introducing-alcove/</guid><description>Pushing forward the consumption of content without the invasion of privacy</description><pubDate>Sun, 23 Nov 2025 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/alcove.jpg&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Most of us take RSS for granted. It’s old, outdated, and perhaps “useless” to many. I would assume a large number of Gen Z doesn’t even know what it is. Despite what you may think of RSS, it represents a crucial belief in the internet: openness. With an RSS reader you can subscribe to feeds from multiple sources, whether its a blog, a YouTube channel, or even a social feed, all without being present on those platforms. “Being present” is a key set of words: most of today’s platforms drive for your presence, attention, and time. The most time you spend on their platform the better, so they decide what is in your feed in order to keep you dialed in. RSS breaks us free from this need.&lt;/p&gt;
&lt;p&gt;Outside of breaking free of platforms, RSS also provides a way out of censorship and boasts freedom of speech. If someone is writing a blog, then someone else can read it and subscribe to it, with few external forces preventing it. It’s not a perfect system, as the publication side can face domain takedowns or even hosting. On the consumption side there is also the risk of what you read being used against you. This problem was &lt;a href=&quot;https://inessential.com/2025/10/04/why-netnewswire-is-not-web-app.html&quot;&gt;originally raised&lt;/a&gt; by the creator of NetNewsWire, an RSS reader for the Apple ecosystem. It’s one of the main reasons NetNewsWire won’t be a web app:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could I encrypt the subscription lists on the server? Yes, but the server would have to be able to decrypt it, or else the app couldn’t possibly work. Which means I could decrypt the lists and turn them over.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a classic case of “won’t be evil” vs “can’t be evil.” With a setup where the subscription lists are encrypted server side then it’s a promise that the owner won’t do the wrong thing, even though there may not be that choice if the law demands it. The better route is a tech stack that enables privacy and makes it impossible to hand over any kind of data.&lt;/p&gt;
&lt;p&gt;I’m pleased to say that this tech stack does exist, and it was used to build &lt;a href=&quot;https://alcove.tools&quot;&gt;Alcove&lt;/a&gt;: an RSS reader for an open web.&lt;/p&gt;
&lt;h2&gt;How Does it Work?&lt;/h2&gt;
&lt;p&gt;Alcove is primarily powered by a single yet powerful library: &lt;a href=&quot;https://evolu.dev&quot;&gt;Evolu&lt;/a&gt;. With this library developers can build apps that run local fist using SQLite instances that are encrypted client side and then synced through relay servers. Anytime the app is opened a new identity keypair is generated to start encrypting data. These keypairs can be used to backup and restore instances on other devices, so I can easily start adding feeds on my computer and then access them on my phone later, all end-to-end encrypted. Evolu has done the hard work of solving CRDT issues that may arise as well as &lt;a href=&quot;https://www.evolu.dev/blog/scaling-local-first-software#evolu-protocol&quot;&gt;complex solutions&lt;/a&gt; for scaling large applications. Huge shoutout to the team building it as Alcove would not exist without it!&lt;/p&gt;
&lt;p&gt;Relays are the server side of the stack that stores and syncs encrypted data from the clients. Even if I wanted to know what was in those databases, I can’t. Evolu makes running relays incredibly simple too, so much so that I have two running for Alcove to help keep redundancy. In theory anyone could run an instance and support the network if the app subscribes to it, which is incredibly powerful.&lt;/p&gt;
&lt;p&gt;Outside of Evolu, Alcove is built on a pretty standard Vite + React app with shadcn/ui components, using a theme I came up with a while ago that was recently added to &lt;a href=&quot;https://tweakcn.com/editor/theme?theme=darkmatter&quot;&gt;tweakcn&lt;/a&gt; (thanks Sahaj!).&lt;/p&gt;
&lt;p&gt;Alcove is also MIT open sourced&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stevedylandev/alcove&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mirror: &lt;code&gt;git clone https://git.stevedylan.dev/alcove.git&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;Using Alcove is overall pretty simple. Visit &lt;a href=&quot;https://alcove.tools&quot;&gt;alcove.tools&lt;/a&gt; and start by a feed you want to subscribe to, or import an OPML file. Once you do Alcove will start fetching the data and populating the local database.&lt;/p&gt;

&lt;p&gt;Once you have feeds you can start reading them and marking them as read, unread, or categorize them for better organization.&lt;/p&gt;

&lt;p&gt;Something important to keep in mind is that all of this is happening locally; if you cleared your browser cache all of it would go away. That’s why is critical to backup your account early on. Just click on the settings in the bottom left and then click “Backup.” This will show a word phrase representing your cryptographic keypair; keep this somewhere safe like a password manager.&lt;/p&gt;

&lt;p&gt;Now if you wanted to start using your Alcove instance on another device, simply use the “Restore from Backup” option on the home screen and paste in your passphrase. This will authorize your new device, download the data from the relay, and decrypt it with your keys.&lt;/p&gt;

&lt;h2&gt;Censorship Resistance&lt;/h2&gt;
&lt;p&gt;While there is only so much we can do in terms of decentralized and censorship resistance in web hosting, Alcove is taking one step by using &lt;a href=&quot;https://ens.domains&quot;&gt;ENS&lt;/a&gt; and &lt;a href=&quot;https://ipfs.tech&quot;&gt;IPFS&lt;/a&gt;. Every published release will have an IPFS CID that can be accessed through a gateway or directly with an IPFS node.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# CID: bafybeiejwvoe4bszg5rgppl4ww2lrvduglc2n55od53t3is7buh5fln5fu&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Gateway: https://dweb.link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;https://bafybeiejwvoe4bszg5rgppl4ww2lrvduglc2n55od53t3is7buh5fln5fu.ipfs.dweb.link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;# Local IPFS node&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;http://bafybeiejwvoe4bszg5rgppl4ww2lrvduglc2n55od53t3is7buh5fln5fu.ipfs.localhost:8080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can get this CID hash from the GitHub releases page, or alternatively you can get it from the &lt;a href=&quot;https://app.ens.domains/alcovetools.eth&quot;&gt;alcovetools.eth ENS&lt;/a&gt;. The &lt;code&gt;contenthash&lt;/code&gt; field will be updated each time there is a release thanks to &lt;a href=&quot;https://omnipin.eth.limo/&quot;&gt;Omnipin&lt;/a&gt;. You can also simply access it through a gateway as well with &lt;a href=&quot;https://alcovetools.eth.limo&quot;&gt;alcovetools.eth.limo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;That’s Alcove. If you aren’t already using RSS feeds or blogs then I highly recommend you start today. As long as someone is writing, someone can read. These are core principles to a society that is truly free, compared to one where you can’t write freely at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For some reason the telescreen in the living-room was in an unusual position. Instead of being placed, as was normal, in the end wall, where it could command the whole room, it was in the longer wall, opposite the window. To one side of it there was a shallow alcove in which Winston was now sitting, and which, when the flats were built, had probably been intended to hold bookshelves. By sitting in the alcove, and keeping well back, Winston was able to remain outside the range of the telescreen, so far as sight went. He could be heard, of course, but so long as he stayed in his present position he could not be seen. It was partly the unusual geography of the room that had suggested to him the thing that he was now about to do. - George Orwell, 1984&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Introducing Sequoia: Publishing for the Open Web</title><link>https://stevedylan.dev/posts/introducing-sequoia/</link><guid isPermaLink="true">https://stevedylan.dev/posts/introducing-sequoia/</guid><description>A new CLI tool for publishing existing blogs to the AT Protocol</description><pubDate>Fri, 30 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/sequoia-hero.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Today I’m excited to release a new tool for the &lt;a href=&quot;https://atproto.com&quot;&gt;AT Protocol&lt;/a&gt;: Sequoia. This is a CLI tool that can take your existing self-hosted blog and publish it to the ATmosphere using &lt;a href=&quot;https://standard.site&quot;&gt;Standard.site&lt;/a&gt; lexicons.&lt;/p&gt;
&lt;p&gt;If you haven’t explored ATProto you can find a primer &lt;a href=&quot;https://stevedylan.dev/posts/atproto-starter/&quot;&gt;here&lt;/a&gt;, but in short, it’s a new way to publish content to the web that puts ownership and control back in the hands of users. Blogs in some ways have already been doing this, but they’ve been missing a key piece: distribution. One of the unique features of ATProto is &lt;a&gt;lexicons&lt;/a&gt;, which are schemas that apps build to create folders of content on a user’s personal data server. The domain verified nature lets them be indexed and aggregated with ease. Outside of apps, lexicons can be extended by community members to build a common standard. That’s exactly how &lt;a href=&quot;https://standard.site&quot;&gt;Standard.site&lt;/a&gt; was brought about, pushing a new way for standardizing publications and documents on ATProto.&lt;/p&gt;
&lt;p&gt;The founders and platforms behind the standard, &lt;a href=&quot;https://leaflet.pub&quot;&gt;leaflet.pub&lt;/a&gt;, &lt;a href=&quot;https://pckt.blog&quot;&gt;pckt.blog&lt;/a&gt;, and &lt;a href=&quot;https://offprint.app&quot;&gt;offprint.app&lt;/a&gt;, all serve to make creating and sharing blogs easy. If you are not a technical person and don’t have a blog already, I would highly recommend checking all of them out! However, for those of us who already have blogs, there was a need for a tool that could make it easy to publish existing and new content with this new standard. Thus Sequoia was born.&lt;/p&gt;
&lt;p&gt;Sequoia is a relatively simple CLI that can do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authenticate with your ATProto handle&lt;/li&gt;
&lt;li&gt;Configure your blog through an interactive setup process&lt;/li&gt;
&lt;li&gt;Create publication and document records on your PDS&lt;/li&gt;
&lt;li&gt;Add necessary verification pieces to your site&lt;/li&gt;
&lt;li&gt;Sync with existing records on your PDS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s designed to be run inside your existing repo, build a one-time config, and then be part of your regular workflow by publishing content or updating existing content, all following the Standard.site lexicons. The best part? It’s designed to be fully interoperable. Doesn’t matter if you’re using Astro, 11ty, Hugo, Svelte, Next, Gatsby, Zola, you name it. If it’s a static blog with markdown, Sequoia will work (and if for some reason it doesn’t, &lt;a href=&quot;https://tangled.org/stevedylan.dev/sequoia/issues/new&quot;&gt;open an issue!&lt;/a&gt;). Here’s a quick demo of Sequoia in action:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;ATProto has proven to be one of the more exciting pieces of technology that has surfaced in the past few years, and it gives some of us hope for a web that is open once ore. No more walled gardens, full control of our data, and connected through lexicons.&lt;/p&gt;
&lt;p&gt;Install Sequoia today and check out the &lt;a href=&quot;https://sequoia.pub/quickstart&quot;&gt;quickstart guide&lt;/a&gt; to publish your content into the ATmosphere 🌳&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;bun&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt; -g&lt;/span&gt;&lt;span&gt; sequoia-cli&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>Learning Rust With AI</title><link>https://stevedylan.dev/posts/learning-rust-with-ai/</link><guid isPermaLink="true">https://stevedylan.dev/posts/learning-rust-with-ai/</guid><description>A glimpse into a better way of learning to code, where you put the LLM in the backseat while you drive</description><pubDate>Fri, 11 Jul 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/ai-mentor.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I think most developers out there would agree that we’re in a bit of an AI hype bubble, yet one piece of AI tech that has recently hit the market which developers can’t stop talking about might be different. Of course I’m talking about “agents” like Claude Code or my personal favorite &lt;a href=&quot;https://opencode.ai&quot;&gt;Opencode&lt;/a&gt;. With a few prompts you can have an AI create a plan to implement a feature or fix a bug, and it will just do it. They generally have deep system integration with your terminal and LSPs to have a great understanding of how to build something. However in my opinion these tools, like any other AI tool, are a double edged sword.&lt;/p&gt;
&lt;h2&gt;Using AI to Learn&lt;/h2&gt;
&lt;p&gt;The truth is if you don’t know what’s going on in the code, you’re going to have a hard time when the AI fails or falls into a bad context loop. Due to this limitation, your AI tools will only take you as far as your programming knowledge. While some CEOs and VCs would argue that programmers aren’t needed anymore, I would counter that it’s more important than ever to learn how to code and program. Agents are going to be 10x more effective in the hands of someone who knows how a language works, how to break down and solve big problems, and just knows fundamental programming principles.&lt;/p&gt;
&lt;p&gt;What if you’re not one of those people yet? I personally found myself in this position where I wanted to learn Rust more, but I knew that if I leaned on AI too much I wouldn’t actually learn anything. It’s far too tempting to let the agent write everything for you and not know exactly what’s happening. On the flip side, AI can still serve as a mentor, but with some caveats. In my expedition to learn Rust I decided that outside of reading the Rust book, I would build a CLI with an AI mentor. The key here is that I would be writing all the code, as well as asking questions along the way when something didn’t make sense. The end result was a great experience, and I wanted to share a few tips in case you want to take this route yourself!&lt;/p&gt;
&lt;h3&gt;Use &lt;code&gt;agents.md&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This might be &lt;code&gt;claude.md&lt;/code&gt; or something else depending on your CLI, but it’s essentially a markdown file with specific instructions for your AI agent. In the case of learning a new language I make it crystal clear of my objectives, and that the agent should never write any code for me. Here’s the &lt;code&gt;agents.md&lt;/code&gt; file from my CLI project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# Learning Approach&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;I want to use AI as a learning tool only. Please follow these guidelines:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;1.&lt;/span&gt;&lt;span&gt; **DO NOT write code for me**&lt;/span&gt;&lt;span&gt; - I want to write all the code myself&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;2.&lt;/span&gt;&lt;span&gt; **DO provide explanations**&lt;/span&gt;&lt;span&gt; - Help me understand Rust concepts and patterns&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;3.&lt;/span&gt;&lt;span&gt; **DO suggest approaches**&lt;/span&gt;&lt;span&gt; - Give me high-level guidance on how to implement features&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;4.&lt;/span&gt;&lt;span&gt; **DO review my code**&lt;/span&gt;&lt;span&gt; - Provide feedback on code I&apos;ve written&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;5.&lt;/span&gt;&lt;span&gt; **DO answer questions**&lt;/span&gt;&lt;span&gt; - Explain concepts when I ask about them&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;6.&lt;/span&gt;&lt;span&gt; **DO provide examples**&lt;/span&gt;&lt;span&gt; - When relevant, show small examples to illustrate concepts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;The goal is for me to learn Rust by doing the implementation work myself, with AI serving as a guide and mentor rather than doing the work for me.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you happen to use something like Opencode you can use the &lt;code&gt;/init&lt;/code&gt; command to create one of these!&lt;/p&gt;
&lt;h3&gt;Write Every Line&lt;/h3&gt;
&lt;p&gt;As you might have guessed by now, the key thing I wanted to achieve was writing every line of code. I knew from past experience that even if I didn’t fully understand what I was writing, the action of writing it made me slow down and think of each piece of the code. I could guess or ask questions about what it did. By repeating patterns over and over again I started to feel those patterns imprint my memory. It’s like riding a bike: you just gotta pedal. At least for me, this became quite therapeutic! Like writing a letter or contemplating in a journal, slowly writing each line of code helps your brain process and think through what flows.&lt;/p&gt;
&lt;h3&gt;Keep Short Sessions&lt;/h3&gt;
&lt;p&gt;One of the big problems with AI tools these days is context limits. If your coding session starts to get too long, your AI is going to try to comprehend everything that is happening. Eventually it starts to lose track of what’s happening and where it is, and that’s when the hallucinating happens. Best thing you can do is break each task into a short session and start a new one when there’s a new goal or task. Where you draw that line is honestly a skill you have to grow as a programmer, so it’s good practice in cases like this.&lt;/p&gt;
&lt;h3&gt;Understand Basic Tooling&lt;/h3&gt;
&lt;p&gt;One of the best things you can do for yourself is learning some basic tooling like LSP and how to debug. LSP or Language Server Protocol is how you get those helpful hints when coding that say you’re doing something wrong. I had multiple instances while learning Rust through building this CLI where the AI was stuck and couldn’t get me out of a bug, but thankfully I knew how to read the LSP, the error checking through cargo, and I could fix the issue myself. Don’t be afraid to check the docs and the LSP, because sometimes that will be faster than attempting to do it a fifth time through AI!&lt;/p&gt;
&lt;h2&gt;The End Product&lt;/h2&gt;
&lt;p&gt;When all was said and done, I accomplished my goal of building a fun little CLI called &lt;a href=&quot;https://github.com/stevedylandev/walletfetch&quot;&gt;walletfetch&lt;/a&gt; that works like Neofetch but for EVM based wallets!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/walletfetch.png&quot; alt=&quot;walletfetch&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Taking the time to learn this way was super helpful, and I will definitely be doing it more often. It’s certainly not perfect, and it’s no replacement for core materials like the Rust book, but in my opinion it’s a great way to build projects and learn something new. If you’re an aspiring developer, I cannot stress this enough: be competent. Always be curious, ask questions, figure out “why,” and look to solve problems you really care about. I saw a post on X recently that went something like “quitting software development now due to AI tools is like quitting woodworking because the table saw was invented.” It’s a tool like anything else, but you’ve got to know your fundamentals, and you’ve got to know what to build. Be a woodworker. Be a craftsman.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s more fun to be competent&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Leaving Neovim for Zed</title><link>https://stevedylan.dev/posts/leaving-neovim-for-zed/</link><guid isPermaLink="true">https://stevedylan.dev/posts/leaving-neovim-for-zed/</guid><description>A journey through text editors and how I landed on Zed after years of Neovim</description><pubDate>Fri, 16 Aug 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/leaving-neovim-for-zed.png&quot; alt=&quot;header image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I think every developer has their own text editor journey and how they landed on the tool they use today. Perhaps I’m a geek but I love those stories. I have a great appreciation for developer tools and the work that goes into them. This post is for the other geeks out there that also care, and I hope my journey and perspective can prompt others to experiment and try developer tools outside their comfort zones. You never know what you might land on and how much you might enjoy it!&lt;/p&gt;
&lt;p&gt;My text editor journey starts with a faint memory of Atom. I was learning the true fundamentals of HTML CSS and Javascript, and I honestly can’t tell you how I landed on Atom as a text editor. I do remember using it for a few weeks, and I kept seeing other people use or mention VSCode, so of course I gave it a shot and used it for a while. However this didn’t last long. At the time my wife needed my laptop for her photo editing job, so I used my brother’s old Macbook that was holding on for dear life. VSCode’s Electron build started taking a noticeable toll on performance, and by chance I also discovered Vim around the same time.&lt;/p&gt;
&lt;p&gt;Immediately I was mesmerized by the speed and powers demonstrated by The Primeagen’s early videos. I was already a keyboard maximalist from &lt;a href=&quot;/posts/why-i-learned-vim&quot;&gt;previous jobs&lt;/a&gt; where I learned speed = productivity, so it was a no brainer that I had to learn it. Started with the basic motions and Vim tutor, and I had the advantage that I was just learning programming on the side instead of doing it full time. Within a few weeks I was in Vim consistently, writing and learning to code. The tweaking of my Vim RC eventually led to discovering Neovim thanks to &lt;a href=&quot;https://www.youtube.com/@chrisatmachine&quot;&gt;chris@machine&lt;/a&gt; and his early videos.&lt;/p&gt;
&lt;p&gt;For the next several years I stuck with Neovim and I loved it, and I owe a mass amount of my productivity to it. There were countless hours spent configuring it like many of us do. I eventually got to a point where I didn’t adjust my config much, but that soon didn’t matter.&lt;/p&gt;
&lt;h2&gt;What Changed&lt;/h2&gt;
&lt;p&gt;Every now and then I would update a plugin in Neovim and everything would break, and I would have to spend time fixing it instead of getting work done. This resulted in slimming down my config more and more, but there was still so much that went into making all the basics work. I stuck with it because it was still better than using VSCode, which I did try for a two week sprint to see if it could be any better. It was also key to a &lt;a href=&quot;/posts/a-terminal-based-workflow&quot;&gt;terminal based workflow&lt;/a&gt; that other editors couldn’t really match.&lt;/p&gt;
&lt;p&gt;The sentiment started to shift again not too long ago as I started working in some really large code bases, and boy Neovim was struggling. I would have random hang ups, frozen screens, stuff that just drove me nuts when productivity was king. I tried switching to other terminal emulators too such as Alacritty and Wezterm but it didn’t help much.&lt;/p&gt;
&lt;p&gt;This is when Zed came back into my sights. I heard about it months before and even gave it a shot back then, but didn’t stick with it because it wasn’t a terminal workflow. However it boasted as being fast, and I decided it was worth another shot. Two months later and I’ve been daily driving it since. I wasn’t sure if it would really hold up, but I can say now it has been an amazing experience and I don’t see myself going back.&lt;/p&gt;
&lt;h2&gt;My Experience with Zed&lt;/h2&gt;
&lt;p&gt;In order to understand why I eventually settled on Zed we’ll look at my general experience with it so far and how I made it work for me.&lt;/p&gt;
&lt;h3&gt;It Just Works&lt;/h3&gt;
&lt;p&gt;One of the biggest things that has stood out to me using Zed so far is how “everything just works.” There are so many features of an IDE or text editor that people take for granted until they have to set it up themselves in something lower level like Neovim. LSP (language server protocol) is certainly one of them. If you’re not familiar it’s the hints or errors that show up while you’re writing up your code, giving you deep insights to your repo on a language level. When you setup LSP in Neovim it’s a lot of work, and sometimes it can be a bit harder to figure out why it might be bugging out. However it does give you way more control and the option to do a lot of customization. With Zed LSP just works. There are configurations you can make to edit some things, but as a whole it just zips out of the box. There are already keybindings for things like “show definition”, “go to definition”, or even code actions. The only downside is outside of an extension you can’t use your own LSP that’s installed on your machine, but there’s always a pretty large language support that I haven’t had this issue yet.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/QmNkysZ5Roy723sphUST8abmHakKR86K2YHXeeVTU22ASH.png&quot; alt=&quot;LSP demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Another piece that’s related to LSP is completions. This is when you’re typing some code and get suggestions for auto completions that quickly fill the rest of the code out. LSPs usually have great auto-completion because they’re aware of the patterns used in that language. Just to be clear we’re not talking about Copilot yet, this is just completions for snippets and LSP. Once again with Zed it just works out of the box, unlike Neovim which ends up requiring several plugins to make it work right.&lt;/p&gt;

&lt;p&gt;Finally there’s Git integrations. What normally required multiple plugins in Neovim is again ready out of the box with Zed, including feature like toggling Git Blame, viewing diffs, and gutter symbols showing the status of edited lines.&lt;/p&gt;

&lt;p&gt;If I had to make a crude comparison, it’s similar to Linux and Apple. Linux will give you far more control over every piece of your software and hardware at the cost of spending time to configure it. Apple will give you less control but it will likely run smoother.&lt;/p&gt;
&lt;h3&gt;Speed&lt;/h3&gt;
&lt;p&gt;Of course one of the biggest reasons I gave Zed a shot was speed, and boy it was worth it. It is noticeably snappy, handles larger codebases like a champ, and I haven’t experienced any lag so far. Normally I wouldn’t recommend building most things in Rust, but this app kinda makes me reconsider. There are so many developer tools written in Rust and perhaps that’s one of its biggest boasts. The team at Zed have really outdone themselves as this app; its truly a work of art.&lt;/p&gt;
&lt;p&gt;Could I possibly make neovim faster by adjusting my config? Perhaps, but in the end it’s more time down the drain that I could have used writing code and being productive. For productivity nerds like me I believe there is a delicate scale: how much time I spend automating a task vs how much time the task would have taken without automation. In this case the benefits aren’t worth the cost when there’s a tool like Zed that will do just fine.&lt;/p&gt;
&lt;h3&gt;Vim Mode&lt;/h3&gt;
&lt;p&gt;While in the middle of my time with Neovim I came across other frustrated Neovim users that found themselves spending too much time fixing issues in Neovim to the point they switched to VSCode. Of course I had a few moments like this myself and I wondered if I had missed something. At this point I had a much faster computer, so I figured I would give it another shot. Of course I downloaded the Vim plugin as the Vim keybindings are worth learning no matter what text editor you use, and it was incredibly disappointing. Nothing had ever felt so buggy and jagged, and it remained one of the big reasons I couldn’t stick with VSCode. I was back in Neovim within a week or two.&lt;/p&gt;
&lt;p&gt;When I was considering Zed again I read &lt;a href=&quot;https://registerspill.thorstenball.com/p/from-vim-to-zed&quot;&gt;a blog post&lt;/a&gt; about the custom Vim mode built into Zed. This was not a third party plugin; this was a labor of love from the developers building Zed. They’ve made it clear that they don’t plan to port absolutely everything to Zed, but they have done a fantastic job supporting the important stuff that makes the editor an S-tier experience.&lt;/p&gt;
&lt;p&gt;I’ll likely get into this further into the post, but the way you can structure keybindings for Vim mode in Zed is fantastic. The structure allows for your typical VScode style config, but with the ability to scope a keybinding to a Vim mode is such a huge win for Neovim users. For instance, I can cheat my way into using a leader key when in normal mode and get things like &lt;code&gt;space d&lt;/code&gt; to see diagnostics, or &lt;code&gt;space t&lt;/code&gt; to open a full window terminal. It’s a pattern many Vim users will appreciate and I wish there was more docs for it as I’ve had to figure some of it out myself.&lt;/p&gt;
&lt;p&gt;Beyond keybindings all the other stuff you would expect in Vim motions are here, with of course a few obscure ones slowly being added release by release. Some of them take a unique approach by using some of Zed’s built in features as a replacement for what Vim would normally use, like search and replace for example. Generally you would have to do a Vim command to search for a word and replace it, and those commands still do what you expect, but it brings up Zed’s Multibuffer view when doing a project wide search. You get Vim mode but it’s still Zed and it’s unique feature set.&lt;/p&gt;
&lt;h3&gt;AI Stuff&lt;/h3&gt;
&lt;p&gt;When it comes to AI features I think Zed provides some great built in tools. I will disclose that I’m not a big user of AI within the text editor (a topic for another blog post), so there might be things you’re looking for compared to Cursor that I can’t speak to. With that said it does have Copilot built in which is probably what most people want to know.&lt;/p&gt;
&lt;p&gt;Zed also features an assistant panel where you can access several AI models via API, including OpenAI, Ollama, and Anthropic. Just requires a few lines of config to get started.&lt;/p&gt;

&lt;p&gt;One feature that I think is particularly nice is the inline assistant, where you can select some lines of code and use &lt;code&gt;ctrl-enter&lt;/code&gt; to trigger a request to be made to your code via the AI assistance configuration mentioned previously. If you like the results then you can confirm and keep coding.&lt;/p&gt;

&lt;h3&gt;Zed ≠ Neovim&lt;/h3&gt;
&lt;p&gt;You can probably gather so far that I’m a huge Zed fan, however I will say it’s not a 1/1 replacement to Neovim. What makes Neovim so special is that it’s native to the terminal. Whenever I need to edit a configuration file for an app or just edit something really quickly while I’m already navigating in a terminal, nothing beats the convenience of whipping out Neovim. Opening Zed for every single file like that would get exhausting, but for longer term sessions or projects it’s perfect. If you compare to motorcycles, Neovim is my dirt bike and Zed is my cruiser.&lt;/p&gt;
&lt;h2&gt;Making Zed Work for A Neovim User&lt;/h2&gt;
&lt;p&gt;While Zed can’t truly be a drop in replacement of Neovim, there are a lot of small configurations that really help improve the experience and make it familiar to a Neovim/Vim user.&lt;/p&gt;
&lt;h3&gt;Vim Mode &amp;amp; Keybindings&lt;/h3&gt;
&lt;p&gt;First one is obvious, turn on Vim mode. Zed has a great &lt;a href=&quot;https://zed.dev/docs/vim&quot;&gt;docs page&lt;/a&gt; with loads of extra info, some default Vim bindings, and some extra configs available to make it work for you. By far my favorite piece that I mentioned earlier is the ability to do key bindings based on Vim modes. Here’s some examples:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; VimControl &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space b&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::ToggleGitBlame&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-k&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::Hover&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space l f&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::Format&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space d&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;diagnostics::Deploy&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space f f&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;file_finder::Toggle&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space o&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tab_switcher::Toggle&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space e&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::ToggleLeftDock&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space /&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::NewSearch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;n&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;search::SelectNextMatch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-n&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;search::SelectPrevMatch&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space t&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::NewCenterTerminal&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;g b&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::ToggleComments&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;+ +&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::Save&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;space c&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pane::CloseActiveItem&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; vim_mode == visual &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !VimObject&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-j&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::MoveLineDown&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;shift-k&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;editor::MoveLineUp&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of these are pretty self explanatory, but the key is that the first set is in “Normal” mode, while the next set is “Visual.” There’s also small improvements or shortcuts they provide in the docs like this set of key bindings that allows you to switch panes similar to how most people have Neovim setup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Dock || Terminal || Editor&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-h&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Left&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-l&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Right&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-k&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Up&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;ctrl-j&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;workspace::ActivatePaneInDirection&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;Down&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Something else I would recommend to anyone who is trying to migrate from Vim/Neovim to Zed is checking out the &lt;a href=&quot;https://github.com/zed-industries/zed/blob/340a1d145ed15e39a4a27afc5a189851308fb91d/assets/keymaps/vim.json#L4&quot;&gt;default Vim keymap&lt;/a&gt;. There’s so much there that acts as a helpful reference of what’s supported and what you may want to adjust!&lt;/p&gt;
&lt;h3&gt;Reduced UI&lt;/h3&gt;
&lt;p&gt;Zed already has a pretty nice minimal UI, but I prefer something closer to my Neovim setup. Thankfully Zed offers these options, such as disabling the tab bar, scroll bar, reduced toolbar, and relative line numbers&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cursor_blink&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;relative_line_numbers&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;scrollbar&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;show&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;never&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;vertical_scroll_margin&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;tab_bar&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;show&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;toolbar&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;breadcrumbs&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;quick_actions&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/QmVtqSNc6Ff9Zy8vBxr92AcVdh9eAGYkU8zWGLoyTnoeDL.png&quot; alt=&quot;Zed settings&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Plugin Replacements&lt;/h3&gt;
&lt;p&gt;Since I don’t use the tab bar I wanted something similar to Telescope to navigate between buffers or files, and thankfully there is an option for that! This key binding will show currently open all buffers, which is separate from the file finder.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; VimControl &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;space o&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;tab_switcher::Toggle&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Speaking of Telescope, one big replacement is project wide search. While Zed doesn’t have a fuzzy find feature, the project wide search is excellent. It will show all results in a multibuffer view which is pretty slick, and allows you to jump between that view and the buffer itself pretty easily.&lt;/p&gt;

&lt;p&gt;The terminal toggle is pretty similar to something like VSCode but there are some other hidden ways to get a better terminal experience. One of them is a shortcut to toggle the bottom terminal to be full screen, but even better is opening a terminal as a buffer in the main editing view.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Editor &amp;amp;&amp;amp; VimControl &amp;amp;&amp;amp; !VimWaiting &amp;amp;&amp;amp; !menu&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;space t&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;workspace::NewCenterTerminal&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One of the big things I had to leave behind was Tmux and switching projects. While it isn’t a perfect replacement, Zed has a “switch projects” feature which works really well and makes it pretty easy to switch contexts. You just won’t get the exact same control and layout setup that you can get with Tmux&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;context&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Workspace&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;bindings&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;cmd-k&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;projects::OpenRecent&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;create_new_window&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Should You Use Zed?&lt;/h2&gt;
&lt;p&gt;If you’re still on the fence of trying Zed I would say it’s at least worth giving it a shot for a few days. In my experience so far it’s a unique and capable text editor, but ultimately I vouch for anything that makes you more productive. That might end up being VS Code or Jetbrains or hell maybe even EMacs. Do what’s best for you, but don’t be too stubborn to try something new.&lt;/p&gt;
&lt;p&gt;Edit: Thank you for all the love on this post! If you want to see my full settings and keymaps I have them linked below for your convenience.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sipp.stevedylan.dev/s/AfQ6gyKPF7&quot;&gt;settings.json&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sipp.stevedylan.dev/s/05l9VQyh92&quot;&gt;keymap.json&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Programmers on the Verge of Extinction</title><link>https://stevedylan.dev/posts/programmers-on-the-verge-of-extinction/</link><guid isPermaLink="true">https://stevedylan.dev/posts/programmers-on-the-verge-of-extinction/</guid><description>Examining the parallels between art, AI, and the existential threat to programmers</description><pubDate>Fri, 27 Feb 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/programmer-extinction.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build a B2B SaaS, make no mistakes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Great meme, but in reality that’s how most of our time “programming” has gone. We tinker with multiple agents, git worktrees, markdown files in a dozen different folders, all to maximize our effectiveness with AI assisted coding. Sometimes it can be exciting, watching a computer spit out code that actually works, bringing your ideas to life at a rapid pace. I personally have been using Claude or OpenCode to build several projects, but to be honest, most of them have left me feeling rather empty. The end product is useful, the job gets done, but… I don’t really care for it? As in, I don’t feel an obligation to keep updating it or make it better. Such an odd and weird feeling that for at least me personally has stuck around since going heavy into AI programming. Why is that? Why does programming feel so empty?&lt;/p&gt;
&lt;h2&gt;Data Learning to Paint&lt;/h2&gt;
&lt;p&gt;I recently watched &lt;a href=&quot;https://youtu.be/mb3uK-_QkOo&quot;&gt;a great keynote by Brian Sandersen&lt;/a&gt;, a sci-fi/fantasy author. In it he tries to answer the question of why AI generated art gives him such a bad taste in his mouth. He admits that his opinion can be counted among those in history who despised new technology and were left to eat their words, so he tries to go another level deeper by asking “what is art?” and “why do we make it?” To help answer this he turns to Star Trek: the Next Generation character Data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/data-painting.jpg&quot; alt=&quot;data painting&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;He’s an android, and a lot of his character arcs were about exploring what it means to be human. One of the recurring themes in the show was his attempts to create art: painting, poetry, music, becoming a comedian. I rooted for Data, a synthetic being without emotions trying so hard to understand the human experience. I still do. I have no problem with Data creating art; if he were real, I’d applaud him.&lt;/p&gt;
&lt;p&gt;Why do I empathize with Data yet not the AI large language models?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As Brian goes down this rabbit hole of creating art, and recalling his own creative writing journey, he realizes that art is more about his journey, rather than the end product.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Data created art because he wanted to grow. He wanted to become something. He wanted to understand. Art is the means by which we become what we want to be.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The Need to Grow&lt;/h2&gt;
&lt;p&gt;You ever look back at some of your earlier projects or pieces of code? For most of us, especially myself, it can be pretty cringe. However, it can also be encouraging. That feeling stems from the root that you have grown since you last wrote that code. Your knowledge has grown and your skills have evolved, giving you the ability to write cleaner and more efficient code. Not only that, your problem solving skills have also advanced. Programming is mostly problem solving, and by pushing yourself further and further, you can solve harder problems as time goes on. At least, that’s how it used to be.&lt;/p&gt;
&lt;p&gt;Nowadays we make AI models solve those problems. You may counter with the fact that “oh well this is a problem I solved before,” and sure some stuff can be pretty trivial, but if you never stop to solve those problems, will you remember how to do it without an agent? I like to tell myself I’m not becoming dull, but I know better. What about the next generation of developers who haven’t solved that problem? I’m sure the agent will give them the answers, but now there’s an implementation that hasn’t been reviewed by someone who understands it.&lt;/p&gt;
&lt;p&gt;I think we can also argue that it’s ok to use AI for scaffolding code, saving time by not having to write boilerplate that we would have needed to write before. While that’s true, and it’s often something I do myself, I’ve started to wonder if this is part of why my code feels so empty. If I already knew the answers to the problem &lt;em&gt;and&lt;/em&gt; I didn’t write it, did I actually do anything? What did I accomplish outside prompting an agent?&lt;/p&gt;
&lt;p&gt;How did I grow?&lt;/p&gt;
&lt;h2&gt;The Need to Produce&lt;/h2&gt;
&lt;p&gt;In Sandersen’s keynote he also talks about how art is mostly useless in the grand scheme of things. Yes there is a case for art being consumed by others, but primarily art is about the intrinsic need to make it. It’s about the process, and how it changes you in the journey.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The book, the painting, the film script is not the only art. It’s important, but in a way it’s a receipt. It’s a diploma. The book you write, the painting you create, the music you compose is important and artistic, but it’s also a mark of proof that you have done the work to learn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The argument could be made that programming is in many ways a type of art. It involves creative solutions to problems that can be solved numerous ways. It can evolve your perspective and strengthen your mind. In my personal experience I even find it therapeutic. However there is a key difference with this and what Sandersen draws: the product.&lt;/p&gt;
&lt;p&gt;Unlike most art, the product of programming is indeed useful, let alone profitable. For the majority it’s why the programming exists in the first place. The finished software is designed to be used, consumed, and often purchased. Those of us who do software development professionally are expected to produce, and to do it quickly. AI has only made this worse since agents bring the ability to write &lt;em&gt;lots&lt;/em&gt; of code at once. In the grand scheme of our programmer evolution, the need to deliver the product outweighs the need to grow.&lt;/p&gt;
&lt;h2&gt;Extinction&lt;/h2&gt;
&lt;p&gt;In the tug-of-war between growth and producing, programmers face an existential threat. Juniors are faced with the decision of switching careers because the industry tells them they are replaceable, even though the &lt;a href=&quot;https://www.theregister.com/2025/08/21/aws_ceo_entry_level_jobs_opinion/&quot;&gt;CEO of AWS would say otherwise&lt;/a&gt;. If the next generation of programmers sticks around and they don’t experience the growing pains of non-AI assisted development, how will they be able to solve harder problems? The senior engineers will only be around for so long. For those juniors, AI can only take you so far, and you better hope that it doesn’t make mistakes in fields like medicine or security. When the machine built to give specific doses of radiation suddenly gives too much because of an obscure bug, they’re not gonna blame the AI agent that wrote it. An extreme example perhaps, but with how much our world depends on software, this type of tech debt adds up. Just look at how many times the internet has basically crashed over the last two years due to bugs from cloud providers.&lt;/p&gt;
&lt;p&gt;True programmers exist because they’re passionate about solving real and hard problems. They enjoy the satisfaction of solving a puzzle that helps people. By offloading that problem solving to an agent, we’re cheating ourselves of an experience that would give us a sense of fulfillment, and knowledge that we would otherwise not have. Not only that, but the desire to maintain that software down the road will be nonexistent. Why would we bother trying to fix bugs in something that cost us zero blood sweat and tears?&lt;/p&gt;
&lt;h2&gt;Moving Forward&lt;/h2&gt;
&lt;p&gt;It’s hard to say what the future looks like.  On one hand I like to be optimistic that traditional programming will continue to exist, but I also know plenty of programmers who don’t care how they get to the final product, and that’s their choice. There’s a chance none of us are writing code in a decade or possibly sooner, but I don’t live in the projections of the future. Instead I’ll be trying to find some kind of balance between using AI and doing the work manually, with the key objective of &lt;em&gt;enjoying&lt;/em&gt; the work I do. I want to solve the harder problems, problems that can’t be solved with vibe coding. I can’t do that if I let my mind become dull.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Therefore, we have the power here and not the machine, for it was created to try to make something useful. But it cannot admire what it made.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Programming Bowls</title><link>https://stevedylan.dev/posts/programming-bowls/</link><guid isPermaLink="true">https://stevedylan.dev/posts/programming-bowls/</guid><description>Realizing how much of the programming space is just bowls</description><pubDate>Thu, 16 Oct 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/other/diogenes.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of my favorite videos I watched recently was by Pewdiepie, and while the title is &lt;a href=&quot;https://www.youtube.com/watch?v=n_Lv_mw6m6c&quot;&gt;“help, Im going through a midlife crisis…”&lt;/a&gt;, you realize about 3/4 of the way through that it was just bait. The real topic of the video is minimalism, and a great story about the philosopher Diogenes and his bowl. Diogenes was known for only having a single possession to his name: a wooden bowl. One day as he was walking he saw a child by the river drinking the water by cupping his hands together. It was in this moment Diogenes realized that his bowl was useless since he had his hands, and he threw away his bowl. It was the ultimate commitment to the principles he believed, that true happiness was not found in wealth or possessions. You should watch the video because 1. Pewdiepie tells the story way better than I do and with a lot more humor, and 2. he articulates how he’s carried out the principles of minimalism in his own life and living by principle.&lt;/p&gt;
&lt;p&gt;I’m not going to argue the points of Diogenes in the grander scheme of life, however I will explore what this might look like in the world of programming. For a few months now I’ve been reconsidering how I approach and solve problems in software, especially in web development. I now am starting to ask myself “what are the bowls of programming?”, and in this post we’ll consider a few.&lt;/p&gt;
&lt;p&gt;Before we get started, I want to make it explicitly clear that these points are not absolute. This way of thinking should be seen as a spectrum, and depending on the context of what you’re building or solving, you may land on different levels of programming minimalism. What I would encourage is to dig deep and ask yourself with honesty where your solutions should land, knowing that sometimes it’s ok just to choose whatever you feel most comfortable in. It’s your life; do what you want, but I hope to argue a point of responsibility towards other programmers.&lt;/p&gt;
&lt;h2&gt;Unnecessary Complexity&lt;/h2&gt;
&lt;p&gt;While this point mostly applies to web development, it can apply elsewhere. For the sake of illustration we’ll look at the web dev space thanks to Javascript frameworks. As web apps started to take off and more and more people moved towards Javascript, there was a brief period where we were onto something. JAMStack, aka “Javascript, APIs, Markdown” was a simple yet brilliant way to standardize building apps or websites. This was the golden era of static site generators and simple client-server relationships, where if a hosting platform didn’t work out it was simple to move somewhere else. The frameworks that emerged were platform agnostic, people hosted their own stuff, and it created an open web.&lt;/p&gt;
&lt;p&gt;Years later we start to see complex SSR Javascript frameworks being pushed more and more by hosting providers. Next.js brings a new developer experience that a lot of people latch onto and quickly becomes one of the most commonly used frameworks in the ecosystem. The main problem is that it has become too complex and specially designed to work on Vercel. Yes you can “self host” Next.js, but it won’t work or perform the same way it does on Vercel. A whole initiative was started by Netlify and Cloudflare to create a fork called &lt;a href=&quot;opennext.js.org&quot;&gt;OpenNext&lt;/a&gt; to help make self hosting a fully featured Next.js app possible, and it’s still not meeting all the features Next.js offers.&lt;/p&gt;
&lt;p&gt;Perhaps the most frustrating part is that most developers don’t actually need Next.js 95% of the time when building web applications. We’ve even seen &lt;a href=&quot;https://x.com/joshtriedcoding/status/1922631724002902083&quot;&gt;influencers give tips&lt;/a&gt; on how they can speed up their Next.js app by making all pages render as static except for API routes. It’s simply bad education for new developers who have no idea how simple it can be to spin up a backend API and use that in combination with a static app. Some don’t even know the difference between client and server architecture, and that’s ok because we were all there once, but I don’t want people to stay there. It’s one of the reasons I built &lt;a href=&quot;https://bhvr.dev&quot;&gt;bhvr&lt;/a&gt; as I wanted people to know that you can have a solid DX while not risking platform lock-in. It can’t be used for every problem, and even I am willing to admit that an SSR stack like Next.js or TanStack Start is better suited for an ecommerce platform. What I am saying is people should know the options out there. You don’t need the complexity of an SSR stack.&lt;/p&gt;
&lt;p&gt;Next.js, by and large, is a bowl.&lt;/p&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;p&gt;Another piece of the programming pie that could be argued as a bowl is dependencies. Just recently we’ve seen multiple &lt;a href=&quot;https://thehackernews.com/2025/09/40-npm-packages-compromised-in-supply.html&quot;&gt;supply chain attacks on NPM and even a worm&lt;/a&gt;. We simply run &lt;code&gt;install&lt;/code&gt; with trusting ignorance that all the code we’re downloading is benevolent. The majority of it is, but it’s the reality that most of us don’t care that’s more disturbing. We’ve become lazy and would rather install a package that’s less than 100 lines of code rather than implement it ourselves and know for a fact that it’s not going to attack us. Dependencies also make us deal with clashing version interdependencies, where two packages require the same external package and they both require different versions. It’s an awful experience.&lt;/p&gt;
&lt;p&gt;While all of these things are true, we must admit the necessity of package managers and dependencies in modern software. Not everything can be written from scratch, some packages like cryptographic libraries need audits and should not be rolled by hand. There are always exceptions and we have to look at it with nuance. With that said, all of these things have made me stop and think, “do I really need to download this?” or “what am I downloading exactly?” For some apps we’re totally ok just installing twenty dependencies as it’s not a serious project, but what about the software we want to last longer than a year? What about web apps that people depend on and we want to keep them running five or even ten years from now? We should think about these things before we just run &lt;code&gt;install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dependencies, in many cases, are a bowl.&lt;/p&gt;
&lt;h2&gt;Languages and Development Environments&lt;/h2&gt;
&lt;p&gt;If I haven’t lost you yet then I’m probably about to with this last one. We’re getting into pretty deep water and areas that most (including myself) are not ready to tread. There are some who argue and live by the principle that most programming languages or development environments are bowls. A huge influence into this particular mindset is &lt;a href=&quot;https://100r.co&quot;&gt;100 Rabbits&lt;/a&gt;, and one of my favorite blog posts on the subject is &lt;a href=&quot;https://100r.co/site/weathering_software_winter.html&quot;&gt;Weathering Software Winter&lt;/a&gt;. In the post they discuss how their desire to live on a solar-powered sailboat greatly limited the kind of software they could download or build with, especially as designers. If you’ve ever touched an Adobe product you probably know exactly what they’re talking about; the need to constantly be connected to the internet to use a piece of software. Even when exploring building their own, they found limitations around tools like Xcode that constantly required heavy downloads or other languages that needed to install a bunch of packages. It’s these situations of limited network speed that we don’t think of often, and it’s exactly where 100 Rabbits found themselves.&lt;/p&gt;
&lt;p&gt;The solution? Perhaps a bit extreme, but they built their own VM and assembler. With a VM they could compile software to run on almost any device, no matter how old it is or what the OS is. It’s low level enough to accomplish most tasks, and it’s with this combination that they continue to build basic OS level applications to fulfill their design and everyday needs. Their goals were to have something so simple that they could fit the documentation on a t-shirt and reimplement it within a weekend. When you know your software so well that you also have a good idea of what it looks like in assembly, you can truly maintain and repair your tools. These tools are also built to last, using recycled hardware that is normally thrown out. A form of eco-minded software development that has labeled itself as permacomputing. Now of course this is not something we can all just pick up and start rewriting all software. The world is complex which means we have complex software running everywhere, but it also demonstrates that it doesn’t always have to be that way. There are ways we can build thoughtful, long lasting, and meaningful software to meet our own needs.&lt;/p&gt;
&lt;p&gt;For 100 Rabbits, most languages were a bowl.&lt;/p&gt;
&lt;h2&gt;Where Do We Land &amp;amp; Why Bother?&lt;/h2&gt;
&lt;p&gt;No matter how hard I might argue these principles, I hope my point of nuance comes across. None of these are absolutes. One could argue programming minimalism to the point of writing ones and zeros. The range of how far you can go with throwing out programming bowls depends on what you’re building. 100 Rabbits also pushes for more offline applications and depending less on the connected web. However there is a big difference between building simple desktop apps and an app like ICEBlock. Context decides how complex something needs to be. We don’t want to roll our own cryptography; it’s ok to trust libraries that have been audited and battle tested.&lt;/p&gt;
&lt;p&gt;A point that Pewdiepie makes in his video is that while he has thrown out a lot of the stuff that he doesn’t need, it doesn’t mean he throws out everything. There are plenty of things he owns simply because he enjoys them, like a random action figure. If it’s something that brings happiness then that’s great! I have a small rock that looks like an owl on my desk. It doesn’t do anything or serve any purpose; I just like it. I believe the same can go for software engineering. Sometimes I like how quickly I can spin up an app with Bun and Typescript. That’s actually &lt;a href=&quot;https://bearblog.stevedylan.dev/sippso-minimal-code-sharing/&quot;&gt;what I did&lt;/a&gt; this weekend while drafting this post. If I really wanted to I could have used React to handle the client side logic, but I personally thought the app was simple enough that I didn’t need it. Again, if I wanted it, that would have been ok too. There are no absolutes in the decisions you make in programming as far as I’m concerned.&lt;/p&gt;
&lt;p&gt;If that’s the case, why bother? There is one important factor that we cannot forget as we make these decisions: people. My greatest aspiration in programming is to build software that advances principles such as privacy, security, freedom, and the ability to repair. ICEBlock was an iOS app that was &lt;a href=&quot;https://www.wired.com/story/apple-took-down-ice-tracking-apps-their-developers-arent-giving-up/&quot;&gt;taken off the Apple App Store&lt;/a&gt;, and that is a chilling reminder that the stack decisions we make actually do matter. Yes a web app could also be taken down but it’s much harder to do. I could have used React in my simple code sharing tool but by choosing to use just html, css, js, and sqlite, it’s a lot easier for someone to use and alter long term without any potential dependency issues. We also have to consider if what we’re building is going to be used by other developers and in what ways. A great example is &lt;a href=&quot;https://github.com/paulmillr/noble-hashes&quot;&gt;@noble/hashes&lt;/a&gt; where there is zero dependencies but acts as a foundational building block for cryptographic operations.&lt;/p&gt;
&lt;p&gt;In the end it comes down to how much we care. Not speed for the sake of speed, or minimalism for the sake of minimalism, but for privacy, security, freedom, and longevity for the sake of people who depend on it.&lt;/p&gt;
&lt;p&gt;Keep the bowl or throw it out, but at the very least think about it first.&lt;/p&gt;</content:encoded></item><item><title>Resizing IPFS Images with Pinata’s Image Optimization Tools</title><link>https://stevedylan.dev/posts/resizing-ipfs-images/</link><guid isPermaLink="true">https://stevedylan.dev/posts/resizing-ipfs-images/</guid><description>Learn how to use Pinata&apos;s Dedicated Gateway image optimization tools</description><pubDate>Thu, 23 Jun 2022 04:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://medium.com/pinata/resizing-ipfs-images-with-pinatas-image-optimization-tools-fb381bee58aa&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/medium.CI909Xjl_oL8pb.webp&quot; alt=&quot;Link to Medium&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Medium &lt;/a&gt;
&lt;p&gt;If you’re a developer in the NFT space, you have probably had to fetch IPFS content before, and depending what tools you use the experience is varied. Using a local IPFS node is not very practical or fast, and using a public gateway can be risky due to congestion. Dedicated Gateways on the other hand are much faster, and are great for app development. But what if you have to fetch an entire NFT project through IPFS? That could be 10,000 images at 5Mb each, awful for web page optimization, and you have to load every. single. one. How is that gonna work? And then what happens when you have another NFT project?&lt;/p&gt;
&lt;p&gt;Pinata’s &lt;a href=&quot;https://docs.pinata.cloud/gateways/dedicated-gateways&quot;&gt;Dedicated Gateways&lt;/a&gt; already have a blazing fast global CDN (content delivery network) that caches content on the first load, so already you’re off to a good start, but that’s still a lot of data to pull through a webpage. With the rollout of our new pricing plan and features, we’ve released a new tool for everyone: &lt;strong&gt;Pinata Image Optimization.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built on top of Pinata’s Dedicated Gateways, Pinata Image Optimization allows you to resize images on the fly with URL queries, all through your own gateway. Seriously. For faster web pages and reduced bandwidth, all you have to do is add a few text characters to the end of your image link. Let’s go through how it works with an example to show how you can use it to speed up your projects.&lt;/p&gt;
&lt;h2&gt;How To Use Pinata Image Optimization&lt;/h2&gt;
&lt;p&gt;Image Optimization works by adding queries to the end of our Dedicated Gateway urls. Here’s an example of a basic dedicated gateway url:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedsimkins.mypinata.cloud/ipfs/{CID}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CID is the content identifier and &lt;a href=&quot;https://docs.pinata.cloud/gateways/retrieving-content&quot;&gt;how we stream IPFS content&lt;/a&gt;. Just for our example, I have a CID of an image I shot out west and uploaded to Pinata!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;QmfKsRfqkWYuShSMDghMpLt8SQnWyPhDaEe8JUauM8E7Uz&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we add this file to our gateway, we get this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedsimkins.mypinata.cloud/ipfs/QmfKsRfqkWYuShSMDghMpLt8SQnWyPhDaEe8JUauM8E7Uz/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you click that link you’ll see our image. If that link took a few seconds to load, there’s a reason for that: it’s huge. Shot on an old medium format film camera and scanned at high resolution, this picture of the Badlands National Park is 10,000 x 10,000 pixels, coming in at around 38Mb. For context, your standard web images are under 300K.&lt;/p&gt;
&lt;p&gt;While the dedicated gateway uses a global CDN to cache the content on the first load, it’s still a lot to load. Most websites are gonna struggle using this image, but if you’re building something like Foundation or SuperRare, or any project that relies on a lot of images, you might not want to give up that full size image. And you certainly don’t want to take the time resizing them one by one.&lt;/p&gt;
&lt;p&gt;And that’s where our tool comes in. Let’s see Pinata Image Optimization in action.&lt;/p&gt;
&lt;p&gt;To add on the image resizing query to the url, we simply need to add a “?” with the query itself. Here’s an example of changing the width of an image to 1080 pixels:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;?img-width=1080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we want to change the height too, we just need to use an ampersand (&amp;amp;) to add on another query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;?img-width=1080&amp;amp;img-height=1080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now all we need to do is add this little snippet to our previous image url:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedsimkins.mypinata.cloud/ipfs/QmfKsRfqkWYuShSMDghMpLt8SQnWyPhDaEe8JUauM8E7Uz?img-width=1080&amp;amp;img-width=1080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check that out. Instead of being a 10,000x10,000 resolution, it’s now 1080x1080 and only 266Kb. Doesn’t get any simpler than that — and this is only the beginning of what this tool can do. Here’s a small list of other things you can do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DPR (Device Pixel Ratio)&lt;/li&gt;
&lt;li&gt;Image Fit — for scaling down, image positions and more!&lt;/li&gt;
&lt;li&gt;Image Quality — Set a scale from 1–100 to easily reduce a high quality image&lt;/li&gt;
&lt;li&gt;Auto Image Formatting — Use Webp where supported, but then fall back to jpeg or png&lt;/li&gt;
&lt;li&gt;Animation Still — Turn a gif into a still image&lt;/li&gt;
&lt;li&gt;On Error Redirect — Redirect to a different image if there is a problem&lt;/li&gt;
&lt;li&gt;Metadata Controls — Control what EXIF data is revealed with the image&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pinata Image Resizing really gives you control as a developer in the NFT space to handle IPFS images with ease. We highly recommend checking out our &lt;a href=&quot;https://docs.pinata.cloud&quot;&gt;developer docs&lt;/a&gt; to see them all.&lt;/p&gt;
&lt;p&gt;To really understand what’s possible with this tool, let’s look at a real world example.&lt;/p&gt;
&lt;h2&gt;Displaying a 10K PFP Project&lt;/h2&gt;
&lt;p&gt;When it comes to NFT projects, right now the classic 10,000 PFP project is the industry standard — it’s what everyone is trying to create. But if you’re an NFT marketplace trying to get up and running, you’re almost certainly going to run into some problems if you have to load an entire 10K project on a single webpage. The speed is going to suffer greatly, especially if you are pulling full sized large images from IPFS.&lt;/p&gt;
&lt;p&gt;In our example, we’ll make a simple app that displays more images I took out West in the Badlands. First thing we need to do is spin up a React app by running the following in our terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; create-react-app&lt;/span&gt;&lt;span&gt; ipfs-image-optimization&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;cd&lt;/span&gt;&lt;span&gt; ipfs-image-optimization&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we’ll delete the CSS files and boilerplate in App.js. We’ll also add in some structure for our image grid.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;App&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;grid&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usually a PFP project will be in a single folder, and each file inside will be named in sequential order such as “1.png, 2.png, 3.png,” etc. This makes our base URL very simple. The only thing that will be changing will be the ID of the image, so it looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&amp;amp;img-height=1080&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s break this down again so we know what’s going on.&lt;/p&gt;
&lt;p&gt;We have our gateway url &lt;code&gt;https://stevedsimkins.mypinata.cloud/ipfs/&lt;/code&gt;, then we have our CID &lt;code&gt;QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ&lt;/code&gt;, then our dynamic image id &lt;code&gt;${id}.jpg&lt;/code&gt; and finally our image optimization &lt;code&gt;?img-width=1080&amp;amp;img-height=1080&lt;/code&gt;. Of course not all PFP projects are this simple, but with this formatting you can pass in multiple parameters with objects to adjust to your needs.&lt;/p&gt;
&lt;p&gt;Our image folder only has 8 images, therefore we just need a simple for loop to generate an array that will hold the numbers 1 through 8. That way we can access it later to generate our image components. Just start with an empty array, then push the numbers into it with the for loop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;let &lt;/span&gt;&lt;span&gt;imageIds&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;for (let &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; 8&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	imageIds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the fun part: generating the images! We’ll take our imageId array and map over it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	imageIds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) =&amp;gt; {});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we’ll declare our base URL with our dynamic image ID, as well as a name for the alt text later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	imageIds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		let &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.jpg?img-width=1080&amp;amp;img-height=1080`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		let &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `nft &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we just need to create a component to hold the image, using the url as the image src and the name as the image alt!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	imageIds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		let &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.jpg?img-width=1080&amp;amp;img-height=1080`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		let &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `nft &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That leaves us with the final code for App.js. Add in a little CSS and we end up with a nice little image grid that loads FAST!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;&quot;./App.css&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let &lt;/span&gt;&lt;span&gt;imageIds&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	for (let &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt; &amp;lt;=&lt;/span&gt;&lt;span&gt; 8&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		imageIds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;App&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;grid&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				{&lt;/span&gt;&lt;span&gt;imageIds&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					let &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.jpg?img-width=1080&amp;amp;img-height=1080`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					let &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; `nft &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;image-container&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;							&amp;lt;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;alt&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;} /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				})}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/medium/v2/resize:fit:1400/1*E8ZdVathmGSrjQn6lz0VYw.gif&quot; alt=&quot;gif of loading&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Keep in mind, each one of these images is 10,000 x 10,000 resolution with over 35MB per file, and thanks to our dedicated gateway we loaded them like it was nothing. All of them dynamically resized to 1080 x 1080, still a decent size and high enough quality for most projects.&lt;/p&gt;
&lt;p&gt;Now you’ve got an idea how IPFS Image Optimization with Pinata’s Dedicated Gateways can help streamline your NFT development, especially for marketplaces and other platforms that need to stream lots of IPFS content. Here are a few more articles that might be helpful as you’re building:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/pinata/how-to-create-an-nft-marketplace-on-flow-with-ipfs-a162a1aeb426&quot;&gt;How To Create an NFT Marketplace on Flow With IPFS&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/pinata/how-to-prevent-nft-trait-sniping-in-your-pfp-project-506f17ff07d6&quot;&gt;How To Prevent NFT Trait Sniping In Your PFP Project&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/pinata/how-to-offset-your-nft-project-carbon-emissions-with-aerial-b5b4b95faba0&quot;&gt;How to Offset Your NFT Project Carbon Emissions with Aerial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And if you haven’t heard, we’ve just released a ton of new features like token gating content and 4k video streaming, putting even more power into the hands of creators. Be sure to join our &lt;a href=&quot;https://discord.gg/pinata&quot;&gt;Discord community&lt;/a&gt; to connect with other creators and see all the amazing projects being built with Pinata. Happy pinning!&lt;/p&gt;</content:encoded></item><item><title>Resurrect the Old Web</title><link>https://stevedylan.dev/posts/resurrect-the-old-web/</link><guid isPermaLink="true">https://stevedylan.dev/posts/resurrect-the-old-web/</guid><description>Let&apos;s go back to when social media was about people</description><pubDate>Tue, 23 Sep 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/other/bear-blog.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recently a local news station in Maine reported a story of some middle schoolers calling their friends with landline telephones. Their parents thought they were too young for cell phones and wanted to hold off on that aspect of reality, so they got an old phone from the 90s, and soon their friends also got phones. It formed schedules of calls they would make to talk to each other, even creating a phone ring of contacts.&lt;/p&gt;
&lt;p&gt;I think I can confidently say that the majority of us aren’t happy with the state of social media. Back in its early days it was fresh and exciting, a fun way to connect with your friends that might be far away, or make new friends online. It was cozy. No ads, no feeds, no endless videos. Instead it was just people, the whole reason you started in the first place. Now it’s just noise and scary addicting and effective algorithms that keep you plugged in for hours on end. We build apps and products to help kill the monster, or perhaps we even delete some social media apps. Many of our friends we used to stay connected with seem so distant, as many of them too are tired and perhaps jumped off socials altogether. Well, what if I told you we could have the old web back?&lt;/p&gt;
&lt;p&gt;In my opinion the answer is honestly pretty simple: blogs and RSS feeds. This was how it was done for years before social media came into the scene. You would find someone’s blog, subscribe to their RSS feed, and anytime a new post came out it would pop up in your feed and you could read it. One important clarification is that when we say “blog” it can be pretty much whatever you want it to be. On my personal website I generally write more of my serious blogs, but on my bear blog I plan to be a bit more casual. It will be a place where I record short thoughts, ideas, musings, or cool things I find on the internet. Just sharing what I would normally share with my friends. That’s what made the web great, and that’s what I want to bring back.&lt;/p&gt;
&lt;p&gt;To do this, I am starting a &lt;a href=&quot;https://bearblog.dev&quot;&gt;bear blog&lt;/a&gt; that will have a dedicated &lt;a href=&quot;https://stevedylandev.bearblog.dev/feeds&quot;&gt;feeds page&lt;/a&gt; that will have all the other blogs I’m subscribing to. The beauty is that you don’t need a dedicated social network to make this work; just click on the links. Use whatever RSS reader you want! You don’t have to use bear blog either, just use whatever blog you want. The key is connection. I want to point to who I follow so that you might follow them too, and hopefully create a page on your own. In some ways it’s bringing back old web rings and simple networking through hyperlinks.&lt;/p&gt;
&lt;p&gt;To kick it off, here’s a few blogs I’m already subscribed to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://polluterofminds.bearblog.dev&quot;&gt;polluterofminds bear blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://sdv.bearblog.dev&quot;&gt;syndicated debatable views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://0xgramajo.xyz&quot;&gt;gramajo’s blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.vrypan.net&quot;&gt;blog.vrypan.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://vitalik.eth.limo&quot;&gt;vitalik.eth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to join but not sure how, check out the video I recorded below:&lt;/p&gt;
&lt;div&gt;&lt;/div&gt;
&lt;p&gt;Best way to keep up with your feeds is to find yourself an RSS reader! There are a lot of options out there (although admittedly a bit old), so just find it on the platform that suits you best. &lt;a href=&quot;https://feeder.co/reader&quot;&gt;Feeder.co&lt;/a&gt; has a pretty generous free plan, and if you’re a dev there’s hundreds of self hosted projects to choose from like &lt;a href=&quot;https://github.com/nkanaev/yarr&quot;&gt;Yarr&lt;/a&gt;. Personally rocking &lt;a href=&quot;https://netnewswire.com&quot;&gt;NetNewsWire&lt;/a&gt; for MacOS and iOS, and loving it so far!&lt;/p&gt;
&lt;p&gt;I have no idea if this will amount to anything or if it’s worthwhile, but I’m gonna give it a shot. The landline phones prove that we don’t have to buy into the social media dopamine machine. We have autonomy, and we have the freedom to choose how we interact with each other. I want to believe we can resurrect the old web, together.&lt;/p&gt;</content:encoded></item><item><title>Returning to Neovim</title><link>https://stevedylan.dev/posts/returning-to-neovim/</link><guid isPermaLink="true">https://stevedylan.dev/posts/returning-to-neovim/</guid><description>Once again coming back to the editor I can&apos;t shake</description><pubDate>Mon, 16 Mar 2026 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/other/return-to-neovim.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of my more popular blog posts was how and why I switched to &lt;a href=&quot;https://stevedylan.dev/posts/leaving-neovim-for-zed/&quot;&gt;Zed from Neovim&lt;/a&gt;. That was almost two years ago, and in that period Zed was my daily driver for programming. Every now and then I would still use Neovim to edit a config or make a quick edit, but outside of that, Zed was where I lived. It performed admirably, with minimal bugs that only irked me from time to time that would eventually be fixed. AI was still relatively minimal and I enjoyed using it. So why go back to Neovim?&lt;/p&gt;
&lt;h2&gt;What Happened&lt;/h2&gt;
&lt;p&gt;The real shift happened about a week ago when Zed updated its terms and policies, including a new age restriction of 18+. The Zed team clarified that this was in regards to their online services and platform used for AI assisted coding, but at least for me, it was still a bit unnerving. A few people had already made forks of Zed through the open source licenses that the editor is under, and I did try them, but it was clear that the Zed experience was not the same. Some stuff didn’t work, extensions had to be installed manually, just overall a horrible experience.&lt;/p&gt;
&lt;p&gt;That was the wake up call. I realized I couldn’t really trust Zed moving forward. I really think the team is awesome and what they’re building is perhaps the best editor alternative to VSCode, but I also understood that they have to make money somehow. When it comes to writing code, the last place I want to find myself in is being held hostage or being forced off the platform. I have to be able to write code productively without my flow being interrupted by the decisions of higher management. Neovim isn’t a perfect drop in replacement in this regard either, but I trust it way more as a community backed and managed project.&lt;/p&gt;
&lt;p&gt;Since switching back to Neovim full time, I’ve honestly had no regrets. I updated my config last year, and having the opportunity to daily drive it has proven how capable it truly is. There was a mental plan to adjust pieces to meet what I might have missed in Zed, but I haven’t had to make any changes yet. With that said I figured it would be a good time to share what my config looks like and how effective it is.&lt;/p&gt;
&lt;h2&gt;The Config&lt;/h2&gt;
&lt;p&gt;There’s generally two ways people end up configuring Neovim. One path is using a distro like &lt;a href=&quot;https://www.lazyvim.org/&quot;&gt;LazyVim&lt;/a&gt;, the other is writing it from scratch. I’ve taken the distro path before and I think it’s great if you have no idea what you want, but eventually you might find yourself wanting to slim things down. If doing a config from scratch feels intimidating, I would highly recommend &lt;a href=&quot;https://www.youtube.com/playlist?list=PLsz00TDipIffreIaUNk64KxTIkQaGguqn&quot;&gt;this series&lt;/a&gt; which goes over all the different aspects of a config. Below is a quick overview of my config structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;nvim&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── lazy-lock.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── plugins&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── ai-vim.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── treesitter.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── mini.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── tmux-navigator.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── colorschemes.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── config&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── options.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── keymaps.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── autocmds.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── core&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── lazy.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        └── lsp.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── lsp&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── gopls.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── solc.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── asm-lsp.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── rust-analyzer.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── astro.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── html.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── json.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── lua_ls.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    └── tsserver.lua&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;└── init.lua&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ll do my best to go over all of the different pieces I have here.&lt;/p&gt;
&lt;h3&gt;Plugin Manager&lt;/h3&gt;
&lt;p&gt;I’ve been using &lt;a href=&quot;https://github.com/folke/lazy.nvim&quot;&gt;lazy.nvim&lt;/a&gt; for years (not to be confused with LazyVim, a distro that uses lazy.nvim), and it’s just solid. Always works, zero issues, and boy it can go &lt;em&gt;fast&lt;/em&gt; (will go over that later). There’s not much else to say due to how much of an industry standard it is. I might give the new native plugin manager coming in Neovim 12 a try, but I’ve already seen some people say it’s not as fast as lazy.nvim, so I’ll be keeping an eye on it for future development.&lt;/p&gt;
&lt;h3&gt;LSP&lt;/h3&gt;
&lt;p&gt;The Language Server Protocol (LSP) provides a standard for different languages to provide feedback in dev workflows. Common example would be writing an incorrect type in Typescript which would cause the compiler to fail. Instead of having to run it, the editor shows some red lines saying something is wrong. Many editors set this up behind the scenes, but that’s not the case for Neovim, and it can be one of the big things people struggle with. In the past I’ve used a few plugin combinations which were always a mess, but I was so excited that native LSP support came to Neovim last year! &lt;a href=&quot;https://youtu.be/IZnhl121yo0&quot;&gt;This video&lt;/a&gt; does a fantastic job walking you through how to set it all up, but its really as simple as creating a dedicated &lt;code&gt;lsp&lt;/code&gt; folder with the different languages, then making a &lt;code&gt;lsp.lua&lt;/code&gt; config file. Here’s an example for Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;return {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	cmd&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;rust-analyzer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	filetypes&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;rust&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	root_markers&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;Cargo.toml&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;Cargo.lock&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&quot;.git&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	settings&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		[&lt;/span&gt;&lt;span&gt;&quot;rust-analyzer&quot;&lt;/span&gt;&lt;span&gt;] = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			cargo&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				allFeatures&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				loadOutDirsFromCheck&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				runBuildScripts&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			-- Add other rust-analyzer specific settings here&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			checkOnSave&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			procMacro&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				enable&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				ignored&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					leptos_macro&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						-- &quot;component&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&quot;server&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	single_file_support&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	log_level&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.lsp.protocol.MessageType.Warning,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells Neovim what files to use the rust-analyzer LSP for, what files might indicate a project, and any other options we may want to add. We follow the same structure for all languages or frameworks that have an LSP. Then inside &lt;code&gt;lsp.lua&lt;/code&gt; we just add the following configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.lsp.&lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;astro&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;gopls&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;lua_ls&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;tsserver&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;rust-analyzer&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;asm-lsp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;solc&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;html&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;json&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  virtual_lines&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  -- virtual_text = true,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  underline&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  update_in_insert&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  severity_sort&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  float&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    border&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;rounded&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    source&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  signs&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    text&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.ERROR] = &lt;/span&gt;&lt;span&gt;&quot;󰅚 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.WARN] = &lt;/span&gt;&lt;span&gt;&quot;󰀪 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.INFO] = &lt;/span&gt;&lt;span&gt;&quot;󰋽 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.HINT] = &lt;/span&gt;&lt;span&gt;&quot;󰌶 &quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    numhl&lt;/span&gt;&lt;span&gt; = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.ERROR] = &lt;/span&gt;&lt;span&gt;&quot;ErrorMsg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      [&lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.severity.WARN] = &lt;/span&gt;&lt;span&gt;&quot;WarningMsg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key is &lt;code&gt;vim.lsp.enable()&lt;/code&gt; where we pass in the names of all our files that have configs. Everything else is just some nicer configuration for looking at diagnostics.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/other/nvim-diagnostics-icons.png&quot; alt=&quot;nvim diagnostics icons&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It’s that simple, and I absolutely love how minimal the experience is. Does require understanding what your LSPs are, where they live, and how to run them, but totally worth it.&lt;/p&gt;
&lt;h3&gt;Plugins&lt;/h3&gt;
&lt;p&gt;You might have noticed that I don’t have that many plugins, but it’s actually a bit deceiving.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ai-vim.lua&lt;/code&gt; - Small inline AI editing plugin which I don’t actually use much, will probably cut it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;treesitter.lua&lt;/code&gt; - Syntax highlighting, pretty standard.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tmux-navigator.lua&lt;/code&gt; - Lets me use &lt;code&gt;ctrl+h/j/k/l&lt;/code&gt; to switch between a Neovim session and another tmux pane.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;colorschemes.lua&lt;/code&gt; - Themes baby, currently on my own called &lt;a href=&quot;https://github.com/stevedylandev/darkmatter-nvim&quot;&gt;Darkmatter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The one I didn’t list here is &lt;a href=&quot;https://github.com/nvim-mini/mini.nvim&quot;&gt;mini.nvim&lt;/a&gt;, which is the true star of this config. mini.nvim is a collection of minimal plugins that are installed and setup through a single config. They’re all simple, functional, and they really help lighten up your config. Here’s a quick run down of some of my favorites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mini.completion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This would normally be handled through something heavier like coc.nvim, but it’s truly awesome to have a simple and lightweight option inside mini.nvim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mini.files&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A minimal file explorer thats a fun mix between oil and netrw.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mini.pick&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick anything. Actually though. In a lot of ways this replaces telescope and lets me fuzzy find files, buffers, you name it!&lt;/p&gt;
&lt;h3&gt;Other Bits&lt;/h3&gt;
&lt;p&gt;There are some smaller quality of life pieces I have that don’t really fit into any specific category, so here’s a few my favorites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VimEnter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I got this one from &lt;a href=&quot;https://www.youtube.com/@adibhanna&quot;&gt;Adib Hanna&lt;/a&gt; a long time ago. Instead of showing a start screen when I open Neovim, instead I use &lt;code&gt;mini.pick&lt;/code&gt; to fuzzy find all files within the current directory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.api.&lt;/span&gt;&lt;span&gt;nvim_create_autocmd&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;VimEnter&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  callback&lt;/span&gt;&lt;span&gt; = function()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if &lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.fn.&lt;/span&gt;&lt;span&gt;argv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) == &lt;/span&gt;&lt;span&gt;&quot;&quot; &lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      vim&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;defer_fn&lt;/span&gt;&lt;span&gt;(function()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;mini.pick&quot;&lt;/span&gt;&lt;span&gt;).builtin.&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      end, &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-- Wait 100ms&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    end&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  end,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Buffer Management&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I don’t have tabs setup in Neovim as I started to realize I don’t really need them. I can use my keyboard shortcuts to move between buffers horizontally,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;-- Navigate buffers&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;S-l&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:bnext&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;S-h&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:bprevious&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or filter through them with &lt;code&gt;mini.pick&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;o&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick buffers&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I can just close them with the following keymap:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;c&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:bd&amp;lt;cr&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Diagnostics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nothing too fancy here but it is nice how versatile the experience can be. I can either use this keymap to do a hover diagnostic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;gl&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;vim&lt;/span&gt;&lt;span&gt;.diagnostic.open_float, &lt;/span&gt;&lt;span&gt;&quot;Open Diagnostic Float&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or I can view all diagnostics for the project with mini.pick:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;d&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick diagnostic&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Finding Stuff&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most important things an editor needs is an easy way to find anything, and I’m quite pleased with what I have setup here. To start, if I wanted to search the entire codebase for a given string, I can use mini.pick with &lt;code&gt;live_grep&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;/&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick grep_live&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same goes for buffers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;o&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick buffers&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For files, I can either do a fuzzy find like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;f&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick files&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or I can use the file browser with mini.files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;e&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;lua MiniFiles.open()&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Last but not least, you can also use Pick to get all the help manuals for Neovim or the plugins installed.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;n&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;leader&amp;gt;hh&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;cmd&amp;gt;Pick help&amp;lt;CR&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;opts&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Speed&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;025.359  000.037: BufEnter autocommands&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;025.363  000.004: editing files in windows&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;025.381  000.019: --- NVIM STARTED ---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration is incredibly fast. Startup times averages around ~25ms. A more detailed report can be found below.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sipp.stevedylan.dev/s/dJfiLGUx9u&quot;&gt;&lt;code&gt;.nvim-startup.txt&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Source&lt;/h3&gt;
&lt;p&gt;By all means feel free to checkout the whole config yourself in my &lt;a href=&quot;https://github.com/stevedylandev/dotfiles&quot;&gt;dotfiles repo&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;I really do appreciate the time I had with Zed and it is a solid editor; I don’t fault anyone who uses it. Personally I’ve just been through this kind of thing too many times, where I thoroughly enjoy and depend on a tool, just for it to go sideways and absolutely cripple my productivity. The Arc Browser was a great example of this. I would much rather jump ship early, figure out a new workflow that is dependable, and stick with it. That’s exactly what I’ve done with Neovim, and it’s good to be back.&lt;/p&gt;</content:encoded></item><item><title>Standard.site: the Publishing Gateway</title><link>https://stevedylan.dev/posts/standard-site-the-publishing-gateway/</link><guid isPermaLink="true">https://stevedylan.dev/posts/standard-site-the-publishing-gateway/</guid><description>Another deep exploration into ATProto and implementing lexicons</description><pubDate>Sun, 11 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/standard-site.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;/posts/using-atproto-for-posse/&quot;&gt;last post&lt;/a&gt; I discussed how I was moving my micro blogging practice to my personal website. I was previously using &lt;a href=&quot;https://bearblog.dev&quot;&gt;Bear Blog&lt;/a&gt; (highly recommend btw) for small life updates that didn’t fit into full blog posts. I decided to use &lt;a href=&quot;https://atproto.org&quot;&gt;ATProto&lt;/a&gt; as the means to make this happen, and ended up with a pretty good workflow that let me post through a ATProto client (Bluesky in this case) and the posts would end up on my &lt;a href=&quot;/now&quot;&gt;/now page&lt;/a&gt; as a feed. While this setup was decent, it still had some major flaws.&lt;/p&gt;
&lt;p&gt;For one, the post size cap for Bluesky feed posts. It worked for the most part, but there were many times I would want to make slightly longer posts or updates. I didn’t like not having this freedom. Another issue was using the Bluesky app itself. I made so many of these &lt;a href=&quot;/posts/resurrect-the-old-web/&quot;&gt;life changes&lt;/a&gt; to remove social media from my life, not add it back in. There was also the fact that these posts would be Bluesky posts, showing up in feeds and pointing back to the Bluesky origin. This was my biggest issue as it prevented me from truly adopting &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is when I discovered &lt;a href=&quot;https://standard.site&quot;&gt;Standard.site&lt;/a&gt;, a new set of &lt;a href=&quot;https://atproto.com/guides/lexicon&quot;&gt;lexicons&lt;/a&gt; designed especially for publishing. The schema had exactly what I was looking for: canonical URLs that point to my site. At the time I knew nothing about lexicons on ATProto or how they worked, but it sounded like the Standard.site lexicon for publishing could be a huge step towards aggregating original publishing platforms in a totally new way. I had to find out. What followed was a journey that significantly boosted my knowledge of ATProto and how Standard.site could be a new gateway for publishing content on the web.&lt;/p&gt;
&lt;p&gt;Before we go any further, I want to make it crystal clear that I’m not an expert at ATProto. I started digging into this stuff maybe a week or two ago. I’m sharing this story in hopes that others who are interested in a similar integration but are having a hard time finding the answers will get some here. If there are better ways to do some of this stuff, &lt;a href=&quot;mailto:contact@stevedylan.dev&quot;&gt;let me know&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;From the very little I knew, it was clear that I would need to make these posts happen through my own client implementation. Since ATProto / PDS’ had OAuth support I thought this would be a great way to post from my own website. When using Bear Blog I loved the ability to post from any browser, and doing the same thing with ATProto on my site was attractive. Overall implementing OAuth took a fair bit of code, but was relatively seamless. My website uses Astro so I added a few React components to handle login, logout, and making a post. For the backend I used a Cloudflare Worker running Hono, as well as a KV instance for saving sessions. Signing into my PDS through my website was an exciting experience, and even more so when making a post worked like a charm!&lt;/p&gt;
&lt;p&gt;The only problem was that I was still posting to Bluesky, so now it was time to dig into Standard.site and lexicons. People kept talking about lexicons but I was pretty clueless to how they worked exactly. Once I looked closer at the code I was using for posts and what was dictated by Standard.site, it ended up being a lot simpler than I thought. Take this payload for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;documentRecord&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	repo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;did&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	collection&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	record&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		$type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		site&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span&gt;`at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.publication/3mbykzswhqc2x`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		...(&lt;/span&gt;&lt;span&gt;normalizedPath&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;normalizedPath&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;() }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;markdownContent&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		textContent&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		publishedAt&lt;/span&gt;&lt;span&gt;: new &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key for using a lexicon is just designating the &lt;code&gt;collection&lt;/code&gt; and the &lt;code&gt;$type&lt;/code&gt;. That’s it. In order to be valid you just have to follow the schema dictated by the lexicon. Standard.site has two primary lexicons: &lt;code&gt;document&lt;/code&gt; for the actual content, and &lt;code&gt;publication&lt;/code&gt; for the source of the document. This made a lot more sense when I started using &lt;a href=&quot;https://pdsls.dev/at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.document/3mc4myqaca22x&quot;&gt;pdsls.dev&lt;/a&gt; to examine the posts I was making.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/pdsls-example.png&quot; alt=&quot;pdsexample&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The presentation is brilliant. There is a clear order and hierarchy to how ATProto and data works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;andromeda.social&lt;/code&gt; - This is my PDS, the core where my data is stored&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stevedylan.dev (did:plc:ia2zdnhjaokf5lazhxrmj6eu)&lt;/code&gt; - My “account” or “DID” that is hosted on my PDS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;site.standard.document&lt;/code&gt; - The collection where my post is, kinda like a folder&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3mc4myqaca22x&lt;/code&gt; - The record itself that has all the data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These pieces make up the AT URI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;at://did:plc:ia2zdnhjaokf5lazhxrmj6eu/site.standard.document/3mc4myqaca22x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seeing lexicons as folders clicked for me. Not just folders, but standards that follow a schema. In order for my posts to follow the standard I simply needed to create an initial &lt;code&gt;publication&lt;/code&gt; record with my website info and then reference it in each &lt;code&gt;document&lt;/code&gt;. Once you have that setup you can actually click on the “Info” tab in pdsls.dev and validate the info to make sure it’s correct. You can also add other fields if you want to as long as the original and required fields are met, making it very flexible.&lt;/p&gt;
&lt;p&gt;With the lexicons in place I finally had what I wanted: posts that aren’t attached to Bluesky, stored on my PDS, and could be indexed and aggregated due to using Standard.site. The final piece I wanted to add was comments. It felt like the logical conclusion, and also would be weird to use ATProto for this concept yet only include “reply via email” at the bottom. The big decision was which lexicon to use. Since I was avoiding using Bluesky lexicons, a simple “reply” wouldn’t really work. Eventually I decided to build a lexicon on top of Standard.site: &lt;code&gt;site.standard.document.comment&lt;/code&gt;. It’s not technically a valid schema, but I figured it would work for now.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;commentRecord&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	repo&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;did&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	collection&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document.comment&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	record&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		$type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;site.standard.document.comment&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		parent&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			uri&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parentUri&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			cid&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;parentCid&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		root&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			uri&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parentUri&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			cid&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;parentCid&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		content&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;content&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		author&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			did&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;did&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			handle&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authorHandle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			...(&lt;/span&gt;&lt;span&gt;authorDisplayName&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;displayName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authorDisplayName&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			...(&lt;/span&gt;&lt;span&gt;authorAvatar&lt;/span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;avatar&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authorAvatar&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		createdAt&lt;/span&gt;&lt;span&gt;: new &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;toISOString&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In no time at all I had an OAuth flow for posting and creating comments for my update documents. On my site I had a component that would request the comments for a given document, but I quickly faced an issue. When I replied to a post from another account, it didn’t show up. I soon realized this was due to the other account being on the &lt;code&gt;bsky.social&lt;/code&gt; PDS, not my self hosted instance. This would be a problem because there might be loads of people who have their own PDS; I can’t limit it to just one. How do I get these lexicons across thousands of hosted instances?&lt;/p&gt;
&lt;p&gt;This was quite a wakeup call. How would Standard.site promise a future where you could aggregate any content that follows the standard? Thankfully there is an answer, and &lt;code&gt;huwcampbell.com&lt;/code&gt; &lt;a href=&quot;https://bsky.app/profile/huwcampbell.com/post/3mc2z6rbmv22d&quot;&gt;helped me out&lt;/a&gt;. It turns out that &lt;a href=&quot;https://atproto.com/guides/glossary#relay&quot;&gt;ATProto Relays&lt;/a&gt; help gather data from any PDS that makes a request to be scraped, and most PDS implementations make this request by default to &lt;code&gt;bsky.network&lt;/code&gt;. Then you can use a indexing tool called &lt;a href=&quot;https://github.com/bluesky-social/indigo/tree/main/cmd/tap&quot;&gt;Tap&lt;/a&gt; to not only listen but backfill data for any given collection.&lt;/p&gt;
&lt;p&gt;We got a step closer to making comments happen. The one small issue I faced was the fact that Tap would only store minimal metadata in its DB regarding the records, primarily the URI. While I could fetch the URI and discover which post the comment belonged to, I didn’t like that data flow. Instead I just forked Tap, made some minimal changes, and made it also record the document URI that the comment was tied to. Then I added a simple API endpoint that could be used to fetch this data from the DB. Took a while to hook it all up, but with my site’s API and my tap instance running, we had comments working.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/comments-example.png&quot; alt=&quot;comments example&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At this point I started to question the value of all of this given the amount of work it took to get what I wanted, but in the end we had something special. Within a week, my site had actually turned ATProto into a CMS. I could make posts, update them, or delete them, and all the while these updates are broadcasted to a network that anyone could index. It was like an RSS feed that because it met a standard could be aggregated with a single tool. When I sign into my site using my own PDS and make posts, it’s totally independent. I have total control over everything. The hype of Standard.site was starting to click.&lt;/p&gt;
&lt;p&gt;Of course this idea does face its own special challenges. Standard.site lexicons will only work if they truly become the standard (clever domain by the way). If there are other competing standards that start to also gain traction then you will have issues getting everyone on the same page. With that said I think Standard.site stands a good chance: it’s well thought out, simple, and people already love it. Even if more competition arises, people will generally go with whatever is the most popular and give them the most distribution. Even still, there’s nothing stopping anyone from taking the same piece of content and creating multiple records to meet multiple standards to get as much attention as possible.&lt;/p&gt;
&lt;p&gt;The other challenge I can see is the Bluesky relay. You can run other relays and have your PDS make a request to them for scraping, but the reality is &lt;code&gt;bluesky.network&lt;/code&gt; is the most popular and comes predefined if you run the main PDS implementation. With that said I think it’s still early and not a huge concern right now. In truth we have a lot to thank from Bluesky with their work on ATProto and the promises it brings. If you’re interested in any of the code mentioned in this post, it’s all open source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stevedylandev/stevedylan.dev&quot;&gt;Personal Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stevedylandev/indigo/tree/main/cmd/tap&quot;&gt;Tap Fork&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also plan to include a link to this post as an update on my &lt;a href=&quot;/now&quot;&gt;/now&lt;/a&gt; page if you want to check out the implementation and leave a comment! While my main blog doesn’t have comments I did write a script that will now create documents on ATProto so they should be indexed as well.&lt;/p&gt;
&lt;p&gt;Wrapping up, this experience has been a great culmination of everything I’ve been prioritizing with the web, that being content publishing and sharing that content through open channels like RSS. Standard.site opens up a whole new world of possibilities for what this could look like in the near future, and takes us a step closer to a truly open web that I long to see.&lt;/p&gt;</content:encoded></item><item><title>The Meaning of Life</title><link>https://stevedylan.dev/posts/the-meaning-of-life/</link><guid isPermaLink="true">https://stevedylan.dev/posts/the-meaning-of-life/</guid><description>42? Sorta, but not exactly</description><pubDate>Fri, 15 Aug 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/42.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Even if you haven’t read the book or seen the movie, you are likely familiar with the famous yet humorous answer to the meaning of life: 42. Just a number. The original author didn’t intend any deeper meaning and stresses that it means nothing. Ironically I think like everything in life, it holds an ounce of truth beyond what we see in it.&lt;/p&gt;
&lt;p&gt;People have searched far and wide to answer the question of why we’re here, or more personally what their purpose in life is. It’s something we either don’t like to talk about because we feel insecure about ourselves, or it’s something we boast about with great confidence due to our abilities. In my opinion the truth is neither, in that the meaning of your life is not within your own skills and abilities.&lt;/p&gt;
&lt;p&gt;In a 2020 Disney movie called Soul, the main character is convinced their life purpose is to play jazz piano. Through a series of events he slowly realizes that while jazz is his “spark” and perhaps his favorite part of being alive, it’s not his purpose. We all eventually die one day and generally history will not remember us at all, not even our distant descendants. We get the idea that our life needs some grand purpose because we see others in history being recognized for their achievements.. What we miss is the context. We only see from afar what their life looked like and then project that definition of purpose upon ourselves.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong, I think you should still have a purpose driven life. It’s good to have a desire and passion to do great things in the world; it’s in our nature. What I am saying is don’t confuse it with the meaning of your life. In an instant, your ability to chase goals or pursuits can be taken away. For those who have experienced it, the feeling of having no purpose is real.&lt;/p&gt;
&lt;p&gt;So what is the meaning of life? Well, I would be foolish to think I know the true answer, but I will tell you what it means for me now.&lt;/p&gt;
&lt;p&gt;The meaning of life, is to live.&lt;/p&gt;
&lt;p&gt;A pivotal scene in Soul is when he looks back through all the things he experienced in his life that were not playing jazz piano, and how they gave him life:&lt;/p&gt;
&lt;p&gt;Riding a bike in the summer&lt;/p&gt;
&lt;p&gt;Listening to music with his father&lt;/p&gt;
&lt;p&gt;Eating the best slice of pizza&lt;/p&gt;
&lt;p&gt;Teaching a kid to play the drums&lt;/p&gt;
&lt;p&gt;Eating a humble yet amazing slice of pecan pie&lt;/p&gt;
&lt;p&gt;Feet in the sand on a sunset beach&lt;/p&gt;
&lt;p&gt;Sure, I have a passion for trying to build a more safe and more free version of the internet, but I also love my kids, my wife, the sound of a stream in the woods, the taste of Italian wine, the feeling of a cool breeze, and the sight of a cloudy hill in a far away country.&lt;/p&gt;
&lt;p&gt;Life is so much more than what you might be passionate about, and if you’re not careful, you’ll get lost. So next time you find yourself taking a beating over finding meaning and purpose in life, think of the simple things that make you alive. Don’t lose site of what’s right in front of you.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are these two young fish swimming along and they happen to meet an older fish swimming the other way, who nods at them and says “Morning, boys. How’s the water?” And the two young fish swim on for a bit, and then eventually one of them looks over at the other and goes “What the hell is water?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;42&lt;/p&gt;</content:encoded></item><item><title>My Developer Journey</title><link>https://stevedylan.dev/posts/my-developer-journey/</link><guid isPermaLink="true">https://stevedylan.dev/posts/my-developer-journey/</guid><description>How I learned web development and transitioned into the tech and Web3 space</description><pubDate>Tue, 28 Feb 2023 05:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;It Started with Clickbait&lt;/h2&gt;
&lt;p&gt;It was late September in 2020, our first son was just born and I was waiting in the car while my wife took him to the hospital for a checkup (becuase of the Covid-19 restrictions at the time only one of us could go in). I had spent the majority of my life doing multiple jobs in various fields. My college degree was in liberal arts so of course it only did so much good in the professional world, so I started small by working in the footwear department at Bass Pro Shops. From there I slowly worked up the chain and eventually ran the archery department.&lt;/p&gt;
&lt;p&gt;It was a fair job for three to four years but eventually the wear of retail grew on me and I got tired of working late hours. That’s when I transitioned into banking as a teller, as I heard it was a good out from retail. After working as a teller for about a year I moved to the back office customer service position. There I worked 8.5 hours a day taking phone calls and helping customers with online banking, debit card problems, or just checking a balance. It was a pretty nice gig since I got to help people and work with some pieces of tech, and later down the road I eventually helped managed the department. That position also helped me learn how to be productive, type faster, and operate a keyboard only interface quickly.&lt;/p&gt;
&lt;p&gt;As you would expect talking to people all day every day took a toll on my mental health after four years, and that’s about when my son was born. I had about three weeks of vacation and sick time off to help my wife before going back to work, and yeah I really didn’t want to go back after taking a good solid break. I sat in that hospital parking lot, scrolling through YouTube, when I came across a video. This video to be precise:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://youtu.be/nupkQD_Mnhg&quot;&gt;How I Learned to Code - And Got a Job in Less Than 3 Months&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;The Grind&lt;/h2&gt;
&lt;p&gt;Of course the title is clickbait and I was hooked. I didn’t learn to code in three months, but I did get started. I bought &lt;a href=&quot;https://www.amazon.com/Head-First-HTML-CSS-Standards-Based/dp/0596159900&quot;&gt;Head First: HTML with CSS and XHTML&lt;/a&gt; off eBay for $10 and blew through it in a weekend; I just couldn’t stop consuming knowledge about web development. This wasn’t programming just yet, but the magic was there because I watched text on a screen transform into something visual. I’ve had a creative background with music and photography, and the ability to create something with lines of code was fascinating. The next book was &lt;a href=&quot;https://www.amazon.com/Head-First-Java-Brain-Learners/dp/0596004656?keywords=head+first+java&amp;amp;qid=1677605428&amp;amp;sr=8-5&quot;&gt;Head First Java&lt;/a&gt; which did not click with me at all. I barely grasped the basic programming principles. I couldn’t understand how they connected with web sites and made things work, and that was likely due to using 5-10 year old books. I switched up and went to YouTube again and found some web development roadmap videos which gave me a rough guideline of what I needed to learn.&lt;/p&gt;
&lt;p&gt;The next year was spent grinding through some coursed by &lt;a href=&quot;https://developedbyed.com/&quot;&gt;Ed&lt;/a&gt;, starting with basic HTML, CSS, and Javascript, and eventually React. That was a rough period, because I was still working at the bank full time. I was helping take care of a difficult newborn baby, and learning something completely new. I would wake up at 5am most days, completely exhausted yet pushing through concept after concept and project after project. After work I would come home, help around the house, and later in the evening I would keep coding. While it was a lot of work, it was totally worth it.&lt;/p&gt;
&lt;h2&gt;The First Smart Contract&lt;/h2&gt;
&lt;p&gt;After about a year I was getting to a point where I was creating projects with the goal of having a portfolio I could use for applying to jobs. That’s when I stumbled upon Web3. I can’t remember how, but I found a project on &lt;a href=&quot;https://buildspace.so&quot;&gt;Buildspace&lt;/a&gt; that introduced me to blockchain, Ethereum, and smart contracts. I’ll never forget the feeling of deploying my first smart contract and interacting with it from a front end website. This was it; I knew from there I wanted to work in this new internet and make it better. I built countless Web3 projects, some of them included minting NFTs, which is where I stumbled upon &lt;a href=&quot;https://pinata.cloud&quot;&gt;Pinata&lt;/a&gt;. When I started to look for jobs I saw that Pinata was hiring a community manager, and even though I was looking to be a developer, I was fond of the idea that I could use some of my other skills like support and customer service in the industry. I applied for the job, and within a month I was hired!&lt;/p&gt;
&lt;p&gt;As the community manager then and head of community now, I’ve had another year of being able to learn technical products and help people use them and understand them. I still get to write code that demonstrates what Pinata can do and snippets to help make it easier to use, which I absolutely love. Pinata took a chance on some guy who used to fetch shoes and take phone calls, and because of that I’ve been able to relocate to a better city where I can raise my family for which I am incredibly grateful. Sitting down to work each day is exciting because I know that I can learn just about anything and I can teach it to others.&lt;/p&gt;
&lt;p&gt;I’m starting this blog to document more of what I’m learning in the Web3 and tech space in hopes that others find it beneficial. If you get anything from this post, let it be these words from the beloved Ratatouille that can apply to just about anything in life:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the past, I have made no secret of my disdain for Chef Gusteau’s famous motto, ‘Anyone can cook.’ But I realize, only now do I truly understand what he meant.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Not everyone can become a great artist; but a great artist &lt;strong&gt;can&lt;/strong&gt; come from &lt;strong&gt;anywhere&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>The Power of Dedicated Gateways</title><link>https://stevedylan.dev/posts/the-power-of-dedicated-gateways/</link><guid isPermaLink="true">https://stevedylan.dev/posts/the-power-of-dedicated-gateways/</guid><description>Dedicated Gateways. What they are, why they&apos;re essential, and how they can revolutionize a creator&apos;s next project.</description><pubDate>Thu, 10 Feb 2022 05:00:00 GMT</pubDate><content:encoded>&lt;a href=&quot;https://www.pinata.cloud/blog/the-power-of-dedicated-gateways&quot; target=&quot;_blank&quot;&gt; &lt;img src=&quot;/_astro/pinnie.D249WNQr_Z1XiysU.webp&quot; alt=&quot;Link to Pinata&quot; loading=&quot;lazy&quot; width=&quot;30&quot; height=&quot;30&quot; /&gt;
Read this post on Pinata &lt;/a&gt;
&lt;h2&gt;What are IPFS Gateways?&lt;/h2&gt;
&lt;p&gt;f you’re in the business of creating NFTs then you are probably familiar with the InterPlanetary File System also known as IPFS. It’s an incredibly powerful protocol that allows creators to host content too large for blockchains on a decentralized peer-to-peer network, leveraging cryptography to ensure content is immutable. This is ideal for NFT projects that want to decentralize their NFT media and make sure it does not change over time. IPFS also allows creators to take ownership of their content and how they share it. What most people don’t understand is how IPFS and HTTP communicate to each other.&lt;/p&gt;
&lt;p&gt;For instance, I have a cool html page that uses 3D libraries and creates a spinning cube. I pinned it to IPFS using Pinata which gave me a content identifier: &lt;code&gt;QmTz8mgtvkf8fG8es5i6vr4LX7dd9vnk1XVtB6ScVuCepr&lt;/code&gt;. A content identifier, or CID for short, is how we can reference content on IPFS. The direct link to that file via IPFS is&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;ipfs://QmTz8mgtvkf8fG8es5i6vr4LX7dd9vnk1XVtB6ScVuCepr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you paste that link into your browser, chances are you are not going to pull anything up or you’ll get a random google search. However, if you have an IPFS node running or you’re using a browser with built-in native IPFS support, then you would actually see something. Why is this?&lt;/p&gt;
&lt;p&gt;n order to see content on the IPFS protocol, you have to participate. By running a local IPFS node you can be part of the network, receiving and sending blocks of data that are hosted on the network. In turn you can view and pin your own files, too.&lt;/p&gt;
&lt;p&gt;This is different from HTTP, which is what most of the internet is built on. An HTTP request is when your computer asks for “&lt;a href=&quot;https://google.com%E2%80%9D&quot;&gt;https://google.com”&lt;/a&gt; and then a server returns the HTML file with the info. This is cool and all, but how is IPFS useful for NFTs since all of the marketplaces and wallets use HTTP?&lt;/p&gt;
&lt;p&gt;The answer is gateways.&lt;/p&gt;
&lt;p&gt;Gateways are exactly what they sound like; a gateway from the IPFS protocol to the HTTP protocol. They allow us to serve content from IPFS into regular https websites that we use everyday. From our example earlier, try pasting this link into your browser:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://pinnieblog.mypinata.cloud/ipfs/QmTz8mgtvkf8fG8es5i6vr4LX7dd9vnk1XVtB6ScVuCepr/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You got the cube right? I took the CID that was already hosted on IPFS and then fed it through a Dedicated Gateway to see the file!&lt;/p&gt;
&lt;p&gt;Notice that I said “Dedicated Gateway.” There are two types of gateways, private (dedicated) and public. Public gateways are convenient since they are available to everyone, and even built into Pinata’s file manager so you can see your content without an IPFS node. However, it’s important to note that IPFS is a public network, which means that it can be viewed or used by anyone. This can become a problem because gateways are still managed on traditional servers. Too much traffic without the right infrastructure could cause a failure, so most public gateways will not serve too much content at once by utilizing rate limits. Public gateways are also not very fast and can take a while to load content. These public gateways can be a good service for testing IPFS content, but not ideal for serving large amounts of data.&lt;/p&gt;
&lt;p&gt;This is where Dedicated Gateways step in! At Pinata, we want to provide blazing fast delivery of your content, and so our engineering team has developed an infrastructure which allows us to do exactly that.&lt;/p&gt;
&lt;h2&gt;Dedicated Gateways&lt;/h2&gt;
&lt;p&gt;Since Pinata released this feature, it’s been amazing to see what people have done with it. One of the main applications we’ve seen of dedicated gateways is NFT marketplaces. Dedicated Gateways can be set as open or restricted. This means you can control whether just your pinned content on your Pinata account are accessible through the gateway, or if any content on the entire IPFS network is accessible through the gateway. NFT marketplaces have the challenge of fetching all the metadata associated with the NFT and then fetching the NFT’s media itself. Once you get the metadata, most of the media are IPFS CIDs, and as mentioned before, you need a gateway to display those over HTTP. Dedicated Gateways make that so easy and make new marketplaces possible.&lt;/p&gt;
&lt;p&gt;NFT marketplaces are just the beginning, just as NFTs can be the beginning of a much larger project. Bored Ape Yacht Club, a Pinata customer, is a great example of this. As many people know, they have taken an NFT project and turned it into a multi-million dollar universe. Projects can take their user base into a world of gaming, staking, earning tokens, and even virtual reality. This allows an NFT project to transform into a brand identity. Brands wanting to utilize NFTs for their metaverses will leverage IPFS, and of course Dedicated Gateways allow these brands to deliver IPFS content quickly and reliably to their platforms.&lt;/p&gt;
&lt;p&gt;Dedicated Gateways will be important to the future of blockchain gaming, as well. There are already numerous NFT games that feature on-chain items or content, such as Axie Infinity. These are real NFTs with data that needs to be preserved through IPFS and served through a Dedicated Gateway.&lt;/p&gt;
&lt;p&gt;At Pinata we can envision a world beyond these metaverses. We would love to see IPFS and Dedicated Gateways as a way for any type of creator to make a valuable experience. Lots of older social media platforms are still valid as a way to be discovered, but the newer web3 technologies will help creators take their content from being discovered to being truly valued. With a Dedicated Gateway, they can control their own content and display it on their own platforms. Just think about most streaming platforms and how much money they give to creators; it’s little to nothing. However, with the exposure from those platforms, a creator can build a fan base. Then with Pinata, creators can build the platform where their content is viewed by their fans and directly paid for there. Now that’s true value and creative control.&lt;/p&gt;
&lt;p&gt;The world of crypto, NFTs, and IPFS is still developing at a rapid rate. It’s exciting to see how we are just scratching at the surface of what web3 will bring us, and it’s important to visualize what this will look like for everyday people.&lt;/p&gt;
&lt;p&gt;Dedicated gateways are exclusively available on Pinata for Picnic plans and up. Get your own dedicated gateway set up today!&lt;/p&gt;</content:encoded></item><item><title>Turning Solidity NatSpec into Interactive Markdown UI</title><link>https://stevedylan.dev/posts/turning-natspec-into-markdown-ui/</link><guid isPermaLink="true">https://stevedylan.dev/posts/turning-natspec-into-markdown-ui/</guid><description>An exploration on how NatSpec could be used to not only maintain context but provide user interfaces</description><pubDate>Sun, 31 Aug 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/natspec-contract.png&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of the most common problems encountered when building decentralized applications is the disconnect between the smart contract and the client. A normal flow might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;solidity -&amp;gt; byte code &amp;amp; abi -&amp;gt; EVM &amp;lt;- byte code &amp;lt;- abi &amp;lt;- client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to the ABI (Application Binary Interface) that is generated at compile time we have instructions we can use in clients to interact with smart contracts. It’s been an essential piece for years as we’ve built apps that interact with smart contracts. More recently at OpenZeppelin we released an open source tool called the &lt;a href=&quot;https://builder.openzeppelin.com&quot;&gt;Contracts UI Builder&lt;/a&gt; which makes it even easier to build UI forms for contracts. Despite how useful ABI has been, it does miss one important piece: context. An ABI field might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;function&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;setNumber&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;inputs&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;newNumber&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;uint256&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;internalType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;uint256&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;outputs&quot;&lt;/span&gt;&lt;span&gt;: [],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;stateMutability&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;nonpayable&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All we know about this function is it probably sets a new number, but why? What is the number for? We might be able to answer these questions by looking at the source code of the contract itself, but there are many cases where we have no idea what a paramter is used for. This is something that &lt;a href=&quot;https://farcaster.xyz/seb/0xf5694e6e&quot;&gt;Seb brought up&lt;/a&gt; on Farcaster this weekend, stating “There should be some sort of universal markup language for every smart contract (that isn’t an ABI) that allows anyone to easily interact onchain.”&lt;/p&gt;
&lt;p&gt;This got me wondering if the NatSpec could be used to help solve this problem. If you’re not familiar with it, the &lt;a href=&quot;https://docs.soliditylang.org/en/latest/natspec-format.html&quot;&gt;NatSpec&lt;/a&gt; works a lot like JSDoc where the developer can leave comments in a particular format that can be used by the compiler to create documentation or even SDKs and CLIs. It’s been in Solidity for years and has actually been used by OpenZeppelin’s documentation to generate API references. While most of the tags handle things like parameters or returns, the &lt;code&gt;@notice&lt;/code&gt; tag can be used as a general description and be filled with whatever we want to write, so why not markdown? It doesn’t stop there though. What if we could build entire UIs out of the NatSpec? Thanks to a new library / proposed standard called &lt;a href=&quot;https://markdown-ui.com/&quot;&gt;Markdown UI&lt;/a&gt; I was able to build a &lt;a href=&quot;https://natspec-ui.orbiter.website&quot;&gt;MVP&lt;/a&gt; of this idea, and in this post I’ll show you how it works!&lt;/p&gt;
&lt;p&gt;The first thing you need to do is write up the markdown as NatSpec in the smart contract.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// SPDX-License-Identifier: MIT&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;pragma &lt;/span&gt;&lt;span&gt;solidity&lt;/span&gt;&lt;span&gt; ^0.8.20&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/// &lt;/span&gt;&lt;span&gt;@title&lt;/span&gt;&lt;span&gt; Counter&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/// &lt;/span&gt;&lt;span&gt;@notice&lt;/span&gt;&lt;span&gt; A simple counter contract that allows incrementing and setting a number&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/// &lt;/span&gt;&lt;span&gt;@dev&lt;/span&gt;&lt;span&gt; This contract maintains a single uint256 state variable that can be modified&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;contract&lt;/span&gt;&lt;span&gt; Counter&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@notice&lt;/span&gt;&lt;span&gt; The current counter value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@dev&lt;/span&gt;&lt;span&gt; Public state variable automatically generates a getter function&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    uint256&lt;/span&gt;&lt;span&gt; public number;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@notice&lt;/span&gt;&lt;span&gt; Sets the counter to a specific value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@dev&lt;/span&gt;&lt;span&gt; Updates the number state variable to the provided value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@param&lt;/span&gt;&lt;span&gt; newNumber&lt;/span&gt;&lt;span&gt; The new value to set the counter to \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ```markdown-ui-widget \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// { &quot;type&quot;: &quot;form&quot;, &quot;id&quot;: &quot;setNumber&quot;, &quot;submitLabel&quot;: &quot;Set Number&quot;, &quot;fields&quot;: [{ &quot;type&quot;: &quot;text-input&quot;, &quot;id&quot;: &quot;newValue&quot;, &quot;label&quot;: &quot;New Counter Value&quot;, &quot;placeholder&quot;: &quot;Enter number&quot;, &quot;default&quot;: &quot;42&quot; }] } \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ``` \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function &lt;/span&gt;&lt;span&gt;setNumber&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;uint256&lt;/span&gt;&lt;span&gt; newNumber&lt;/span&gt;&lt;span&gt;) public {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        number &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newNumber;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@notice&lt;/span&gt;&lt;span&gt; Increments the counter by 1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// &lt;/span&gt;&lt;span&gt;@dev&lt;/span&gt;&lt;span&gt; Increases the number state variable by 1 using the increment operator \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ```markdown-ui-widget \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// { &quot;type&quot;: &quot;form&quot;, &quot;id&quot;: &quot;increment&quot;, &quot;submitLabel&quot;: &quot;Increment&quot;, &quot;fields&quot;: [] } \n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /// ```\n&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    function &lt;/span&gt;&lt;span&gt;increment&lt;/span&gt;&lt;span&gt;() public {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        number&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might have noticed our one small twist: the Markdown UI component.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;`markdown-ui-widget&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{ &quot;type&quot;: &quot;form&quot;, &quot;id&quot;: &quot;increment&quot;, &quot;submitLabel&quot;: &quot;Increment&quot;, &quot;fields&quot;: [] }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what we can use in our front end to build interactive components along side the markdown describing how it works! When we compile this contract it’s going to include a json file with out generated &lt;code&gt;userdoc&lt;/code&gt; and &lt;code&gt;devdoc&lt;/code&gt;. In order to make it easier to share these files along with the ABI, we can verify the contract with &lt;a href=&quot;https://sourcify.dev/&quot;&gt;Sourcify&lt;/a&gt; which will store our contract metadata for anyone to fetch via an API. That API response looks something like this when we use the query &lt;code&gt;?fields=devdoc&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;devdoc&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;kind&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dev&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Counter&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;This contract maintains a single uint256 state variable that can be modified&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;methods&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;increment()&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Increases the number state variable by 1 using the increment operator &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n ```markdown-ui-widget &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n { &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;form&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;increment&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;submitLabel&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Increment&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: [] } &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n ```&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;setNumber(uint256)&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;params&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          &quot;newNumber&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;The new value to set the counter to &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n  ```markdown-ui-widget &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n { &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;form&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;setNumber&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;submitLabel&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Set Number&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: [{ &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;text-input&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;newValue&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;New Counter Value&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;placeholder&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;Enter number&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;42&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt; }] } &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n ``` &lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;n&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Updates the number state variable to the provided value&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;stateVariables&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &quot;number&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &quot;details&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Public state variable automatically generates a getter function&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;matchId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;8931344&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;creationMatch&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;exact_match&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;runtimeMatch&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;exact_match&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;verifiedAt&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2025-08-31T16:03:47Z&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;match&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;exact_match&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;chainId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;11155111&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;address&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0xEeF9B4a84C3327860CD14E1E066D7D6762b9bC3F&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see we’re able to get all of the markdown we put in earlier. Now all we have to do is create a frontend client that can render it all!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useEffect&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;MarkdownUI&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@markdown-ui/react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;Marked&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;marked&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;markedUiExtension&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;@markdown-ui/marked-ext&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;&quot;@markdown-ui/react/widgets.css&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  parseContractToMarkdown&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type &lt;/span&gt;&lt;span&gt;ContractResponse&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;} from &lt;/span&gt;&lt;span&gt;&quot;./utils/contractParser&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;marked&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Marked&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;use&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;markedUiExtension&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;0xEeF9B4a84C3327860CD14E1E066D7D6762b9bC3F&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;CHAIN_ID&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; &quot;11155111&quot;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;// Sepolia&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const [&lt;/span&gt;&lt;span&gt;contractHtml&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setContractHtml&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const [&lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setLoading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const [&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setError&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt; | &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  useEffect&lt;/span&gt;&lt;span&gt;(() =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;fetchContractData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async () =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          `https://sourcify.dev/server/v2/contract/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;CHAIN_ID&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;CONTRACT_ADDRESS&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?fields=devdoc`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          throw new &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`HTTP error! status: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;ContractResponse&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;markdownContent&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; parseContractToMarkdown&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;markdownContent&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        const &lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;marked&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          markdownContent&lt;/span&gt;&lt;span&gt; ||&lt;/span&gt;&lt;span&gt; &quot;# No markdown widgets found&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setContractHtml&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } catch (&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error fetching contract data:&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt; instanceof &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt; ? &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt; : &lt;/span&gt;&lt;span&gt;&quot;Unknown error&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } finally {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        setLoading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fetchContractData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }, []);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if (&lt;/span&gt;&lt;span&gt;loading&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Loading&lt;/span&gt;&lt;span&gt; contract&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        &amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text-red-500&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;}&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mx-auto flex min-h-screen max-w-xl flex-col items-center justify-center gap-6&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      &amp;lt;&lt;/span&gt;&lt;span&gt;MarkdownUI&lt;/span&gt;&lt;span&gt; html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;contractHtml&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  );&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export default &lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a result we get a nice page that not only has markdown formatting but interactive UI components that are built in thanks to Markdown UI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/natspec-markdown-ui-2.png&quot; alt=&quot;natspec demo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I took a little extra time to add in Wagmi to the app which resulted in a fully interactive contract, which you can check out &lt;a href=&quot;https://natspec-ui.orbiter.website&quot;&gt;here&lt;/a&gt;. In a sense we achievied the goal of a unified standard markup that can make user interactions with contracts easier. Of course we have to keep in mind the limitations here, primarily being it would require developers to make sure they include all of this markup in their contract and the Markdown UI standard isn’t even out of a beta stage, and for that reason I would highly recommend a professional solution like the Contracts UI Builder. Nevertheless it’s fun to see how extensible and open Markdown and Solidity have come in the past few years. Each day we’re getting closer to an internet that is not only safe, but user friendly as well.&lt;/p&gt;
&lt;p&gt;As always the code for this small project is open source and can be found &lt;a href=&quot;https://github.com/stevedylandev/natspec-markdown-ui&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;</content:encoded></item><item><title>ATProto, POSSE, and Personal Sites</title><link>https://stevedylan.dev/posts/using-atproto-for-posse/</link><guid isPermaLink="true">https://stevedylan.dev/posts/using-atproto-for-posse/</guid><description>My little weekend experiment to bring micro updates to my personal site</description><pubDate>Mon, 05 Jan 2026 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/atproto.png&quot; alt=&quot;atproto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Last year after reading &lt;a href=&quot;https://herman.bearblog.dev/slow-social-media/&quot;&gt;a post by Herman&lt;/a&gt; I realized I didn’t need social media; I could just be blogging. Thus started a new journey into &lt;a href=&quot;https://bearblog.stevedylan.dev/resurrect-the-old-web/&quot;&gt;feeds&lt;/a&gt;, RSS, and deleting the majority of my social media accounts. I started by using &lt;a href=&quot;https://bearblog.dev&quot;&gt;Bear Blog&lt;/a&gt; as a “side blog”, a place to post more casual thoughts or updates in my life. This approach has honestly been a great success in my opinion as I’ve been able to connect with so many wonderful people. Lately, however, I’ve been thinking more about &lt;a href=&quot;https://henry.codes/writing/a-website-to-destroy-all-websites/&quot;&gt;personal sites&lt;/a&gt;, &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;(publish on your own site syndicate elsewhere), and how I could better adapt this concept of a side blog. While I did have my bear blog on a subdomain of my primary domain, I liked the idea of bringing it all back to my personal site and investing in it more.&lt;/p&gt;
&lt;p&gt;These ideas got me thinking about building a simple API + DB that would create, list, update, or delete small posts for my site. Not long after sketching out what this might look like, I realized it looked awful lot like a Personal Data Server (PDS) from &lt;a href=&quot;https://atproto.com/&quot;&gt;ATProto&lt;/a&gt;. I got on Bluesky not long after it launched during the invite code frenzy, and I used it on and off but it didn’t stick much. I never looked too much into the protocol behind it, but my curiosity got the best of me. Within a couple of hours I already had my own PDS hosted and migrated my existing account over.&lt;/p&gt;
&lt;p&gt;In the process of setting up the PDS, I had a realization that it might not be possible or a &lt;a href=&quot;https://atproto.com/guides/going-to-production#domain-names&quot;&gt;good idea&lt;/a&gt; to use my primary domain for the instance. This is when I started to question if I could truly implement POSSE with ATProto. On one hand the approach of using a self hosted PDS checks several boxes like ownership, reduce third party dependencies, etc. The primary missing piece is canonical URLs. If I make a post to my PDS through a client like Bluesky, there isn’t a direct URL that points back to my domain. At best it could maybe point to it’s location on my PDS. Due to this I’m sure it doesn’t count as true POSSE, but it did give me some ideas of how I could get pretty close.&lt;/p&gt;
&lt;p&gt;What I ended up doing is implementing a &lt;a href=&quot;/now&quot;&gt;now page&lt;/a&gt; for my site. This includes some general updates or bullets on what’s happening in my life right now, but it also includes a feed of posts coming directly from my PDS. I love this setup because it means I can let people view these updates without asking them to sign up for Bluesky and follow me; they can just visit my site.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.stevedylan.dev/now-updates.png&quot; alt=&quot;now page&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Naturally I turned this feed of posts into an RSS feed. This introduced another piece of the puzzle: a place to view individual posts. The RSS feed needs a canonical URL, and instead of using a Bluesky URL, I could setup my own! The URL is a bit ugly since I want to keep my site static, but I setup a page on my site that loads posts client side via a query param, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;https://stevedylan.dev/pds?rkey=3mbjwj62kak2u&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The page includes a share button that copies the link to the current page to the viewer’s clipboard. If we step back and consider what we have now, I think you could argue that it is a close second to POSSE. Someone can visit my &lt;code&gt;/now&lt;/code&gt; page, view a post, maybe subscribe to the RSS feed, and all of the URLs in that feed point to my site. Everything happens on a protocol layer, and yeah sure you could go on Bluesky and see all these posts, but it also enables a flow where anyone can view my updates with ease and build links pointing back to my site.&lt;/p&gt;
&lt;p&gt;While my PDS doesn’t share my personal domain, I still have my handle setup to be my domain. This is probably one of the best features behind ATProto because even if your content isn’t self hosted, the content follows your domain. I will admit that you could easily do this with an account hosted on the default &lt;code&gt;bsky.social&lt;/code&gt; PDS, but I love the fact that this is data I own and I have full control over it.&lt;/p&gt;
&lt;p&gt;By all means this little experiment and implementation is not perfected or complete, and that’s because it’s my personal site. I don’t think any personal site can be truly “complete;” it should be an ever evolving garden that you tend with care. Adding these small pieces is just me getting my hands dirty after a long period of neglect, and most important of all, I had fun.&lt;/p&gt;</content:encoded></item><item><title>Vibe Coding and Kodak Cameras</title><link>https://stevedylan.dev/posts/vibe-coding-and-kodak-cameras/</link><guid isPermaLink="true">https://stevedylan.dev/posts/vibe-coding-and-kodak-cameras/</guid><description>A perspective on the rise of AI coding and how it relates to technological shifts throughout history</description><pubDate>Sun, 30 Mar 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/bafkreidwjks56b7ifrnltsepgknkygxukctjdywujcbh5c3uqu44lkefrq.jpg&quot; alt=&quot;cover&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I’m sure many who read this are familiar by now with the term “vibe coding,” a euphoric style of programming where you prompt AI models or IDEs to write software and just “vibe.” One of the more popular instances that made the practice takeoff was the indie hacker Levelsio building a flight simulator entirely in JavaScript and selling ad space within the game. Others have followed suit and even started businesses by vibe coding them into existence. It’s hard to deny the reality that AI has been changing much of the software ecosystem, and with any major shift in technology there are lots of opinions. I certainly have my own, but I thought it would be more productive to look at history repeat itself.&lt;/p&gt;
&lt;h2&gt;Evolution of Photography&lt;/h2&gt;
&lt;p&gt;Most people don’t know it, but there was a major controversy in the world of photography in the year 1900. Before that time photography was an art form protected by its sages, who poured their money, time, and practice into it. Not anyone could just take a picture, only those who had worked in the craft and mastered it. All of that changed in February of 1900 when Kodak released the Brownie camera. It wasn’t much to look at, a little cardboard box that could take a picture no bigger than 2.25 inches using 117mm film. What made it special was the service behind it. The Brownie only cost $1 (which would be $38 at the time of this post), and it included the cost to develop the film. All someone had to do was take a picture, send it off to Kodak, and they would return the picture. “You press the button—we do the rest.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/bafybeidg6ljbwwlfs3cvrekxwfyhw73pm577irs2h4zzkj4srm7q5x6dne.jpg&quot; alt=&quot;bronwie ad&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Suddenly anyone could take photos: parents, grandparents, kids, truly anyone. It sold like crazy, and it upset the old photographer guild. There was great concern that there would now be a huge amount of “slop” photography and the art of photography would be washed away. Surely no one who took photos in such a way could be an artist… right? These artists were also concerned for their profession. If everyone had a camera, why would families pay for a photography session?&lt;/p&gt;
&lt;p&gt;Fast forward to the digital age of cellphones and photography once again accelerated into the unknown. Now you don’t have to even own a camera. Almost every person on earth has a cellphone, and the chances are that cellphone has a camera. In this evolution a photo could be taken at any time, not just when you remembered to bring a camera. Around the same time we saw storage become dirt cheap, and now there’s no limit to how many photos you can take. The artists and professionals once again voiced their concern of the art form being lost in the sea of latte art and dog photos.&lt;/p&gt;
&lt;h2&gt;Evolution of Programming and Computers&lt;/h2&gt;
&lt;p&gt;Programming and computers seem to have a similar story, where as technology advances, the wider it’s user base becomes. For hardware it was the home computer that made it possible for anyone to have access to one. The World Wide Web in its infancy was only accessible by universities or libraries, but soon grew to be used by everyone. Content creation on the web used to mean writing HTML and running a server, but thanks to the movement of social media, APIs, and hosted web apps, people could write and express freely. An explosion of thoughts, ideas, and memes filling thousands of servers, racing at unbelievable speeds.&lt;/p&gt;
&lt;p&gt;Pivoting to programming languages, these too have seen lots of technological advancements. In the beginning it was feeding paper into a machine the size of a large room, which slowly evolved into languages that could be typed directly into computers. Low level communication through assembly to modern stacks like C, eventually becoming more and more abstract from the bare metal we used to speak to. Now we use languages like Javascript and package management systems that allow you install everything, and many of us cringe at the thought of it being used in backends. Last but not least, we now have vibe coding which many suggest means we don’t need to learn how to code. Instead we’re reaching the longed for era of end user programming.&lt;/p&gt;
&lt;p&gt;As with any technological advance, I believe the technology itself to be neutral, but in the hands of people, we see the good, the bad, and the ugly.&lt;/p&gt;
&lt;h2&gt;The Good&lt;/h2&gt;
&lt;p&gt;The evolution of photography technology enabled plenty of bad photos and photographers, but it also created a whole new series of artists we would not have otherwise. One of my favorite examples of this is Vivian Maier. If you’re not familiar, Vivian Maier was an unknown nanny in the 1940’s and 50’s. It wasn’t until after her death that her life’s work as a photographer was discovered by a man who won it at an auction. To his surprise it was a stunning collection, hundreds of thousands of them, all taken by a nanny no one had ever heard of. She was passionate about photography, and her perspectives of the world at that time were unique.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/bafybeicc62frhqspkismnm7ocuhvzutlffnxkgjuxirs46faig47btgwjm.jpg&quot; alt=&quot;vivian maier&quot; /&gt;&lt;/p&gt;
&lt;p&gt;They’re made possible thanks to the much later successor of a Brownie style camera that she could take everywhere and shoot roll after roll of film. If photography was still stuck in the dark ages of carrying around big pieces of equipment that only certain people could afford, we wouldn’t have the stunning work of Vivian Maier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/bafkreicb3fn44kn2lfitq7bmoionmmw5maho3fvk2i7zr5x4xfafieafmu.jpg&quot; alt=&quot;photo by vivian maier&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Another unrecognized photographer is one of my favorites, Joe Greer. Greer started his career on an app: Instagram. He didn’t take photography lessons, he didn’t have a nice camera, he just had his phone. The more and more he shot with his phone and posted his photos on Instagram, the more people liked them and the bigger it got. Eventually he did switch to professional cameras and continued his craft, but the key was his access to an art form that otherwise wouldn’t be available apart from cell phone cameras.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/bafybeieyixzansv22m6kzhuqbi4vxqb5zlgdcwvpm7ikkzz5j3dugtdhtu.jpg&quot; alt=&quot;joe greer photo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the realm of programming, even in the evolution of languages, we see a similar pattern where mediocrity increases but so does the number of discovered programmers. Sure there’s a lot of Javascript slop out there, but thanks to Javascript there have been more and more people discovering programming and starting a wonderful journey. You don’t have to start in a lower language to find the joy of programming, and most people who do find it will experiment in many different languages.&lt;/p&gt;
&lt;p&gt;Even in vibe coding I believe we’ll see people who were not programmers before, but discover the beauty of watching code they wrote achieve a goal they had in mind. While end user programming has it’s negatives, the positives are that people are solving their own every day problems. Those who go beyond solving a problem might stumble into a new passion they can pour hours of learning into.&lt;/p&gt;
&lt;h2&gt;The Bad, and Perhaps the Ugly&lt;/h2&gt;
&lt;p&gt;Of course it’s not all sunshine and roses, and I truly believe we face some challenges ahead with the consequences of vibe coding. As with any technological advance things become easier, and when a challenge is taken away, there is less for us to learn. The old way of programming which involved hours of getting stuck, forcing you to read documentation, researching answers, asking wiser people, all of that slowly fades for the easy way out. Sure you solved your problem, but what did you learn? How much that question matters might depend on who you are: a software engineer who needs to hone their craft, or just some Joe on the street that wanted a fun app to play with.&lt;/p&gt;
&lt;p&gt;Even for the everyday man that uses AI to build software they can miss out on the side effects of programming, which include building problem solving skills, critical thinking, and finding ways to think outside the box. In some ways I think this is where vibe coding doesn’t necessarily achieve end user programming, because with true end user programming we have people who learn how to code in the same way they learn to read and spell. It would be much more beneficial for society if people had these basic skills, but I’m not sure we’ll get that with AI and vibe coding.&lt;/p&gt;
&lt;p&gt;There is also the question of sensitive data and how much code controls our modern society. With our photography example the impact was mostly limited to the photography industry and artistic expression. It’s a different story with programming as we depend on code to handle our banking, our hospital records, our information and privacy, everything we do depends so much on the security of our code. Giving someone an AI enabled code editor is like giving Peter Parker supernatural spider abilities. With too much power and a dismissal of responsibility we face potentially terrible consequences. We’ve already seen this in some cases where a fresh vibe coder doesn’t practice basic API key protection and security and had their platform destroyed by the internet. Without proper training and knowing the “how” behind the code that’s running we face a new world of software that can be vulnerable.&lt;/p&gt;
&lt;p&gt;Lastly there is the slow decay of information. We all know public open source code is being fed into AI model training, and we can only hope that it’s good code. As more and more AI generated code is put online, the more AI models feed on it, and we face the reality of “Ouroboros,” a snake eating it’s own tail. Suddenly our all powerful AI coding assistants can’t solve the problems they once did, and they start to struggle with the basics. You as a professional developer might have known how to fix these issues before, but your mind has grown soft from your dependence on AI and now you too are stuck. This is what I am afraid of the most, and what I try to keep in mind when using AI.&lt;/p&gt;
&lt;h2&gt;What Do We Do?&lt;/h2&gt;
&lt;p&gt;Just as we have seen with any other advance of technology, we cannot stop it. Vibe coding is here and it likely isn’t going to disappear anytime soon. While we may not be able to stop the march of progress, we can change how we react to it. Being a snob won’t help the journey of someone discovering programming through vibe coding. You don’t have to like AI or vibe coding and you can still show kindness to a new generation of programmers. Instead of discouraging them, why not show interest in what they’re building and offer our help? If they are truly falling in love with programming, we have the opportunity to encourage them towards competence. Instead of just achieving their goals we can show them the beauty of knowing how they did it outside of “AI made it work.”&lt;/p&gt;
&lt;p&gt;It’s for this reason I disagree with &lt;a href=&quot;https://x.com/amasad/status/1905103640089825788&quot;&gt;Amjad&lt;/a&gt;; I do think you should still learn how to code. Perhaps it would be helpful to qualify “who” I think should still learn how to code. I would love everyone to learn, but more realistically I think those who find joy in programming should learn how to code, especially if your career depends on it. If we ever do face the Ouroboros then we need champions of the old way to write code that withstands the test of time. We need more people like Alejandro, who &lt;a href=&quot;https://x.com/apr/status/1828914554115436661&quot;&gt;wrote his own program to manage his checkbook in the 90’s and still uses it to this day&lt;/a&gt;. We’ll still be writing code in five years for the same reasons people take photos: we enjoy doing it.&lt;/p&gt;</content:encoded></item><item><title>When AI Gives the &apos;Ick&apos;</title><link>https://stevedylan.dev/posts/when-ai-gives-the-ick/</link><guid isPermaLink="true">https://stevedylan.dev/posts/when-ai-gives-the-ick/</guid><description>Ever look at something made by an AI company that gives you the &apos;ick&apos;?</description><pubDate>Fri, 03 Oct 2025 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/other/friend-1.jpeg&quot; alt=&quot;friend-ads&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A few days ago OpenAI released Sora, which was not only the latest version of their video generation model, but also a social app. Humans sign up, but the only content is that generated by users through AI. If you’re like me, the very thought of an AI-only generated feed might have given you the “ick.” It’s not the first time either. The debut of Friend (an AI powered “friend” app / pendant you wear around your neck) got even more backlash recently with their NYC subway ads.&lt;/p&gt;
&lt;p&gt;Needless to say, people didn’t like it.&lt;/p&gt;
&lt;div&gt; &lt;div&gt; &lt;div&gt; &lt;img src=&quot;/blog-images/other/friend-2.jpeg&quot; alt=&quot;Image 1&quot; loading=&quot;lazy&quot; /&gt; &lt;/div&gt;&lt;div&gt; &lt;img src=&quot;/blog-images/other/friend-3.jpeg&quot; alt=&quot;Image 2&quot; loading=&quot;lazy&quot; /&gt; &lt;/div&gt;&lt;div&gt; &lt;img src=&quot;/blog-images/other/friend-4.jpeg&quot; alt=&quot;Image 3&quot; loading=&quot;lazy&quot; /&gt; &lt;/div&gt;&lt;div&gt; &lt;img src=&quot;/blog-images/other/friend-5.jpeg&quot; alt=&quot;Image 4&quot; loading=&quot;lazy&quot; /&gt; &lt;/div&gt; &lt;/div&gt; &lt;div&gt; &lt;img alt=&quot;&quot; /&gt; &lt;/div&gt; &lt;/div&gt;  
&lt;p&gt;It’s these kinds of AI innovations that give us the “ick,” but why? There’s plenty of AI tools we use all the time without much thought, but sometimes we get the feeling that the tech goes “too far.” To understand why, we need to step back and ask a few more questions.&lt;/p&gt;
&lt;h2&gt;Why did we build the web?&lt;/h2&gt;
&lt;p&gt;The answer to this one is quite simple: communication. We wanted to build infrastructure that would allow people to communicate with other people. It was publishing web pages, Email, IRC, AIM, Blogs, and eventually social media in the form of MySpace or Facebook. It was not only a place where people could keep up with those they already knew, but also a place to meet new people. It was a place to explore, to wonder, and to discover. Even back then ads helped keep the lights on, but something shifted with the release of the endless Feed.&lt;/p&gt;
&lt;p&gt;It was a revolutionary UX concept that allowed someone to keep scrolling. At first that feed might have been people you follow, but what if you ran out of posts? Thanks to algorithms and using people’s data, people could scroll and find new content that was calculated to be engaging. Bit by bit, tech giants found the value of keeping people on their screens, collecting the data they provided by stopping the feed, clicking on something, or sharing it. We now live in a world where social feeds are addictive sources of dopamine that make minds sick. We became the product.&lt;/p&gt;
&lt;p&gt;The web we built to communicate, discuss, and connect with other people seems much more distant and harder to find. Our programmatic feeds make sure we see the content we agree with, and for the content we don’t agree with, it’s in the worst place possible to have a civil discussion with proper context and humility. Thanks to the recent release of AI content feeds, perhaps we don’t need people at all. That’s one reason we start to feel the ick, because it’s unnatural for us to be in isolation. Our need for connection plays into the next question.&lt;/p&gt;
&lt;h2&gt;Why do we create?&lt;/h2&gt;
&lt;p&gt;I have to clarify that I’m not anti-AI. I use it all the time for getting helpful information or learning how to do something, but also the idea of using it to create certain pieces of my life gives me the ick. I recently found &lt;a href=&quot;https://files.stevedylan.dev/ai-love-poems.mp4&quot;&gt;this video&lt;/a&gt; where a panel of people were addressing some of the issues with AI, and a person stated there are plenty of moments in their lives where they would choose to do it themselves even if AI could do it better. One example they gave was a love poem. The whole point of writing a love poem is that YOU wrote it, that YOU enjoyed the process and benefited from it. At the end of the video they say, “I think if we like living then we have the right to live even if we’re not good at it.”&lt;/p&gt;
&lt;p&gt;The truth is that people live to create, to explore, to feel. It’s not always about productivity. It’s the arts and our connection with other people that make us human. We read, write, think, discuss, debate, and problem solve for the experience of improving ourselves and our society. In some ways it’s antithetical to how tech is built today, where everything is about consumption. Everything is designed to make us the consumer. We sit on our phones and scroll through endless video feeds and provide little in return. It’s not who we’re supposed to be as humans. We should pursue the hard work of creating and thinking instead of being passive and consuming.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We don’t read and write poetry because it’s cute. We read and write poetry because we are members of the human race. And the human race is filled with passion. And medicine, law, business, engineering, these are noble pursuits and necessary to sustain life. But poetry, beauty, romance, love, these are what we stay alive for. To quote from Whitman, ‘O me! O life!… of the questions of these recurring; of the endless trains of the faithless… of cities filled with the foolish; what good amid these, O me, O life?’ Answer. That you are here - that life exists, and identity; that the powerful play goes on and you may contribute a verse. What will your verse be?
― N.H. Kleinbaum, Dead Poets Society&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;What is at risk?&lt;/h2&gt;
&lt;p&gt;I know some will argue that there is still creativity and creation happening when using an app like Sora, but is that truly the case? Sure you have to prompt an idea, but does that count as creating? While this might be an area up to debate, I would argue that AI prompting hardly counts as creating in the traditional sense. There is a direct correlation between how much credit you can take for a piece of work based on how much work and influence you had in the process. If creativity is a spectrum, where on that spectrum did you contribute? How hard did you work for it? Does it channel your emotions in a way that causes others to think more deeply about the topic? Does it evoke emotions out of others? How did you feel or benefit from creating it? Can you truly call it “your work”? Of course I admit that sometimes it’s pure fun to use stuff like Sora. It’s not serious and not meant to be serious. That’s the current state, but have you checked Facebook lately? The danger is what Sora introduces.&lt;/p&gt;
&lt;p&gt;As humans create, we gain humanity. The process, the brainpower, the emotion, the connection with others, all these culminate into the very things that make us human. If we spend our time doing “creative” acts that don’t build these principles, then we risk losing our humanity.&lt;/p&gt;
&lt;h2&gt;What can we do?&lt;/h2&gt;
&lt;p&gt;The first thing we can do is recognize the problem: social media needs reform. Not necessarily policy reform, but how we approach it in our every day lives. Social media is engineered addiction where we offer our most precious resource: time. Every social platform is after your attention and keeping it as much as possible. Many have tried to combat this problem with apps that block your access to certain apps, but I believe that only taps the surface of the issue. Much like the dieting craze of the ’90s, eating less food doesn’t solve your health issues. You have to substitute junk food for real food and exercise. I’m not gonna say you can’t use AI tools or have fun; AI generated content can be fun! I also enjoy the occasional fast food. The issue is when it’s all you’re consuming, and you’re not substituting or getting enough real food. Endless feeds make this difficult, but not impossible. Even in my own experience I am feeling the pull of social media feeds less and less as I invest more time in “real food.”&lt;/p&gt;
&lt;p&gt;On the note of “real food,” I’m personally exploring alternative methods of how we might engage in social interactions online. My favorite so far is &lt;a href=&quot;https://blogfeeds.net&quot;&gt;Blog Feeds&lt;/a&gt;. Rather than an endless feed of things to read, it’s just a short list of people you actually care about and accessing what they write on their blogs. It’s autonomous, not depending on any large tech corporation that’s after your time. It’s just writing on a blog like this one, subscribing to other people’s blogs, and sharing a list of the people you follow. That’s it. Blog Feeds take us back to why we built the web: communication. With this approach we can finally get back to exchanging ideas, seeing people as people, and finding common ground. Is it harder than prompting Sora or scrolling TikTok? Yes. Is the barrier high? Nope. It’s relatively simple and just about anyone can participate. It just requires being a human, and using your mind in ways that we’re slowly losing.&lt;/p&gt;
&lt;p&gt;In order to regain our humanity, we must change how we engage with the web, and how we connect with other people. Otherwise our minds will end up looking no different than the slop on Sora that gives us the ick.&lt;/p&gt;</content:encoded></item><item><title>Why I Learned Vim</title><link>https://stevedylan.dev/posts/why-i-learned-vim/</link><guid isPermaLink="true">https://stevedylan.dev/posts/why-i-learned-vim/</guid><description>A brief look at my history and how ordinary jobs lead to learning programming and Vim/Neovim</description><pubDate>Fri, 05 Jan 2024 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/placeholder.png&quot; alt=&quot;image&quot; /&gt;&amp;gt;&lt;/p&gt;
&lt;p&gt;Something I see pretty consistently when the topic of Vim/Neovim comes up is the inevitable question “Why?” Thanks to years of memes about not being able to quit Vim, it has gained a cult popularity of being “elite,” “ancient,” or both. Most developers who are comfortable in their VSCode environment can’t imagine using something so unfriendly and perhaps ugly. So yeah, “why” is a fair question. I reflected on that question and I realized how my own life experience led me to learn Vim, and perhaps it would be helpful to share that story.&lt;/p&gt;
&lt;p&gt;To understand why I use Vim, we gotta go back to 2013. I just graduated college, and I needed a job. I got one at Bass Pro Shops as a footwear associate. It was a pretty standard retail job, I would fetch shoes from the back as people requested sizes, organize displays, and unload freight. Starting out, I was an “ok” employee, but thanks to my boss Chuck, that changed. Early on he would half joke “oh you’re finally done?” or “it’s about time.” It was strange combination of mocking and encouragement, hinting that I was not meeting my full potential. It spurred me to do better, to work smarter and faster. Chuck not only gave me a strong work ethic, he gave me the desire to be productive. Even in that footwear position I learned how I could be fast, accurate, and highly productive in ways that other people noticed.&lt;/p&gt;
&lt;p&gt;I worked up the ranks and eventually became a manager of the hunting department, doing stuff I never thought I would, like doing inventory stock requests and other computer-heavy tasks. I’m not sure about retail stores, but this one used an IBM system to handle their internal inventory and product info. It was the classic black screen with green text; all keyboard, no mouse. Here I learned to be fast thanks to keyboard shortcuts. I was able to apply what I had already learned, that fast and accurate resulted in productivity, to machines.&lt;/p&gt;
&lt;p&gt;Of course, my time in retail was limited. It’s a hard job to sustain and justify over a long period of time, and I wanted something more regular. I heard banking was a good transition from retail, so I got a position as a teller at a local bank. Just like retail, banks work with data. A lot of data. From day one I had to learn how to use their software to access all that data, and it was not the best experience at first. We used a web GUI nicknamed “white screen” where you had to do a whole bunch of clicking to navigate or get access to anything. It wasn’t the keyboard speed I was used to at my previous job. I found ways to get sort of fast, but nothing amazing.&lt;/p&gt;
&lt;p&gt;Then one day, a coworker showed me “black screen.” It was the exact same access to the data but through the familiar IBM style terminal UI, all driven by keyboard with no mouse! I was ecstatic, and quickly learned to master it. I was fast again, boosting productivity at incredible rates. I could pull up anything in a few key strokes faster than anyone. I loved it. Once again the hard work ethic and productivity paid off, and I climbed through the ranks of the bank and later became a manager in a back office support role. Through my speed and accuracy, I became the guy who knew a lot and was infamous for getting stuff done.&lt;/p&gt;
&lt;p&gt;Several years go by in that role and it started to take its toll on my mental health. It was 2020, my first son was just born, and while waiting in the car for my wife finish to a doctor’s appointment (no plus ones back then), I stumbled upon the opportunity of programming via YouTube. I wanted a career change, and programming sounded interesting. After just one weekend of deep diving into web development basics, I was hooked. This began my journey of becoming a self-taught developer, which you can read more about &lt;a href=&quot;https://stevedylan.dev/posts/my-developer-journey&quot;&gt;here&lt;/a&gt;. It was a rough but exciting time, as I would wake up early to learn and write code, go to work, help out my wife with our new son, then stay up late into the night to keep going.&lt;/p&gt;
&lt;p&gt;After a few months I was making pretty basic websites, using the ever popular editor VSCode. Even then I was prioritizing how I could be more productive by taking typing tests and working on improving my WPM. It was logical to see that being able to type faster would result in higher productivity in both writing code and just writing in general. However, it was around that time I saw a video about Vim from none other than &lt;a href=&quot;https://www.youtube.com/@ThePrimeagen&quot;&gt;The Primeagen&lt;/a&gt;. Immediately I was blown away by the speed, and I knew I had to learn it. This was the next level of using the keyboard and special shortcuts to achieve the speeds I craved. I started with the classic Vim Tutor, slowly learning the keybindings and exploring the config options.&lt;/p&gt;
&lt;p&gt;That was about three years ago, and I had the advantage of learning Vim early on and it becoming a standard part of my workflow. Plus it was on my own pace; I was not programming professionally at the time so it’s not like I had to go through a period of not being able to push out code for a job. Nevertheless, I am so glad I took the time then to push through it. To this day Vim keybindings and Neovim have helped me become more productive and write code faster.&lt;/p&gt;
&lt;p&gt;Perhaps a question you might be asking yourself now is “should I learn Vim?” Honestly the answer might depend. I would say there needs to be an important distinction between Vim keybindings and Vim/Neovim itself. AKA “Vim Motions”, allow you to manipulate and “edit” text. You can write text in pretty much any application, but true editing lets you maneuver and mutate text in ways you never thought were possible. The keybindings are used in other applications including VSCode with the Vim extension, and in my opinion they are worth learning. You can at the very least toggle between them and practice them, and can help you if you ever find yourself on a server and need to edit a config file quickly.&lt;/p&gt;
&lt;p&gt;Neovim as a text editor is a different story, because at that point you are setting up your dev environment on a personal and custom level. The UI and features that you would see at startup in VSCode will likely not exist at all, and would require plugins to recreate pieces of functionality. Some might see this as a downside, however it gives you the opportunity to make something that works for you and won’t slow you down. Taking the reverse route in VSCode by removing UI elements is much more difficult in my opinion as I have tried it, and it just isn’t the same. Nothing beats a Neovim configuration specially built to handle the code you need to write.&lt;/p&gt;
&lt;p&gt;Is Vim worth learning? Perhaps a better question would be “how fast and productive do you want to be?” If you’re like me where you have an itch that can’t be scratched, always finding ways to automate, speed up, and maximize productivity, then you may want to consider trying it out. It does take time to learn, and it could initially hamper your productivity, but at least you gave it a shot. For others Vim is not the answer, and VSCode is actually what makes them more productive, but you never know until you try.&lt;/p&gt;
&lt;p&gt;My personal advice for those wanting to dip their toes into Vim would be using the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vscodevim.vim&quot;&gt;Vim extension in VSCode&lt;/a&gt;. Try to use it as much as possible, grind through the pain, watch &lt;a href=&quot;https://youtube.com/playlist?list=PLm323Lc7iSW_wuxqmKx_xxNtJC_hJbQ7R&amp;amp;si=iaJI_NAzkQT-Kvnu&quot;&gt;these videos&lt;/a&gt; to learn the keybindings and movements, and just push yourself to the max. It could take weeks or months to start feeling the results, but once you do, it’s hard to go back. When you feel pretty comfortable then you should try working in Neovim from the terminal and start building your configuration. There are plenty of distros that can give you a head start, but arguably the best is &lt;a href=&quot;https://github.com/nvim-lua/kickstart.nvim&quot;&gt;kickstart&lt;/a&gt; as it teaches you how to build your config instead of handing you too much. Getting a solid config will help your speed too as it will help you access files faster, switch projects, handle git, and more.&lt;/p&gt;
&lt;p&gt;The goal of this post is not to convince you that you should learn Vim, but show you why I personally use it and why I think thousands of other developers enjoy it as well. In the end you should use what works best for you, but also keep an open mind to the possibilities.&lt;/p&gt;</content:encoded></item><item><title>Why You Should Learn jq in 2024</title><link>https://stevedylan.dev/posts/why-you-should-learn-jq-in-2024/</link><guid isPermaLink="true">https://stevedylan.dev/posts/why-you-should-learn-jq-in-2024/</guid><description>Discover why learning jq isn&apos;t just about boosting your productivity, it&apos;s about becoming a more curious developer</description><pubDate>Sat, 12 Oct 2024 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;/blog-images/files-stevedylan-dev/bafybeihderpsuxl43msvzletfuhuqw75ygo3jhbh2psiboate4xc7gzhde.webp&quot; alt=&quot;header image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The chances are that if you are a modern developer or if you’re starting out, you probably don’t know what &lt;code&gt;jq&lt;/code&gt; is, and that’s why I’m writing this post. It won’t take long to explain what &lt;code&gt;jq&lt;/code&gt; is, so let’s just get that out of the way.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; could be labeled a command line tool, but in truth it‘s a very “high-level lexically scoped functional programming language” (at least according to Wikipedia) that has been around for over a decade. The whole thing is based around JSON and helping you manipulate it in the terminal quickly, which may seem dull or not very useful to the untrained, but let me &lt;strong&gt;show you&lt;/strong&gt; why this goes so hard.&lt;/p&gt;
&lt;p&gt;A great way to experience &lt;code&gt;jq&lt;/code&gt; yourself is to follow their &lt;a href=&quot;https://jqlang.github.io/jq/tutorial/&quot;&gt;short tutorial&lt;/a&gt; but I wanted to give a more personal example. Recently I built a &lt;a href=&quot;https://cli.pinata.cloud&quot;&gt;CLI&lt;/a&gt; for Pinata that lets you upload, access, and manage your files from the terminal. One simple command you can run is &lt;code&gt;pinata files list -a 5&lt;/code&gt; to display a list of your most recent uploads (limiting the amount to 5 items for now), and the output looks like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;files&quot;: [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;: &quot;01927e06-6f36-7208-adb9-8cdd53ce1c98&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;: &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;: &quot;bafybeicvzjxfbo5a54q5jnteo52sbgqlvnvlcpqix24gnepow2ussmxbbq&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;: 4262857,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;: &quot;application/gzip&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;group_id&quot;: &quot;01921735-7dd6-746b-b091-170e204c03d4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;: &quot;2024-10-12T00:00:04.998938Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;: &quot;019278e0-1383-73b9-9051-4ec9db013927&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;: &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;: &quot;bafybeic7agnfmxn7qktejq74xosobmurrg4yak7np2j2kfaivdxl3wt3pm&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;: 4263052,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;: &quot;application/gzip&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;group_id&quot;: &quot;01921735-7dd6-746b-b091-170e204c03d4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;: &quot;2024-10-11T00:00:05.235771Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;: &quot;01927709-7dfd-7820-bd83-0e54b5f9ecd8&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;: &quot;list&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;: &quot;bafkreigsvbe22ir66kqiaabfa76eunkp4mnbs5jtyd2euy66zsab3uuvm4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;: 443,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;: &quot;text/plain&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;: &quot;2024-10-10T15:26:04.181151Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;: &quot;01927707-8a16-7d7e-a862-211b4145455d&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;: &quot;zed-keybindings&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;: &quot;bafkreicap37u35xrlrvzyhzgbn5p7mtbivfdda2qbh4wgzy4fhctoxbg5i&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;: 19212,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;: &quot;application/json&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;: &quot;2024-10-10T15:23:56.04911Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;id&quot;: &quot;019273b9-b556-7ea3-b793-847b69095a7a&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;name&quot;: &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;cid&quot;: &quot;bafybeia5kwvkyqwvqaifqmwmlxopysvyqs7hxvkqlzqv7jlnnv4lshx5xm&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;size&quot;: 4262964,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;number_of_files&quot;: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;mime_type&quot;: &quot;application/gzip&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;keyvalues&quot;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;group_id&quot;: &quot;01921735-7dd6-746b-b091-170e204c03d4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            &quot;created_at&quot;: &quot;2024-10-10T00:00:04.879244Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &quot;next_page_token&quot;: &quot;MDE5MjczYjktYjU1Ni03ZWEzLWI3OTMtODQ3YjY5MDk1YTdh&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trying to view this in the terminal can be pretty rough; there’s a lot to look at here. That’s the case with most APIs, and this one isn’t even that complicated. If you were trying to fetch data from the Google books API, it will be almost impossible to navigate. That’s where &lt;code&gt;jq&lt;/code&gt; comes in.&lt;/p&gt;
&lt;p&gt;Let’s say I just wanted the first item in the &lt;code&gt;files[]&lt;/code&gt; array; I can do that with &lt;code&gt;jq&lt;/code&gt; by &lt;a href=&quot;https://www.geeksforgeeks.org/piping-in-unix-or-linux/&quot;&gt;piping&lt;/a&gt; the results of the previous command into &lt;code&gt;jq&lt;/code&gt; like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list -a 5 | jq .files[0]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will select the array and particularly the zero index of the array, just one object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;id&quot;: &quot;01927e06-6f36-7208-adb9-8cdd53ce1c98&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;name&quot;: &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;cid&quot;: &quot;bafybeicvzjxfbo5a54q5jnteo52sbgqlvnvlcpqix24gnepow2ussmxbbq&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;size&quot;: 4262857,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;number_of_files&quot;: 1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;mime_type&quot;: &quot;application/gzip&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;keyvalues&quot;: {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;group_id&quot;: &quot;01921735-7dd6-746b-b091-170e204c03d4&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;created_at&quot;: &quot;2024-10-12T00:00:04.998938Z&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ok big whoop, we just reduced our results to a single object. However if we stop and think through the syntax of what we just did, we can start to see what else is possible here. What if I just wanted to grab the &lt;code&gt;cid&lt;/code&gt; for the file that matches the name “pinnie”?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list -n pinnie | jq .files[0].cid&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What if I can to pipe that &lt;code&gt;cid&lt;/code&gt; into another command, perhaps open it in the browser?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list -n pinnie | jq .files[0].cid | xargs pinata gateways open&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might have noticed we slipped an extra tool in here called &lt;code&gt;xargs&lt;/code&gt; and while it’s outside the scope of this post I would highly recommend learning that tool as well. Back to &lt;code&gt;jq&lt;/code&gt;, let’s try something else. What if I need the file names for not just the last 5 results but 50 or 100. How do we go outside just one index of an array? Just a little change in our syntax.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list | jq [.files[].name]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will give us an array of all the file names like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;list&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;zed-keybindings&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;hello.txt&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;data.json&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;b68ad739-3664-4f9f-8921-49f30ad5d615.txt&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;pgdata.tar.gz&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can pipe that into a list for later if we need it!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;pinata files list | jq [.files[].name] &amp;gt; files.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The possibilities are endless; we could continue piping commands, use a for loop in bash, etc. &lt;code&gt;jq&lt;/code&gt; is just one of those special tools that really enables you do to more from the terminal, and that brings us to the real point of this article: learning to use CLIs.&lt;/p&gt;
&lt;p&gt;Sadly there are many devs out their whose knowledge of the terminal stops at &lt;code&gt;npm run dev&lt;/code&gt; at the bottom of VSCode. Maybe they go a bit further and know some commands in &lt;code&gt;git&lt;/code&gt;, but generally people miss out on massive productivity gains that they could have had otherwise. Further, they miss out on a better understanding how all of this stuff works. I‘ve mentioned &lt;a href=&quot;https://stevedylan.dev/posts/why-i-learned-vim/&quot;&gt;in previous posts&lt;/a&gt; that learning Neovim and building your config will give you a better low-level understanding of your tooling; this includes things like LSPs or syntax highlighting which are normally abstracted away from you.&lt;/p&gt;
&lt;p&gt;Not all abstraction is bad. For example, I would much rather use &lt;a href=&quot;https://pinata.cloud&quot;&gt;Pinata&lt;/a&gt; than S3, but I won’t deny that it could be good to learn how to use S3. In today’s developer ecosystem it’s becoming a growing skill to learn what is worth abstracting and what isn’t. For example, I personally don’t find much value in abstracting &lt;code&gt;git&lt;/code&gt; into a formal gui or dedicated client, however I do find value in using a tui like &lt;a href=&quot;https://github.com/jesseduffield/lazygit&quot;&gt;lazygit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Like most things in life, there are so many gray areas in our current developer environment. Most of this comes down to preference, and it could be argued from one side to the other. Sometimes it’s more important to ship a product, sometimes it’s more important to really understand what you’re doing. Ultimately it’s up to you how far you want to go down the rabbit hole, or perhaps even more important, how &lt;em&gt;open&lt;/em&gt; you are to the possibilities.&lt;/p&gt;
&lt;p&gt;In my opinion the best developer is a curious one. “How does this work? Why does this work? How could I use this elsewhere?” Obviously you can’t learn everything, and you don’t necessarily need to these days, but curiosity grows your knowledge. I don’t have to write in basic at all for my job, but that hasn’t kept me from at least being curious and exploring how it works. The magic of programming is the ability to control computers and data. As was said in a recent stream with DHH and The Primeagen, “It’s more fun to be competent,” and I couldn’t agree more.&lt;/p&gt;
&lt;p&gt;I don’t know about you, but I don’t want to settle for status quo; I want to be a wizard. So yeah, I think you should learn &lt;code&gt;jq&lt;/code&gt; in 2024.&lt;/p&gt;</content:encoded></item><item><title>How to Encrypt and Decrypt Files on IPFS Using Lit Protocol</title><link>https://stevedylan.dev/posts/how-to-encrypt-and-decrypt-files-on-ipfs-using-lit/</link><guid isPermaLink="true">https://stevedylan.dev/posts/how-to-encrypt-and-decrypt-files-on-ipfs-using-lit/</guid><description>Experience the power of decentralized storage, encryption, and token gating with this tutorial</description><pubDate>Sat, 04 Nov 2023 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The most popular method used for sharing files off-chain in Web3 is IPFS, and there are some &lt;a href=&quot;https://www.pinata.cloud/blog/why-ipfs-is-the-storage-solution-for-web3-developers&quot;&gt;good reasons for that&lt;/a&gt;. However it does not come without its own share of problems, and one of those is the ability to share private files. IPFS is a public network so anyone with a CID can access and download that content, and this hinders projects that may want to token gate content or create subscriptions to content. With that said, encryption has proven to be one solution to this problem. Remarkably, the solution of &lt;a href=&quot;https://www.okta.com/identity-101/asymmetric-encryption&quot;&gt;asymmetric encryption&lt;/a&gt; is used in blockchain all the time and can be reused for the purpose of token gating. &lt;a href=&quot;https://litprotocol.com/&quot;&gt;Lit Protocol&lt;/a&gt; is a decentralized middleware client that enables access controls to help extend asymmetric encryption to token gating based on crypto ownership, such as owning an NFT, ERC-20 token balance, or simply designating a recipient address. In this post, we’ll show you how you can combine the best of both worlds and create an app that will encrypt content, upload it to IPFS, and then given an encrypted CID, decrypt it.&lt;/p&gt;
&lt;h2&gt;Why IPFS?&lt;/h2&gt;
&lt;p&gt;IPFS is public and openly available, but it’s also not permanent by default. This means that unlike blockchain storage protocols that make every piece of content permanent as soon as it’s uploaded, you can potentially remove content from IPFS. You’ll see why this is important as we explore encryption more deeply.&lt;/p&gt;
&lt;p&gt;The biggest problem with encryption is that its always evolving. One encryption method we use today will be outdated one day in the future. An example is &lt;a href=&quot;https://www.okta.com/identity-101/md5/#:~:text=The%20MD5%20hash%20function&apos;s%20security,be%20used%20for%20malicious%20purposes.&quot;&gt;MD5 which was cracked almost perhaps 10 years ago&lt;/a&gt; but people still use it without knowing the risk. When we consider putting files on a decentralized network that are specifically designed to not be taken down, things get messy. Arweave is a common consideration for encryption and decentralized storage, however, their model puts content on the network permanently. There is the possibility those encrypted files could be cracked in another 10 years.&lt;/p&gt;
&lt;p&gt;IPFS is different in that content is not “permanent,” but rather it is “persistent.” It’s a subtle difference but has massive ramifications. With IPFS, the content will only stay on the network if at least one IPFS node keeps the content “&lt;a href=&quot;https://www.pinata.cloud/blog/what-is-pinning&quot;&gt;pinned&lt;/a&gt;,” which tells other nodes that might have a cached copy of the content to keep it available. As soon as there are no nodes pinning a particular CID, then the nodes holding that cache will dump it when they use garbage collection. It’s a unique mechanism that helps prevent digital waste and ensures only the content we value will persist. The concepts of permanence and persistence are truly philosophical differences of approaching the same problem.&lt;/p&gt;
&lt;p&gt;When you combine IPFS with encryption, you get a unique situation where content that is no longer used can be unpinned. Granted it does not guarantee the content will be completely wiped from the network, but it does give users a level of control over their content they would normally not have with other decentralized storage networks. It is also unlikely that bad actors would go through the trouble and costs to keep encrypted content pinned for the purpose of decrypting it years down the road. The &lt;a href=&quot;https://www.pinata.cloud/blog/is-ipfs-free&quot;&gt;cost of storage&lt;/a&gt; helps balance situations like these. With that said let’s actually build this thing!&lt;/p&gt;
&lt;aside&gt;
ℹ️ &lt;b&gt;Disclosure:&lt;/b&gt; There will always be limitations to encryption and eventually current methods may be cracked. Please be aware of the risk involved and be sure to read &lt;a href=&quot;https://developer.litprotocol.com/v3/sdk/authentication/security&quot;&gt;Lit Protocol’s best practices for security.&lt;/a&gt;
&lt;/aside&gt;
&lt;h2&gt;Building the App&lt;/h2&gt;
&lt;p&gt;What’s great about this project is that we already have most of what we need to build it! Pinata created a &lt;a href=&quot;https://www.pinata.cloud/blog/announcing-pinata-ipfs-developer-starter-templates&quot;&gt;Next.js template&lt;/a&gt; a while back which we can use again and just add in our Lit Protocol SDK.&lt;/p&gt;
&lt;p&gt;To follow this tutorial you will want to make sure you have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 18 or higher&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://app.pinata.cloud/register&quot;&gt;A Free Pinata Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A text editor like VSCode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before going any further make sure you get a &lt;a href=&quot;https://app.pinata.cloud/register&quot;&gt;free Pinata account&lt;/a&gt; so you can make an &lt;a href=&quot;https://docs.pinata.cloud/docs/api-keys&quot;&gt;API key&lt;/a&gt; and get a &lt;a href=&quot;https://docs.pinata.cloud/docs/dedicated-ipfs-gateways&quot;&gt;free Dedicated Gateway&lt;/a&gt; for this project! Once you make an API key, save the &lt;code&gt;JWT&lt;/code&gt; that we’ll use in a little bit, as well as the gateway domain for your Dedicated Gateways.&lt;/p&gt;
&lt;p&gt;Thats it! To kick it off, simply run the command to use the Pinata Next.js Template&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; create-pinata-app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will be prompted to choose your flavors of the app, such as Typescript vs Javascript, or Tailwindcss vs Vanilla CSS. For this template, I chose Typescript and Tailwindcss but feel free to choose your own. After giving it a name and making your selections go ahead and open the project in VSCode.&lt;/p&gt;
&lt;p&gt;Back in the terminal the next thing we’re going to do is install the &lt;a href=&quot;https://developer.litprotocol.com/v3/sdk/installation&quot;&gt;Lit Protocol SDK&lt;/a&gt;. We’ll be using the V3 of the SDK which is in beta, and you can install based on their docs &lt;a href=&quot;https://developer.litprotocol.com/v3/sdk/installation&quot;&gt;here&lt;/a&gt; or use this command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; install&lt;/span&gt;&lt;span&gt; @lit-protocol/lit-node-client@cayenne&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last thing you need to do to set up the project is open the &lt;code&gt;.env.sample&lt;/code&gt; file which should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;PINATA_JWT=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;NEXT_PUBLIC_GATEWAY_URL=&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;NEXT_PUBLIC_GATEWAY_TOKEN=&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste in the &lt;code&gt;PINATA_JWT&lt;/code&gt; that you made earlier when you set up your Pinata account and also paste in the &lt;code&gt;NEXT_PUBLIC_GATEWAY_URL&lt;/code&gt; with the format &lt;code&gt;[https://mygateway.mypinata.cloud](https://mygateway.mypinata.cloud)&lt;/code&gt; with of course your own domain URL. Then change the name of the file from &lt;code&gt;.env.sample&lt;/code&gt; to &lt;code&gt;.env.local&lt;/code&gt;, a very important step for our app to work!&lt;/p&gt;
&lt;p&gt;Now lets go ahead and spin up the dev server with &lt;code&gt;npm run dev&lt;/code&gt; and start building. All of our work will be done in just one file, &lt;code&gt;pages/index.tsx&lt;/code&gt;; easy! We’ll start by importing the Lit Protocol SDK at the top of the file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import { &lt;/span&gt;&lt;span&gt;useState&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;useRef&lt;/span&gt;&lt;span&gt; } from &lt;/span&gt;&lt;span&gt;&quot;react&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;Head&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;next/head&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;Image&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;next/image&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;Files&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;@/components/Files&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// import lit protocol sdk&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; as &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt; from &lt;/span&gt;&lt;span&gt;&quot;@lit-protocol/lit-node-client&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside the &lt;code&gt;Home&lt;/code&gt; component, we’ll add another state variable that we’ll come back to later.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setFile&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;cid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setCid&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;uploading&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setUploading&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// add a new state for the cid to decrypt&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const [&lt;/span&gt;&lt;span&gt;decryptionCid&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;setDecryptionCid&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The great thing about this template is that it’s already got uploads to IPFS with Pinata baked in with an &lt;code&gt;/api/files&lt;/code&gt; route on the backend, so all we have to do is encrypt the file before we upload it. We’ll do this in the &lt;code&gt;uploadFile&lt;/code&gt; function inside of &lt;code&gt;Home&lt;/code&gt;, and it should look like this to start.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;uploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;formData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;FormData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    formData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;file&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/files&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;formData&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;ipfsHash&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setCid&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ipfsHash&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } catch (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Trouble uploading file&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the top of our &lt;code&gt;try&lt;/code&gt; statement but underneath our &lt;code&gt;setUploading&lt;/code&gt; state, we’ll initialize the &lt;code&gt;LitNodeClient&lt;/code&gt; using the &lt;code&gt;ceyenne&lt;/code&gt; network, connect our app to that network, then get the &lt;code&gt;authSig&lt;/code&gt;. Lit Protocol is a decentralized network middleware that helps us do some cool token gating and lets us do encryption. In these few statements, we create a client that connects to that middleware network and then gets a signature from the user. This signature will be used for signing the encrypted files.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;uploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;cayenne&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // then get the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// rest of the code&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up we’ll set up our access controls for this encrypted content. We’ll dive deeper into this later in the tutorial, but essentially this is the most important part of our app as it determines who can decrypt the content we encrypt. It could be something like token gating by NFT collection or a direct address. For now, we’ll keep it simple and allow anyone with a balance of 0 ETH or higher to decrypt it (that should be everyone with a wallet).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;uploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			// Create our litNodeClient&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;cayenne&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Then get the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			// Define our access controls, this is set to be anyone&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const &lt;/span&gt;&lt;span&gt;accs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          contractAddress&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          standardContractType&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;eth_getBalance&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          parameters&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&apos;:userAddress&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;latest&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          returnValueTest&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            comparator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&amp;gt;=&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;0&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// rest of the code&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fun part; encrypting! There are several methods of encryption that Lit Protocol offers through their SDK, such as just a string, or a file, and in our case, we’ll use the &lt;code&gt;encryptFileAndZipWithMetadata&lt;/code&gt; method. This is handy because in order to decrypt a file, our recipient will need the &lt;code&gt;accs&lt;/code&gt; parameters we set and a secure hash. We want a simple way for all of this to be packaged and included in our IPFS CID, and that’s exactly what this method will do. All we have to do is pass in our access control conditions array, our &lt;code&gt;authSig&lt;/code&gt;, the chain, our &lt;code&gt;fileToUpload&lt;/code&gt; that we passed into the function argument, the &lt;code&gt;litNodeClient&lt;/code&gt;, and finally a simple &lt;code&gt;readme&lt;/code&gt; that will explain to whoever happens to download it from IPFS what they need to do with it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;uploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Create our litNodeClient&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;cayenne&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Then get the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Define our access controls, this is set to be anyone&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;accs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          contractAddress&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          standardContractType&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;eth_getBalance&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          parameters&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&apos;:userAddress&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;latest&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          returnValueTest&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            comparator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;&amp;gt;=&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;0&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      ];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Then we use our access controls and authSig to encrypt the file and zip it up with the metadata&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;encryptedZip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encryptFileAndZipWithMetadata&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        accessControlConditions&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;accs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        authSig&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNodeClient&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        readme&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Use IPFS CID of this file to decrypt it&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// rest of the code&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One last little touch we need to do is adapt this encrypted zip file so it will be accepted by our &lt;code&gt;/api/files&lt;/code&gt; endpoint, and we’ll do so with just two lines of code.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// Then we turn it into a file that will be accepted by the API endpoint&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;encryptedBlob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Blob&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;encryptedZip&lt;/span&gt;&lt;span&gt;], { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;encryptedFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;encryptedBlob&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All together we should have an upload function that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;uploadFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Create our litNodeClient&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;cayenne&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Then get the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ethereum&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Define our access controls, this is set to be anyone&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;accs&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        contractAddress&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        standardContractType&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ethereum&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;eth_getBalance&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        parameters&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;:userAddress&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;latest&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        returnValueTest&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          comparator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;gt;=&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Then we use our access controls and authSig to encrypt the file and zip it up with the metadata&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;encryptedZip&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encryptFileAndZipWithMetadata&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      accessControlConditions&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;accs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      authSig&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ethereum&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      litNodeClient&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      readme&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Use IPFS CID of this file to decrypt it&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Then we turn it into a file that will be accepted by the Pinata API&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;encryptedBlob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Blob&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;encryptedZip&lt;/span&gt;&lt;span&gt;], { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;encryptedFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;encryptedBlob&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;fileToUpload&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Finally we upload the file by passing it to our /api/files endpoint&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Keep in mind this works for smaller files and you may need to do a presigned JWT and upload from the client if you&apos;re dealing with larger files&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // Read more about that here: https://www.pinata.cloud/blog/how-to-upload-to-ipfs-from-the-frontend-with-signed-jwts&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;formData&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;FormData&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    formData&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;file&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encryptedFile&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encryptedFile&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/api/files&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;POST&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      body&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;formData&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    const &lt;/span&gt;&lt;span&gt;ipfsHash&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setCid&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ipfsHash&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } catch (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    setUploading&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Trouble uploading file&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing to note is that there is a file size restriction when using the Next API routes, so if you have larger files you may want to move uploading to the client side and utilize pre-signed JWTs which we talk about in &lt;a href=&quot;https://www.pinata.cloud/blog/how-to-upload-to-ipfs-from-the-frontend-with-signed-jwts&quot;&gt;this post.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We now have encrypted uploads! If you upload a file through the app you should get a CID, and if you download the file it will result in a zip folder with all the stuff we just made. This is cool, but how do we decrypt it? How do we let other people decrypt it? Its actually pretty easy! We’re gonna make a new function right below our upload function called &lt;code&gt;decryptFile()&lt;/code&gt;, which will take a &lt;code&gt;fileToDecrypt&lt;/code&gt; CID. First thing we’ll do is fetch that file using our Dedicated Gateway and turn it into a blob.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;decryptFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // First we fetch the file from IPFS using the CID and our Gateway URL, then turn it into a blob&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_GATEWAY_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/ipfs/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?filename=encrypted.zip`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Trouble decrypting file&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can re-create the &lt;code&gt;litNodeClient&lt;/code&gt; and get the auth signature. The beauty of this SDK is that once some signs they will not need to sign again unless they disconnect from the app, making the interactions fairly smooth.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;decryptFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // First we fetch the file from IPFS using the CID and our Gateway URL, then turn it into a blob&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_GATEWAY_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/ipfs/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?filename=encrypted.zip`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // We recreated the litNodeClient and the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;cayenne&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Trouble decrypting file&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just like we used &lt;code&gt;encryptFileAndZipWithMetadata&lt;/code&gt; method, we have a matching method for decryption called &lt;code&gt;decryptZipFileWithMetadata&lt;/code&gt; which we’ll use very similarly to the encryption. The zip folder has everything we need so all we have to pass in is the &lt;code&gt;file&lt;/code&gt; blob, our &lt;code&gt;litNodeClient&lt;/code&gt;, and the recipient &lt;code&gt;authSig&lt;/code&gt;. Piece of cake! From this, we’ll extract the &lt;code&gt;decryptedFile&lt;/code&gt; and &lt;code&gt;metadata&lt;/code&gt; from our request.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;decryptFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // First we fetch the file from IPFS using the CID and our Gateway URL, then turn it into a blob&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_GATEWAY_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/ipfs/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?filename=encrypted.zip`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // We recreated the litNodeClient and the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;cayenne&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Then we simpyl extract the file and metadata from the zip&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // We could do more with this, like try to display it in the app UI if we wanted to&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const { &lt;/span&gt;&lt;span&gt;decryptedFile&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;decryptZipFileWithMetadata&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNodeClient&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        authSig&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Trouble decrypting file&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that’s left to do is deliver the file to the user! There are several ways you could go about it, for example, if you want to display the content to the user such as an image or video you could do so with a bit of formatting. In this example, we’ll just trigger a download to the recipient’s computer. All together we should have the following.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;decryptFile&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; async (&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;) =&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    try {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // First we fetch the file from IPFS using the CID and our Gateway URL, then turn it into a blob&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;NEXT_PUBLIC_GATEWAY_URL&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/ipfs/&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;fileToDecrypt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;?filename=encrypted.zip`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;fileRes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // We recreated the litNodeClient and the authSig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;LitNodeClient&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNetwork&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;cayenne&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      await &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;connect&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;checkAndSignAuthMessage&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        chain&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;ethereum&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Then we simpyl extract the file and metadata from the zip&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // We could do more with this, like try to display it in the app UI if we wanted to&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const { &lt;/span&gt;&lt;span&gt;decryptedFile&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;metadata&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await &lt;/span&gt;&lt;span&gt;LitJsSdk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;decryptZipFileWithMetadata&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        file&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        litNodeClient&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;litNodeClient&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        authSig&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;authSig&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // After we have our dcypted file we can download it&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; new &lt;/span&gt;&lt;span&gt;Blob&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;decryptedFile&lt;/span&gt;&lt;span&gt;], { &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&apos;application/octet-stream&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const &lt;/span&gt;&lt;span&gt;downloadLink&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createElement&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;a&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      downloadLink&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;createObjectURL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;blob&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      downloadLink&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;download&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; metadata&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;;  &lt;/span&gt;&lt;span&gt;// Use the metadata to get the file name and type&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    } catch (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      alert&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Trouble decrypting file&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One small little change we’ll make to the JSX is a text input where someone can paste in a CID and a “Decrypt” button someone can press after pasting in their CID.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;text&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onChange&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) =&amp;gt; &lt;/span&gt;&lt;span&gt;setDecryptionCid&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;px-4 py-2 border-2 border-secondary rounded-3xl text-lg&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  placeholder&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Enter CID to decrypt&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  onClick&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{() =&amp;gt; &lt;/span&gt;&lt;span&gt;decryptFile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;decryptionCid&lt;/span&gt;&lt;span&gt;)}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mr-10 w-[150px] bg-light text-secondary border-2 border-secondary rounded-3xl py-2 px-4 hover:bg-secondary hover:text-light transition-all duration-300 ease-in-out&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;gt;Decrypt&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With all of this together, you should have an app with the following flow!&lt;/p&gt;

    

&lt;p&gt;You can also download and use this exact template &lt;a href=&quot;https://github.com/PinataCloud/pinata-lit-protocol-template&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Going Further&lt;/h2&gt;
&lt;p&gt;This little template is really designed just to get you started and help you understand how Pinata and Lit Protocol work, and there is so much you can do with it. I would highly recommend checking out Lit Protocol’s documentation, in particular their section on all the &lt;a href=&quot;https://developer.litprotocol.com/v3/sdk/access-control/evm/basic-examples&quot;&gt;different access controls&lt;/a&gt; you can do. For example, if you only wanted holders of a particular ERC721 NFT you could use the following.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;accessControlConditions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    contractAddress&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0xA80617371A5f511Bf4c1dDf822E6040acaa63e71&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    standardContractType&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;ERC721&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chain&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;balanceOf&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parameters&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;:userAddress&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    returnValueTest&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      comparator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you could do DAO membership (MolochDAOv2.1, also supports DAOHaus)*&lt;strong&gt;*&lt;a href=&quot;https://developer.litprotocol.com/v3/sdk/access-control/evm/basic-examples#must-be-a-member-of-a-dao-molochdaov21-also-supports-daohaus&quot;&gt;&lt;/a&gt;**&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;accessControlConditions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    contractAddress&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0x50D8EB685a9F262B13F28958aBc9670F06F819d9&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    standardContractType&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;MolochDAOv2.1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chain&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;members&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parameters&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;:userAddress&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    returnValueTest&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      comparator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can even do a simple check if the recipient is a particular wallet address.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;accessControlConditions&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    contractAddress&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    standardContractType&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    chain&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    method&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    parameters&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;:userAddress&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    returnValueTest&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      comparator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0x50e2dac5e78B5905CB09495547452cEE64426db2&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With these building blocks, you could easily build a standalone token gating app, or build a custom solution for your holders. The possibilities are endless!&lt;/p&gt;
&lt;p&gt;Happy Pinning!&lt;/p&gt;</content:encoded></item></channel></rss>