try this again? b4228f69
Steve · 2023-04-23 11:33 20 file(s) · +749 −649
src/components/BaseHead.astro +1 −1
11 11
const siteTitle = `${title} ${titleSeparator} ${siteConfig.title}`;
12 12
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
13 13
const socialImageURL = new URL(ogImage ? ogImage : "/social-card.png", Astro.url).href;
14 -
15 14
---
15 +
16 16
<meta charset="utf-8" />
17 17
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no" />
18 18
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
src/components/OutLinkButton.astro +4 −4
1 1
---
2 -
const { link, site, image } = Astro.props
2 +
const { link, site, image } = Astro.props;
3 3
import { Image } from "@astrojs/image/components";
4 4
---
5 5
6 -
<a class="flex justify-start items-center gap-2 font-bold" href={link} target="_blank">
7 -
  <Image src={image} width={30} height={30} alt={`Link to ${site}`} />
8 -
  Read this post on {site}
6 +
<a class="flex items-center justify-start gap-2 font-bold" href={link} target="_blank">
7 +
	<Image src={image} width={30} height={30} alt={`Link to ${site}`} />
8 +
	Read this post on {site}
9 9
</a>
src/content/post/3d-nft-resume.mdx +20 −13
5 5
tags: ["web3", "nfts", "tutorials", "web development"]
6 6
ogImage: "https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/6410b6848afd85df8fe0a193_2023-01-10_How-to-Create_blog-img-tiny.png"
7 7
---
8 +
8 9
import { Image } from "@astrojs/image/components";
9 10
import pinnie from "../../assets/pinnie.png";
10 -
import OutLinkButton from "../../components/OutLinkButton.astro"
11 +
import OutLinkButton from "../../components/OutLinkButton.astro";
11 12
12 -
<OutLinkButton link="https://www.pinata.cloud/blog/resume-app-nft" site="Pinata" image={pinnie} /> 
13 +
<OutLinkButton link="https://www.pinata.cloud/blog/resume-app-nft" site="Pinata" image={pinnie} />{" "}
13 14
14 15
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?
15 16
35 36
36 37
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:
37 38
38 -
<iframe src="https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html" frameborder="0" height="500px" width="100%" class="hidden sm:block" />
39 +
<iframe
40 +
	src="https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html"
41 +
	frameborder="0"
42 +
	height="500px"
43 +
	width="100%"
44 +
	class="hidden sm:block"
45 +
/>
39 46
<Image
40 -
src="https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/63bd95503c654e14fc1b3b00_Slide%2016_9%20-%203.png"
41 -
alt="Screenshot of web app"
42 -
width={1920}
43 -
aspectRatio={1/1}
44 -
class="sm:hidden"
47 +
	src="https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/63bd95503c654e14fc1b3b00_Slide%2016_9%20-%203.png"
48 +
	alt="Screenshot of web app"
49 +
	width={1920}
50 +
	aspectRatio={1 / 1}
51 +
	class="sm:hidden"
45 52
/>
46 53
47 54
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!
50 57
51 58
```json
52 59
{
53 -
  "name": "Steve's App NFT Resume",
54 -
  "description": "A dynamic NFT resume by Steve",
55 -
  "image": "ipfs://QmTa46bKHxcQCBoNt887X2zNJwAHpAZ93hTXDi9KeJeM4W",
56 -
  "animation_url": "https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html"
60 +
	"name": "Steve's App NFT Resume",
61 +
	"description": "A dynamic NFT resume by Steve",
62 +
	"image": "ipfs://QmTa46bKHxcQCBoNt887X2zNJwAHpAZ93hTXDi9KeJeM4W",
63 +
	"animation_url": "https://stevedsimkins.mypinata.cloud/ipfs/QmdKYQpczE7giv15Yx2tkk1pkbRe862eaLhTR5e7FjhJ8F/index.html"
57 64
}
58 65
```
59 66
60 67
With the metadata.json file complete, Steve uploaded that file to Pinata as well and used the CID as the token URI like so:
61 68
62 69
```javascript
63 -
const URI = "ipfs://QmU85vmit8ShrUpnJFg3wEAMA61GcQB2X5KcgabchDV1kt"
70 +
const URI = "ipfs://QmU85vmit8ShrUpnJFg3wEAMA61GcQB2X5KcgabchDV1kt";
64 71
```
65 72
66 73
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! [Check it out](https://testnets.opensea.io/assets/goerli/0x45602432657d8100119e8633b677043b9022c22b/1) 😎
src/content/post/3d-solana-nfts.mdx +101 −52
7 7
---
8 8
9 9
import { Image } from "@astrojs/image/components";
10 -
import medium from "../../assets/medium.png"
11 -
import OutLinkButton from "../../components/OutLinkButton.astro"
10 +
import medium from "../../assets/medium.png";
11 +
import OutLinkButton from "../../components/OutLinkButton.astro";
12 12
13 -
<OutLinkButton link="https://medium.com/pinata/how-to-scan-and-create-1-1-3d-nfts-on-solana-using-polycam-and-pinata-df513dd87937" site="Medium" image={medium} />
13 +
<OutLinkButton
14 +
	link="https://medium.com/pinata/how-to-scan-and-create-1-1-3d-nfts-on-solana-using-polycam-and-pinata-df513dd87937"
15 +
	site="Medium"
16 +
	image={medium}
17 +
/>
14 18
15 19
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.
16 20
34 38
35 39
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.
36 40
37 -
<div style="padding:216.22% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/703877466?h=9659ba4952" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
41 +
<div style="padding:216.22% 0 0 0;position:relative;">
42 +
	<iframe
43 +
		src="https://player.vimeo.com/video/703877466?h=9659ba4952"
44 +
		style="position:absolute;top:0;left:0;width:100%;height:100%;"
45 +
		frameborder="0"
46 +
		allow="autoplay; fullscreen; picture-in-picture"
47 +
		allowfullscreen
48 +
	></iframe>
49 +
</div>
50 +
<script src="https://player.vimeo.com/api/player.js"></script>
38 51
39 52
After you’re done scanning there will be a processing step that will take all the data and map it into a 3D environment!
40 53
41 -
<div style="padding:216.22% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/703880632?h=52b5f574b7" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
54 +
<div style="padding:216.22% 0 0 0;position:relative;">
55 +
	<iframe
56 +
		src="https://player.vimeo.com/video/703880632?h=52b5f574b7"
57 +
		style="position:absolute;top:0;left:0;width:100%;height:100%;"
58 +
		frameborder="0"
59 +
		allow="autoplay; fullscreen; picture-in-picture"
60 +
		allowfullscreen
61 +
	></iframe>
62 +
</div>
63 +
<script src="https://player.vimeo.com/api/player.js"></script>
42 64
43 65
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.
44 66
45 -
<div style="padding:216.22% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/703884780?h=de95c5de7a" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
67 +
<div style="padding:216.22% 0 0 0;position:relative;">
68 +
	<iframe
69 +
		src="https://player.vimeo.com/video/703884780?h=de95c5de7a"
70 +
		style="position:absolute;top:0;left:0;width:100%;height:100%;"
71 +
		frameborder="0"
72 +
		allow="autoplay; fullscreen; picture-in-picture"
73 +
		allowfullscreen
74 +
	></iframe>
75 +
</div>
76 +
<script src="https://player.vimeo.com/api/player.js"></script>
46 77
47 78
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.
48 79
49 80
Once viewing it in the web browser, we can download the model by clicking “export.”
50 81
51 82
<Image
52 -
src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Olex5OAd3sMaugwF_iWGig.png"
53 -
alt="screenshot of room capture"
54 -
width={1920}
55 -
aspectRatio={16/9}
83 +
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Olex5OAd3sMaugwF_iWGig.png"
84 +
	alt="screenshot of room capture"
85 +
	width={1920}
86 +
	aspectRatio={16 / 9}
56 87
/>
57 88
58 89
From there we want to select the GLTF format and start the download!
59 90
60 91
<Image
61 -
src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*0z9cGRbGbAWoflqk7rU0yw.png"
62 -
alt="screenshot of aerial export screen"
63 -
width={1920}
64 -
aspectRatio={16/9}
92 +
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*0z9cGRbGbAWoflqk7rU0yw.png"
93 +
	alt="screenshot of aerial export screen"
94 +
	width={1920}
95 +
	aspectRatio={16 / 9}
65 96
/>
66 97
67 98
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.
68 99
69 -
<div style="padding:216.22% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/704181596?h=92cada3864" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
100 +
<div style="padding:216.22% 0 0 0;position:relative;">
101 +
	<iframe
102 +
		src="https://player.vimeo.com/video/704181596?h=92cada3864"
103 +
		style="position:absolute;top:0;left:0;width:100%;height:100%;"
104 +
		frameborder="0"
105 +
		allow="autoplay; fullscreen; picture-in-picture"
106 +
		allowfullscreen
107 +
	></iframe>
108 +
</div>
109 +
<script src="https://player.vimeo.com/api/player.js"></script>
70 110
71 111
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!
72 112
73 -
<div style="padding:216.22% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/704185926?h=2a929c3d73" style="position:absolute;top:0;left:0;width:100%;height:100%;" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
113 +
<div style="padding:216.22% 0 0 0;position:relative;">
114 +
	<iframe
115 +
		src="https://player.vimeo.com/video/704185926?h=2a929c3d73"
116 +
		style="position:absolute;top:0;left:0;width:100%;height:100%;"
117 +
		frameborder="0"
118 +
		allow="autoplay; fullscreen; picture-in-picture"
119 +
		allowfullscreen
120 +
	></iframe>
121 +
</div>
122 +
<script src="https://player.vimeo.com/api/player.js"></script>
74 123
75 124
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!
76 125
89 138
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!
90 139
91 140
<Image
92 -
src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*v-x8ltZQ0ep6vlmqoPn2WA.png"
93 -
alt="uploading screen at pinata"
94 -
width={1920}
95 -
aspectRatio={16/9}
141 +
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*v-x8ltZQ0ep6vlmqoPn2WA.png"
142 +
	alt="uploading screen at pinata"
143 +
	width={1920}
144 +
	aspectRatio={16 / 9}
96 145
/>
97 146
98 147
Then we just need to choose a subdomain and make sure its available. If it is, click next!
119 168
120 169
```json
121 170
{
122 -
  "name": "3D Pinnie",
123 -
  "symbol": "PIN",
124 -
  "description": "A 3D scan of Pinnie taken with Polycam",
125 -
  "seller_fee_basis_points": 0,
126 -
  "image": "null",
127 -
  "animation_url": "https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb",
128 -
  "external_url": "https://pinata.cloud",
129 -
  "collection": {
130 -
     "name": "Pinnie's 3D NFTs",
131 -
     "family": "3D NFTs"
132 -
  },
133 -
  "properties": {
134 -
    "files": [
135 -
      {
136 -
        "uri": "null",
137 -
        "type": "image/png"
138 -
      },
139 -
      {
140 -
        "uri": "https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb",
141 -
        "type": "vr/glb"
142 -
      }
143 -
    ],
144 -
    "category": "3D",
145 -
    "creators": [
146 -
      {
147 -
        "address": "D8KLFUfnRwGsMt6n56FzkyRYmVUQXiRnJWFV7rZYCYdd",
148 -
        "share": 100
149 -
      }
150 -
    ]
151 -
  }
171 +
	"name": "3D Pinnie",
172 +
	"symbol": "PIN",
173 +
	"description": "A 3D scan of Pinnie taken with Polycam",
174 +
	"seller_fee_basis_points": 0,
175 +
	"image": "null",
176 +
	"animation_url": "https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb",
177 +
	"external_url": "https://pinata.cloud",
178 +
	"collection": {
179 +
		"name": "Pinnie's 3D NFTs",
180 +
		"family": "3D NFTs"
181 +
	},
182 +
	"properties": {
183 +
		"files": [
184 +
			{
185 +
				"uri": "null",
186 +
				"type": "image/png"
187 +
			},
188 +
			{
189 +
				"uri": "https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb",
190 +
				"type": "vr/glb"
191 +
			}
192 +
		],
193 +
		"category": "3D",
194 +
		"creators": [
195 +
			{
196 +
				"address": "D8KLFUfnRwGsMt6n56FzkyRYmVUQXiRnJWFV7rZYCYdd",
197 +
				"share": 100
198 +
			}
199 +
		]
200 +
	}
152 201
}
153 202
```
154 203
156 205
157 206
```json
158 207
{
159 -
  "uri": "https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb",
160 -
  "type": "vr/glb"
208 +
	"uri": "https://pinnieblog.mypinata.cloud/ipfs/QmWmcX9ikvNTJtCmuX9oWiUvTABQZ6tC6UARqa7ozBh2Ry?filename=Pinnie.glb",
209 +
	"type": "vr/glb"
161 210
}
162 211
```
163 212
src/content/post/arc-internet.mdx +36 −35
2 2
title: "Arc: The Internet Computer"
3 3
publishDate: "08 Mar 2023"
4 4
description: "How the Arc web browser is paving the way for the future of consumer computers"
5 -
tags: ["arc", "web browsers", "tech philosophy", "internet", ]
5 +
tags: ["arc", "web browsers", "tech philosophy", "internet"]
6 6
ogImage: "https://res.cloudinary.com/df9dofjus/image/upload/v1678385122/arc-browser-blog-post/opluqtxq1ceoigepyjwf.png"
7 7
---
8 8
9 9
import { Image } from "@astrojs/image/components";
10 10
11 11
<Image
12 -
src="https://res.cloudinary.com/df9dofjus/image/upload/v1678295026/arc-browser-blog-post/s3udhcext52umxrydoum.png"
13 -
alt="Arc Logo"
14 -
width={1920}
15 -
aspectRatio={16/9}
12 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1678295026/arc-browser-blog-post/s3udhcext52umxrydoum.png"
13 +
	alt="Arc Logo"
14 +
	width={1920}
15 +
	aspectRatio={16 / 9}
16 16
/>
17 17
18 18
## 20 Years of the Same Thing
20 20
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.
21 21
22 22
## Tabs, Folders, and Spaces
23 +
23 24
<Image
24 -
src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294127/arc-browser-blog-post/skdp4gdysro6bmmipgkb.png"
25 -
alt="Tabs folders and spaces in arc"
26 -
width={1920}
27 -
aspectRatio={16/9}
25 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294127/arc-browser-blog-post/skdp4gdysro6bmmipgkb.png"
26 +
	alt="Tabs folders and spaces in arc"
27 +
	width={1920}
28 +
	aspectRatio={16 / 9}
28 29
/>
29 30
30 31
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.
32 +
31 33
<Image
32 -
src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294257/arc-browser-blog-post/pkslbdu6vwxmhcsfpiah.png"
33 -
alt="View of spaces in Arc"
34 -
width={1920}
35 -
aspectRatio={16/9}
34 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294257/arc-browser-blog-post/pkslbdu6vwxmhcsfpiah.png"
35 +
	alt="View of spaces in Arc"
36 +
	width={1920}
37 +
	aspectRatio={16 / 9}
36 38
/>
37 39
38 40
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.
39 41
40 42
<Image
41 -
src="https://res.cloudinary.com/df9dofjus/image/upload/v1678294333/arc-browser-blog-post/c3rjxfzg9fd2f5wjkdje.png"
42 -
alt="View of favorites in Arc"
43 -
width={912}
44 -
height={528}
43 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/v1678294333/arc-browser-blog-post/c3rjxfzg9fd2f5wjkdje.png"
44 +
	alt="View of favorites in Arc"
45 +
	width={912}
46 +
	height={528}
45 47
/>
46 48
47 49
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.
51 53
## Split View
52 54
53 55
<Image
54 -
src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294406/arc-browser-blog-post/riqyizem3k19rq7gjcot.png"
55 -
alt="Split view in Arc"
56 -
width={1920}
57 -
aspectRatio={16/9}
56 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678294406/arc-browser-blog-post/riqyizem3k19rq7gjcot.png"
57 +
	alt="Split view in Arc"
58 +
	width={1920}
59 +
	aspectRatio={16 / 9}
58 60
/>
59 61
60 62
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.
70 72
## Easels
71 73
72 74
<Image
73 -
src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678295775/arc-browser-blog-post/jwihj6ivleo9hx90ikag.png"
74 -
alt="Easels in Arc"
75 -
width={1920}
76 -
aspectRatio={16/9}
75 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678295775/arc-browser-blog-post/jwihj6ivleo9hx90ikag.png"
76 +
	alt="Easels in Arc"
77 +
	width={1920}
78 +
	aspectRatio={16 / 9}
77 79
/>
78 80
79 81
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 & 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!
85 87
## Boosts
86 88
87 89
<Image
88 -
src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678296116/arc-browser-blog-post/ow0z8neu9fnv3j21uor0.png"
89 -
alt="A view of the Boosts menu in Arc"
90 -
width={1920}
91 -
aspectRatio={16/9}
90 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678296116/arc-browser-blog-post/ow0z8neu9fnv3j21uor0.png"
91 +
	alt="A view of the Boosts menu in Arc"
92 +
	width={1920}
93 +
	aspectRatio={16 / 9}
92 94
/>
93 95
94 96
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.
95 97
96 98
<Image
97 -
src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678296633/arc-browser-blog-post/m0kdu35asmm6p43t7ml8.png"
98 -
alt="Boost being made in Arc for Tiwtter"
99 -
width={1920}
100 -
aspectRatio={16/9}
99 +
	src="https://res.cloudinary.com/df9dofjus/image/upload/w_1920,h_1080,c_fill/v1678296633/arc-browser-blog-post/m0kdu35asmm6p43t7ml8.png"
100 +
	alt="Boost being made in Arc for Tiwtter"
101 +
	width={1920}
102 +
	aspectRatio={16 / 9}
101 103
/>
102 104
103 105
As someone who is obsessed with customizing the personal computer environment, I absolutely love Boosts. [Nord](https://nordtheme.com/) 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.
110 112
111 113
## Internet Computers
112 114
113 -
That last point brings us to the concept of Internet Computers that Josh, the CEO of The Browser Company, talks about in [videofile_ : the internet computer](https://youtu.be/v0160IirdL4). 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
115 +
That last point brings us to the concept of Internet Computers that Josh, the CEO of The Browser Company, talks about in [videofile\_ : the internet computer](https://youtu.be/v0160IirdL4). 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
114 116
115 117
> 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.
116 -
117 118
118 119
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!
119 120
src/content/post/beginners-guide-to-ipfs.mdx +14 −14
7 7
---
8 8
9 9
import { Image } from "@astrojs/image/components";
10 -
import bueno from "../../assets/bueno.png"
11 -
import OutLinkButton from "../../components/OutLinkButton.astro"
10 +
import bueno from "../../assets/bueno.png";
11 +
import OutLinkButton from "../../components/OutLinkButton.astro";
12 12
13 13
<OutLinkButton link="https://bueno.art/blog/pinata-ipfs-guide" site="Bueno" image={bueno} />
14 14
20 20
21 21
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.
22 22
23 -
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: 
23 +
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:
24 24
25 25
```
26 -
https://storageservice.com/friend/crazycar1.png 
26 +
https://storageservice.com/friend/crazycar1.png
27 27
```
28 28
29 29
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 [IPFS](https://ipfs.io) - the InterPlanetary File System.
35 35
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.
36 36
37 37
<Image
38 -
src="https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fe4cfc48a5e05d952dd2c2_IPFS.png"
39 -
alt="bueno graphic"
40 -
width={1920}
41 -
aspectRatio={9/16}
38 +
	src="https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fe4cfc48a5e05d952dd2c2_IPFS.png"
39 +
	alt="bueno graphic"
40 +
	width={1920}
41 +
	aspectRatio={9 / 16}
42 42
/>
43 43
44 44
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.
45 45
46 46
<Image
47 -
src="https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fe4d182d482c2b9c29f199_IPFS-1.png"
48 -
alt="ipfs node graphic"
49 -
width={1920}
50 -
aspectRatio={16/9}
47 +
	src="https://assets-global.website-files.com/6171adb6a942ed69f5e6b5ee/62fe4d182d482c2b9c29f199_IPFS-1.png"
48 +
	alt="ipfs node graphic"
49 +
	width={1920}
50 +
	aspectRatio={16 / 9}
51 51
/>
52 52
53 -
## How does IPFS protect your NFTs? 
53 +
## How does IPFS protect your NFTs?
54 54
55 55
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:
56 56
58 58
QmRAuxeMnsjPsbwW8LkKtk6Nh6MoqTvyKwP3zwuwJnB2yP
59 59
```
60 60
61 -
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. 
61 +
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.
62 62
63 63
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.
64 64
src/content/post/case-for-ipfs-on-l1-chains.mdx +7 −4
6 6
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*4xA96GrA9iLYMp5vorcjyQ.jpeg"
7 7
---
8 8
9 -
import medium from "../../assets/medium.png"
10 -
import OutLinkButton from "../../components/OutLinkButton.astro"
9 +
import medium from "../../assets/medium.png";
10 +
import OutLinkButton from "../../components/OutLinkButton.astro";
11 11
12 -
<OutLinkButton link="https://medium.com/pinata/a-case-for-ipfs-on-layer-1-blockchains-like-solana-aptos-and-sui-165a9732c214" site="Medium" image={medium} />
13 -
12 +
<OutLinkButton
13 +
	link="https://medium.com/pinata/a-case-for-ipfs-on-layer-1-blockchains-like-solana-aptos-and-sui-165a9732c214"
14 +
	site="Medium"
15 +
	image={medium}
16 +
/>
14 17
15 18
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.
16 19
src/content/post/how-to-mint-on-sui.mdx +131 −126
5 5
tags: ["web3", "ipfs", "nfts", "tutorials", "web development"]
6 6
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*JbX3kWI20G3EaKJNgXfQiQ.png"
7 7
---
8 -
import { Image } from "@astrojs/image/components";
9 -
import medium from "../../assets/medium.png"
10 -
import OutLinkButton from "../../components/OutLinkButton.astro"
11 8
12 -
<OutLinkButton link="https://medium.com/pinata/how-to-mint-an-nft-on-sui-using-pinata-and-the-sui-js-sdk-4386655e403" site="Medium" image={medium} />
9 +
import { Image } from "@astrojs/image/components";
10 +
import medium from "../../assets/medium.png";
11 +
import OutLinkButton from "../../components/OutLinkButton.astro";
13 12
13 +
<OutLinkButton
14 +
	link="https://medium.com/pinata/how-to-mint-an-nft-on-sui-using-pinata-and-the-sui-js-sdk-4386655e403"
15 +
	site="Medium"
16 +
	image={medium}
17 +
/>
14 18
15 19
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!
16 20
25 29
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.”
26 30
27 31
<Image
28 -
src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*TF200pv3qCMx41dHZoE_kg@2x.png"
29 -
alt="pinata files page"
30 -
width={1920}
31 -
aspectRatio={16/9}
32 +
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*TF200pv3qCMx41dHZoE_kg@2x.png"
33 +
	alt="pinata files page"
34 +
	width={1920}
35 +
	aspectRatio={16 / 9}
32 36
/>
33 37
34 38
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.
35 39
36 40
<Image
37 -
src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*68MMViE3btD8hHuxaTbEzg@2x.png"
38 -
alt="pinata files page"
39 -
width={1920}
40 -
aspectRatio={16/9}
41 +
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*68MMViE3btD8hHuxaTbEzg@2x.png"
42 +
	alt="pinata files page"
43 +
	width={1920}
44 +
	aspectRatio={16 / 9}
41 45
/>
42 46
43 47
## Code Setup with the Sui JS SDK
58 62
59 63
```json
60 64
{
61 -
  "name": "sui-nft",
62 -
  "type": "module",
63 -
  "version": "1.0.0",
64 -
  "description": "",
65 -
  "main": "index.js",
66 -
  "scripts": {
67 -
    "test": "echo \"Error: no test specified\" && exit 1"
68 -
  },
69 -
  "keywords": [],
70 -
  "author": "",
71 -
  "license": "ISC",
72 -
  "dependencies": {
73 -
    "@mysten/sui.js": "^0.26.1"
74 -
  }
65 +
	"name": "sui-nft",
66 +
	"type": "module",
67 +
	"version": "1.0.0",
68 +
	"description": "",
69 +
	"main": "index.js",
70 +
	"scripts": {
71 +
		"test": "echo \"Error: no test specified\" && exit 1"
72 +
	},
73 +
	"keywords": [],
74 +
	"author": "",
75 +
	"license": "ISC",
76 +
	"dependencies": {
77 +
		"@mysten/sui.js": "^0.26.1"
78 +
	}
75 79
}
76 80
```
77 81
88 92
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.
89 93
90 94
```javascript
91 -
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from '@mysten/sui.js';
95 +
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from "@mysten/sui.js";
92 96
```
93 97
94 98
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.
95 99
96 100
```javascript
97 -
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from '@mysten/sui.js';
101 +
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from "@mysten/sui.js";
98 102
99 -
const keypair = new Ed25519Keypair()
100 -
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString()
101 -
console.log(address)
103 +
const keypair = new Ed25519Keypair();
104 +
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString();
105 +
console.log(address);
102 106
```
103 107
104 108
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!
110 114
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.”
111 115
112 116
```javascript
113 -
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from '@mysten/sui.js';
117 +
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from "@mysten/sui.js";
114 118
115 119
//Create keypair
116 -
const keypair = new Ed25519Keypair()
117 -
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString()
118 -
console.log(address)
120 +
const keypair = new Ed25519Keypair();
121 +
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString();
122 +
console.log(address);
119 123
120 -
//Create network connection 
124 +
//Create network connection
121 125
const provider = new JsonRpcProvider(Network.DEVNET);
122 126
```
123 127
124 128
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.
125 129
126 130
```javascript
127 -
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from '@mysten/sui.js';
131 +
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from "@mysten/sui.js";
128 132
129 133
//Create keypair
130 -
const keypair = new Ed25519Keypair()
131 -
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString()
132 -
console.log(address)
134 +
const keypair = new Ed25519Keypair();
135 +
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString();
136 +
console.log(address);
133 137
134 -
//Create network connection 
138 +
//Create network connection
135 139
const provider = new JsonRpcProvider(Network.DEVNET);
136 140
137 141
// Get Sui from faucet
138 -
const fund = await provider.requestSuiFromFaucet(address)
139 -
console.log(fund)
142 +
const fund = await provider.requestSuiFromFaucet(address);
143 +
console.log(fund);
140 144
```
141 145
142 146
Let’s run the node mint-nft.js command now and see what we get!
182 186
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.
183 187
184 188
```javascript
185 -
// Merge two of the Sui coin objects 
186 -
const coin1 = fund.transferred_gas_objects[0].id
187 -
const coin2 = fund.transferred_gas_objects[1].id
189 +
// Merge two of the Sui coin objects
190 +
const coin1 = fund.transferred_gas_objects[0].id;
191 +
const coin2 = fund.transferred_gas_objects[1].id;
188 192
const signer = new RawSigner(keypair, provider);
189 193
const mergeTxn = await signer.mergeCoin({
190 -
  primaryCoin: coin1,
191 -
  coinToMerge: coin2,
192 -
  gasBudget: 1000,
194 +
	primaryCoin: coin1,
195 +
	coinToMerge: coin2,
196 +
	gasBudget: 1000,
193 197
});
194 -
console.log('MergeCoin txn', mergeTxn);
198 +
console.log("MergeCoin txn", mergeTxn);
195 199
```
200 +
196 201
There’s quite a bit going on here so let’s break it down.
197 202
198 203
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.
202 207
```javascript
203 208
// Pause function
204 209
const wait = async (time) => {
205 -
  return new Promise((resolve, reject) => {
206 -
    setTimeout(() => {
207 -
      resolve();
208 -
    }, time)
209 -
  });
210 -
}
210 +
	return new Promise((resolve, reject) => {
211 +
		setTimeout(() => {
212 +
			resolve();
213 +
		}, time);
214 +
	});
215 +
};
211 216
```
212 217
213 218
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.
214 219
215 220
```javascript
216 -
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from '@mysten/sui.js';
221 +
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from "@mysten/sui.js";
217 222
218 223
// Generate a new Keypair
219 224
const keypair = new Ed25519Keypair();
220 -
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString()
221 -
console.log(address)
225 +
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString();
226 +
console.log(address);
222 227
223 -
// Create Network Connection and receive airdrop 
228 +
// Create Network Connection and receive airdrop
224 229
const provider = new JsonRpcProvider(Network.DEVNET);
225 230
226 231
// Get Sui from faucet
227 -
const fund = await provider.requestSuiFromFaucet(address)
228 -
console.log(fund)
232 +
const fund = await provider.requestSuiFromFaucet(address);
233 +
console.log(fund);
229 234
230 235
// Pause function
231 236
const wait = async (time) => {
232 -
  return new Promise((resolve, reject) => {
233 -
    setTimeout(() => {
234 -
      resolve();
235 -
    }, time)
236 -
  });
237 -
}
237 +
	return new Promise((resolve, reject) => {
238 +
		setTimeout(() => {
239 +
			resolve();
240 +
		}, time);
241 +
	});
242 +
};
238 243
239 -
await wait(3000)
244 +
await wait(3000);
240 245
241 -
// Merge two of the Sui coin objects 
242 -
const coin1 = fund.transferred_gas_objects[1].id
243 -
const coin2 = fund.transferred_gas_objects[2].id
246 +
// Merge two of the Sui coin objects
247 +
const coin1 = fund.transferred_gas_objects[1].id;
248 +
const coin2 = fund.transferred_gas_objects[2].id;
244 249
const signer = new RawSigner(keypair, provider);
245 250
const mergeTxn = await signer.mergeCoin({
246 -
  primaryCoin: coin1,
247 -
  coinToMerge: coin2,
248 -
  gasBudget: 1000,
251 +
	primaryCoin: coin1,
252 +
	coinToMerge: coin2,
253 +
	gasBudget: 1000,
249 254
});
250 -
console.log('MergeCoin txn', mergeTxn);
255 +
console.log("MergeCoin txn", mergeTxn);
251 256
```
252 257
253 258
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!
255 260
```javascript
256 261
// Call to Mint NFT
257 262
const mintTxn = await signer.executeMoveCall({
258 -
  packageObjectId: '0x2',
259 -
  module: 'devnet_nft',
260 -
  function: 'mint',
261 -
  typeArguments: [],
262 -
  arguments: [
263 -
    'gm',
264 -
    'A nice gm brought to you by Pinata and Sui',
265 -
    'ipfs://QmZhnkimthxvL32vin2mrQvnhN8ZbWFMvKMxRqHEq7dPz3',
266 -
  ],
267 -
  gasBudget: 10000
263 +
	packageObjectId: "0x2",
264 +
	module: "devnet_nft",
265 +
	function: "mint",
266 +
	typeArguments: [],
267 +
	arguments: [
268 +
		"gm",
269 +
		"A nice gm brought to you by Pinata and Sui",
270 +
		"ipfs://QmZhnkimthxvL32vin2mrQvnhN8ZbWFMvKMxRqHEq7dPz3",
271 +
	],
272 +
	gasBudget: 10000,
268 273
});
269 -
console.log('mint transaction:', mintTxn);
274 +
console.log("mint transaction:", mintTxn);
270 275
```
271 276
272 277
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!
275 280
276 281
```javascript
277 282
// View NFT
278 -
const nftId = mintTxn.effects.effects.created[0].reference.objectId.toString()
279 -
console.log(`View NFT: https://explorer.sui.io/object/${nftId}?network=devnet`)
283 +
const nftId = mintTxn.effects.effects.created[0].reference.objectId.toString();
284 +
console.log(`View NFT: https://explorer.sui.io/object/${nftId}?network=devnet`);
280 285
```
281 286
282 287
Now let’s look at our full code to make sure everything is good, then run it!
283 288
284 289
```javascript
285 -
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from '@mysten/sui.js';
290 +
import { Ed25519Keypair, JsonRpcProvider, Network, RawSigner } from "@mysten/sui.js";
286 291
287 292
// Generate a new Keypair
288 293
const keypair = new Ed25519Keypair();
289 -
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString()
290 -
console.log(address)
294 +
const address = "0x" + keypair.getPublicKey().toSuiAddress().toString();
295 +
console.log(address);
291 296
292 -
// Create Network Connection and receive airdrop 
297 +
// Create Network Connection and receive airdrop
293 298
const provider = new JsonRpcProvider(Network.DEVNET);
294 299
295 300
// Get Sui from faucet
296 -
const fund = await provider.requestSuiFromFaucet(address)
297 -
console.log(fund)
301 +
const fund = await provider.requestSuiFromFaucet(address);
302 +
console.log(fund);
298 303
299 304
// Pause function
300 305
const wait = async (time) => {
301 -
  return new Promise((resolve, reject) => {
302 -
    setTimeout(() => {
303 -
      resolve();
304 -
    }, time)
305 -
  });
306 -
}
306 +
	return new Promise((resolve, reject) => {
307 +
		setTimeout(() => {
308 +
			resolve();
309 +
		}, time);
310 +
	});
311 +
};
307 312
308 -
await wait(3000)
313 +
await wait(3000);
309 314
310 -
// Merge two of the Sui coin objects 
311 -
const coin1 = fund.transferred_gas_objects[1].id
312 -
const coin2 = fund.transferred_gas_objects[2].id
315 +
// Merge two of the Sui coin objects
316 +
const coin1 = fund.transferred_gas_objects[1].id;
317 +
const coin2 = fund.transferred_gas_objects[2].id;
313 318
const signer = new RawSigner(keypair, provider);
314 319
const mergeTxn = await signer.mergeCoin({
315 -
  primaryCoin: coin1,
316 -
  coinToMerge: coin2,
317 -
  gasBudget: 1000,
320 +
	primaryCoin: coin1,
321 +
	coinToMerge: coin2,
322 +
	gasBudget: 1000,
318 323
});
319 -
console.log('MergeCoin txn', mergeTxn);
324 +
console.log("MergeCoin txn", mergeTxn);
320 325
321 326
// Call to Mint NFT
322 327
const mintTxn = await signer.executeMoveCall({
323 -
  packageObjectId: '0x2',
324 -
  module: 'devnet_nft',
325 -
  function: 'mint',
326 -
  typeArguments: [],
327 -
  arguments: [
328 -
    'gm',
329 -
    'A nice gm brought to you by Pinata and Sui',
330 -
    'ipfs://QmZhnkimthxvL32vin2mrQvnhN8ZbWFMvKMxRqHEq7dPz3',
331 -
  ],
332 -
  gasBudget: 10000
328 +
	packageObjectId: "0x2",
329 +
	module: "devnet_nft",
330 +
	function: "mint",
331 +
	typeArguments: [],
332 +
	arguments: [
333 +
		"gm",
334 +
		"A nice gm brought to you by Pinata and Sui",
335 +
		"ipfs://QmZhnkimthxvL32vin2mrQvnhN8ZbWFMvKMxRqHEq7dPz3",
336 +
	],
337 +
	gasBudget: 10000,
333 338
});
334 -
console.log('mint transaction:', mintTxn);
339 +
console.log("mint transaction:", mintTxn);
335 340
336 341
// View NFT
337 -
const nftId = mintTxn.effects.effects.created[0].reference.objectId.toString()
338 -
console.log(`View NFT: https://explorer.sui.io/object/${nftId}?network=devnet`)
342 +
const nftId = mintTxn.effects.effects.created[0].reference.objectId.toString();
343 +
console.log(`View NFT: https://explorer.sui.io/object/${nftId}?network=devnet`);
339 344
```
340 345
341 346
If all works as it should you’ll get a link and then you should see your final NFT!
342 347
343 348
<Image
344 -
src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*oCtNaZK3LOx807tA8IEldg@2x.png"
345 -
alt="pinata files page"
346 -
width={1920}
347 -
aspectRatio={16/9}
349 +
	src="https://miro.medium.com/v2/resize:fit:4800/format:webp/1*oCtNaZK3LOx807tA8IEldg@2x.png"
350 +
	alt="pinata files page"
351 +
	width={1920}
352 +
	aspectRatio={16 / 9}
348 353
/>
349 354
350 355
## You did it!! 🎉
src/content/post/how-to-offset-NFT-emissions.mdx +266 −256
5 5
tags: ["web3", "nfts", "tutorials", "web development", "tech philosophy"]
6 6
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*KxoVDEZFH3mJrlfeguMYjg.jpeg"
7 7
---
8 +
8 9
import { Image } from "@astrojs/image/components";
9 -
import medium from "../../assets/medium.png"
10 -
import OutLinkButton from "../../components/OutLinkButton.astro"
10 +
import medium from "../../assets/medium.png";
11 +
import OutLinkButton from "../../components/OutLinkButton.astro";
11 12
12 -
<OutLinkButton link="https://medium.com/pinata/how-to-offset-your-nft-project-carbon-emissions-with-aerial-b5b4b95faba0" site="Medium" image={medium} />
13 -
13 +
<OutLinkButton
14 +
	link="https://medium.com/pinata/how-to-offset-your-nft-project-carbon-emissions-with-aerial-b5b4b95faba0"
15 +
	site="Medium"
16 +
	image={medium}
17 +
/>
14 18
15 19
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 [Submarine.me](https://submarine.me)). 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.
16 20
17 21
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.
18 22
19 23
<Image
20 -
src="https://cdn-images-1.medium.com/max/2400/1*xQEP67xsPsQ4vU7wjKLFBw.png"
21 -
alt="ethereum carbon emissions chart"
22 -
width={1920}
23 -
aspectRatio={16/9}
24 +
	src="https://cdn-images-1.medium.com/max/2400/1*xQEP67xsPsQ4vU7wjKLFBw.png"
25 +
	alt="ethereum carbon emissions chart"
26 +
	width={1920}
27 +
	aspectRatio={16 / 9}
24 28
/>
25 29
26 30
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?
36 40
```
37 41
38 42
<Image
39 -
src="https://cdn-images-1.medium.com/max/2000/1*IPAefM4T_NiRygdri_DzhA.png"
40 -
alt="screenshot of widget"
41 -
width={1920}
42 -
aspectRatio={6/2}
43 +
	src="https://cdn-images-1.medium.com/max/2000/1*IPAefM4T_NiRygdri_DzhA.png"
44 +
	alt="screenshot of widget"
45 +
	width={1920}
46 +
	aspectRatio={6 / 2}
43 47
/>
44 -
45 48
46 49
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!
47 50
57 60
58 61
```javascript
59 62
function App() {
60 -
  return (
61 -
    <div className="App">
62 -
    </div>
63 -
  );
63 +
	return <div className="App"></div>;
64 64
}
65 65
66 66
export default App;
70 70
71 71
```javascript
72 72
const Aerial = () => {
73 -
  return (
74 -
    <h1>Aerial</h1>
75 -
  )
76 -
}
73 +
	return <h1>Aerial</h1>;
74 +
};
77 75
78 76
export default Aerial;
79 -
80 77
```
81 78
82 79
Now let’s import the new component to our App.js
83 -
84 80
85 81
```javascript
86 -
import Aerial from "./Aerial"
82 +
import Aerial from "./Aerial";
87 83
88 84
function App() {
89 -
  return (
90 -
    <div className="App">
91 -
      <Aerial />
92 -
    </div>
93 -
  );
85 +
	return (
86 +
		<div className="App">
87 +
			<Aerial />
88 +
		</div>
89 +
	);
94 90
}
95 91
96 92
export default App;
112 108
{
113 109
 "co2": <emissions in CO2>,
114 110
 "gas": <gas used>,
115 -
 "transactions": <number of transactions>, 
111 +
 "transactions": <number of transactions>,
116 112
 "credits": <credits required to offset>,
117 113
 "cost": <cost to offset in USD>,
118 114
 "credits_purchased": <number of credits already purchased>,
129 125
Now make sure to import it into the top of our Aerial component like so
130 126
131 127
```javascript
132 -
import axios from "axios"
128 +
import axios from "axios";
133 129
```
134 130
135 131
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.
136 132
137 133
```javascript
138 134
const getEmissionsData = async () => {
139 -
    try{
140 -
      const response = await axios.get("https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b")
141 -
      console.log(response.data)
142 -
    } catch (error) {
143 -
      console.log(error)
144 -
    }
145 -
  }
135 +
	try {
136 +
		const response = await axios.get(
137 +
			"https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
138 +
		);
139 +
		console.log(response.data);
140 +
	} catch (error) {
141 +
		console.log(error);
142 +
	}
143 +
};
146 144
```
147 145
148 146
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
154 152
Then we need to run the function inside useEffect,
155 153
156 154
```javascript
157 -
useEffect(()=> {
158 -
    getEmissionsData()
159 -
  }, [])
155 +
useEffect(() => {
156 +
	getEmissionsData();
157 +
}, []);
160 158
```
161 159
162 160
This is what our code will look like with everything in place:
163 161
164 162
```javascript
165 -
166 163
import { useEffect } from "react";
167 164
import axios from "axios";
168 165
169 166
const Aerial = () => {
167 +
	const getEmissionsData = async () => {
168 +
		try {
169 +
			const response = await axios.get(
170 +
				"https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
171 +
			);
172 +
			console.log(response.data);
173 +
		} catch (error) {
174 +
			console.log(error);
175 +
		}
176 +
	};
170 177
171 -
  const getEmissionsData = async () => {
172 -
    try{
173 -
      const response = await axios.get("https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b")
174 -
      console.log(response.data)
175 -
    } catch (error) {
176 -
      console.log(error)
177 -
    }
178 -
  }
178 +
	useEffect(() => {
179 +
		getEmissionsData();
180 +
	}, []);
179 181
180 -
  useEffect(()=> {
181 -
    getEmissionsData()
182 -
  }, [])
183 -
184 -
  return (
185 -
    <div className="aerial-container">
186 -
      <h1>Aerial</h1>
187 -
    </div>
188 -
  )
189 -
}
182 +
	return (
183 +
		<div className="aerial-container">
184 +
			<h1>Aerial</h1>
185 +
		</div>
186 +
	);
187 +
};
190 188
191 189
export default Aerial;
192 190
```
194 192
If we run the app and check the dev console, we can see our data!
195 193
196 194
<Image
197 -
src="https://cdn-images-1.medium.com/max/2000/1*UrdopTgDhErJObLI3V8DwQ.png"
198 -
alt="dev tools"
199 -
width={1920}
200 -
aspectRatio={16/4}
195 +
	src="https://cdn-images-1.medium.com/max/2000/1*UrdopTgDhErJObLI3V8DwQ.png"
196 +
	alt="dev tools"
197 +
	width={1920}
198 +
	aspectRatio={16 / 4}
201 199
/>
202 200
203 201
Now that we have the data, it’s as simple as displaying it so users on our website can see it!
205 203
To store the data we’ll import the useState hook at the top of our app along with useEffect
206 204
207 205
```javascript
208 -
import { useEffect, useState } from "react"
206 +
import { useEffect, useState } from "react";
209 207
```
210 208
211 209
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.
212 210
213 211
```javascript
214 -
const [emissionsData, setEmissionsData] = useState([])
212 +
const [emissionsData, setEmissionsData] = useState([]);
215 213
```
216 214
217 215
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
218 216
219 217
```javascript
220 218
const getEmissionsData = async () => {
221 -
    try{
222 -
      const response = await axios.get("https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b")
223 -
      console.log(response.data)
224 -
      setEmissionsData(response.data)
225 -
    } catch (error) {
226 -
      console.log(error)
227 -
    }
228 -
  }
219 +
	try {
220 +
		const response = await axios.get(
221 +
			"https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
222 +
		);
223 +
		console.log(response.data);
224 +
		setEmissionsData(response.data);
225 +
	} catch (error) {
226 +
		console.log(error);
227 +
	}
228 +
};
229 229
```
230 230
231 231
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
232 232
233 233
```javascript
234 234
const loading = () => (
235 -
    <div className="loading-container">
236 -
      <h1>Loading</h1>
237 -
    </div>
238 -
  )
235 +
	<div className="loading-container">
236 +
		<h1>Loading</h1>
237 +
	</div>
238 +
);
239 239
240 240
const emissionsComponent = () => {
241 -
    
242 -
    return (
243 -
      <div className="data-container">
244 -
       <h1>Data goes here</h1>
245 -
      </div>
246 -
    )
247 -
  }
241 +
	return (
242 +
		<div className="data-container">
243 +
			<h1>Data goes here</h1>
244 +
		</div>
245 +
	);
246 +
};
248 247
```
249 248
250 249
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
251 250
252 251
```javascript
253 -
const [emissionsData, setEmissionsData] = useState([])
254 -
const [isLoading, setIsLoading] = useState(false)
252 +
const [emissionsData, setEmissionsData] = useState([]);
253 +
const [isLoading, setIsLoading] = useState(false);
255 254
```
256 255
257 256
Back in our getEmissionsData function we need to turn the “loading” on when we start the request, and then off when we’re done.
258 257
259 258
```javascript
260 259
const getEmissionsData = async () => {
261 -
    try{
262 -
      setIsLoading(true)
263 -
      const response = await axios.get("https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b")
264 -
      console.log(response.data)
265 -
      setEmissionsData(response.data)
266 -
      setIsLoading(false)
267 -
    } catch (error) {
268 -
      console.log(error)
269 -
    }
270 -
  }
260 +
	try {
261 +
		setIsLoading(true);
262 +
		const response = await axios.get(
263 +
			"https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
264 +
		);
265 +
		console.log(response.data);
266 +
		setEmissionsData(response.data);
267 +
		setIsLoading(false);
268 +
	} catch (error) {
269 +
		console.log(error);
270 +
	}
271 +
};
271 272
```
272 273
273 274
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.”
274 275
275 276
```javascript
276 -
return (
277 -
    <div className="aerial-container">
278 -
      {isLoading ? loading() : emissionsComponent()}
279 -
    </div>
280 -
  )
277 +
return <div className="aerial-container">{isLoading ? loading() : emissionsComponent()}</div>;
281 278
```
282 279
283 280
As a quick recap this is what our component looks like at the moment
284 281
285 282
```javascript
286 -
import { useState, useEffect } from "react"
287 -
import axios from "axios"
283 +
import { useState, useEffect } from "react";
284 +
import axios from "axios";
288 285
289 286
const Aerial = () => {
287 +
	const [emissionsData, setEmissionsData] = useState([]);
288 +
	const [isLoading, setIsLoading] = useState(false);
290 289
291 -
  const [emissionsData, setEmissionsData] = useState([])
292 -
  const [isLoading, setIsLoading] = useState(false)
290 +
	const getEmissionsData = async () => {
291 +
		try {
292 +
			setIsLoading(true);
293 +
			const response = await axios.get(
294 +
				"https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
295 +
			);
296 +
			console.log(response.data);
297 +
			setEmissionsData(response.data);
298 +
			setIsLoading(false);
299 +
		} catch (error) {
300 +
			console.log(error);
301 +
		}
302 +
	};
293 303
294 -
  const getEmissionsData = async () => {
295 -
    try{
296 -
      setIsLoading(true)
297 -
      const response = await axios.get("https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b")
298 -
      console.log(response.data)
299 -
      setEmissionsData(response.data)
300 -
      setIsLoading(false)
301 -
    } catch (error) {
302 -
      console.log(error)
303 -
    }
304 -
  }
304 +
	const loading = () => (
305 +
		<div className="loading-container">
306 +
			<h1>Loading</h1>
307 +
		</div>
308 +
	);
305 309
306 -
  const loading = () => (
307 -
    <div className="loading-container">
308 -
      <h1>Loading</h1>
309 -
    </div>
310 -
  )
311 -
312 -
  const emissionsComponent = () => {
313 -
    
314 -
    return (
315 -
      <div className="data-container">
316 -
        <h1>Data goes here</h1>
317 -
      </div>
318 -
    )
319 -
  }
320 -
    
310 +
	const emissionsComponent = () => {
311 +
		return (
312 +
			<div className="data-container">
313 +
				<h1>Data goes here</h1>
314 +
			</div>
315 +
		);
316 +
	};
321 317
322 -
  useEffect(()=> {
323 -
    getEmissionsData()
324 -
  }, [])
318 +
	useEffect(() => {
319 +
		getEmissionsData();
320 +
	}, []);
325 321
326 -
  return (
327 -
    <div className="aerial-container">
328 -
      {isLoading ? loading() : emissionsComponent()}
329 -
    </div>
330 -
  )
331 -
}
322 +
	return <div className="aerial-container">{isLoading ? loading() : emissionsComponent()}</div>;
323 +
};
332 324
333 325
export default Aerial;
334 326
```
338 330
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.
339 331
340 332
```javascript
341 -
const co2 = new Intl.NumberFormat().format(Math.round(emissionsData.co2))
333 +
const co2 = new Intl.NumberFormat().format(Math.round(emissionsData.co2));
342 334
```
343 335
344 336
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.
345 337
346 338
```javascript
347 -
const gas = new Intl.NumberFormat().format(emissionsData.gas)
339 +
const gas = new Intl.NumberFormat().format(emissionsData.gas);
348 340
```
349 341
350 342
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!
352 344
For the credits remaining, we just need to subtract the credits already purchased from the total credits needed to offset like so.
353 345
354 346
```javascript
355 -
const creditsRemaining = new Intl.NumberFormat().format(emissionsData.credits - emissionsData.credits_purchased)
347 +
const creditsRemaining = new Intl.NumberFormat().format(
348 +
	emissionsData.credits - emissionsData.credits_purchased
349 +
);
356 350
```
357 351
358 352
Of course we want to display how many have already purchased and that’s pretty simple.
359 353
360 354
```javascript
361 -
const creditsPurchased = new Intl.NumberFormat().format(emissionsData.credits_purchased)
355 +
const creditsPurchased = new Intl.NumberFormat().format(emissionsData.credits_purchased);
362 356
```
363 357
364 358
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!
365 359
366 360
```javascript
367 -
const cost = new Intl.NumberFormat().format((emissionsData.cost / emissionsData.credits) * (emissionsData.credits - emissionsData.credits_purchased))
361 +
const cost = new Intl.NumberFormat().format(
362 +
	(emissionsData.cost / emissionsData.credits) *
363 +
		(emissionsData.credits - emissionsData.credits_purchased)
364 +
);
368 365
```
369 366
370 367
Lastly, we want the number of total number of transactions already offset.
377 374
378 375
```javascript
379 376
return (
380 -
      <div className="data-container">
381 -
        <div className="header">
382 -
          <h1>Deadfellaz Carbon Offset</h1>
383 -
        </div>
384 -
        <div className="data-grid">
385 -
          <div className="data-cell">
386 -
            <h2>{co2} Kg</h2>
387 -
            <h3>CO2 Emissions</h3>
388 -
          </div>
389 -
          <div className="data-cell">
390 -
            <h2>{gas}</h2>
391 -
            <h3>Gas Used</h3>
392 -
          </div>
393 -
          <div className="data-cell">
394 -
            <h2>{transactions}</h2>
395 -
            <h3>Transactions</h3>
396 -
          </div>
397 -
          <div className="data-cell">
398 -
            <h2>${cost}</h2>
399 -
            <h3>Cost to Offset</h3>
400 -
          </div>
401 -
          <div className="data-cell">
402 -
            <h2>{creditsRemaining}</h2>
403 -
            <h3>Credits needed to offset</h3>
404 -
          </div>
405 -
          <div className="data-cell">
406 -
            <h2>{creditsPurchased}</h2>
407 -
            <h3>Credits Purchased so Far</h3>
408 -
          </div>
409 -
        </div>
410 -
        <a className="cta-button" target="_blank" rel="noreferrer" href="https://aerial.is/nft/0x2acab3dea77832c09420663b0e1cb386031ba17b">Offset</a>
411 -
    </div>
412 -
    )
377 +
	<div className="data-container">
378 +
		<div className="header">
379 +
			<h1>Deadfellaz Carbon Offset</h1>
380 +
		</div>
381 +
		<div className="data-grid">
382 +
			<div className="data-cell">
383 +
				<h2>{co2} Kg</h2>
384 +
				<h3>CO2 Emissions</h3>
385 +
			</div>
386 +
			<div className="data-cell">
387 +
				<h2>{gas}</h2>
388 +
				<h3>Gas Used</h3>
389 +
			</div>
390 +
			<div className="data-cell">
391 +
				<h2>{transactions}</h2>
392 +
				<h3>Transactions</h3>
393 +
			</div>
394 +
			<div className="data-cell">
395 +
				<h2>${cost}</h2>
396 +
				<h3>Cost to Offset</h3>
397 +
			</div>
398 +
			<div className="data-cell">
399 +
				<h2>{creditsRemaining}</h2>
400 +
				<h3>Credits needed to offset</h3>
401 +
			</div>
402 +
			<div className="data-cell">
403 +
				<h2>{creditsPurchased}</h2>
404 +
				<h3>Credits Purchased so Far</h3>
405 +
			</div>
406 +
		</div>
407 +
		<a
408 +
			className="cta-button"
409 +
			target="_blank"
410 +
			rel="noreferrer"
411 +
			href="https://aerial.is/nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
412 +
		>
413 +
			Offset
414 +
		</a>
415 +
	</div>
416 +
);
413 417
```
414 418
415 419
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.
417 421
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!
418 422
419 423
```javascript
420 -
import { useState, useEffect } from "react"
421 -
import "./Aerial.css"
424 +
import { useState, useEffect } from "react";
425 +
import "./Aerial.css";
422 426
import axios from "axios";
423 -
import Lottie from "react-lottie"
424 -
import co2 from "./co2.json"
425 -
427 +
import Lottie from "react-lottie";
428 +
import co2 from "./co2.json";
426 429
427 430
const Aerial = () => {
431 +
	const [emissionsData, setEmissionsData] = useState([]);
432 +
	const [isLoading, setIsLoading] = useState(false);
428 433
429 -
  const [emissionsData, setEmissionsData] = useState([])
430 -
  const [isLoading, setIsLoading] = useState(false)
434 +
	const getEmissionsData = async () => {
435 +
		try {
436 +
			setIsLoading(true);
437 +
			const response = await axios.get(
438 +
				"https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
439 +
			);
440 +
			console.log(response.data);
441 +
			setEmissionsData(response.data);
442 +
			setIsLoading(false);
443 +
		} catch (error) {
444 +
			console.log(error);
445 +
		}
446 +
	};
431 447
432 -
  const getEmissionsData = async () => {
433 -
    try{
434 -
      setIsLoading(true)
435 -
      const response = await axios.get("https://aerial.is/_nft/0x2acab3dea77832c09420663b0e1cb386031ba17b")
436 -
      console.log(response.data)
437 -
      setEmissionsData(response.data)
438 -
      setIsLoading(false)
439 -
    } catch (error) {
440 -
      console.log(error)
441 -
    }
442 -
  }
448 +
	const loading = () => (
449 +
		<div className="loading-container">
450 +
			<Lottie options={{ animationData: co2 }} height={400} width={400} />
451 +
		</div>
452 +
	);
443 453
444 -
  const loading = () => (
445 -
    <div className="loading-container">
446 -
      <Lottie options={{animationData: co2}} height={400} width={400} />
447 -
    </div>
448 -
  )
449 -
450 -
  const emissionsComponent = () => {
454 +
	const emissionsComponent = () => {
455 +
		const co2 = new Intl.NumberFormat().format(Math.round(emissionsData.co2));
456 +
		const gas = new Intl.NumberFormat().format(emissionsData.gas);
457 +
		const creditsRemaining = new Intl.NumberFormat().format(
458 +
			emissionsData.credits - emissionsData.credits_purchased
459 +
		);
460 +
		const creditsPurchased = new Intl.NumberFormat().format(emissionsData.credits_purchased);
461 +
		const cost = new Intl.NumberFormat().format(
462 +
			(emissionsData.cost / emissionsData.credits) *
463 +
				(emissionsData.credits - emissionsData.credits_purchased)
464 +
		);
465 +
		const transactions = new Intl.NumberFormat().format(emissionsData.transactions);
451 466
452 -
    const co2 = new Intl.NumberFormat().format(Math.round(emissionsData.co2))
453 -
    const gas = new Intl.NumberFormat().format(emissionsData.gas)
454 -
    const creditsRemaining = new Intl.NumberFormat().format(emissionsData.credits - emissionsData.credits_purchased)
455 -
    const creditsPurchased = new Intl.NumberFormat().format(emissionsData.credits_purchased)
456 -
    const cost = new Intl.NumberFormat().format((emissionsData.cost / emissionsData.credits) * (emissionsData.credits - emissionsData.credits_purchased))
457 -
    const transactions = new Intl.NumberFormat().format(emissionsData.transactions)
458 -
    
459 -
    return (
460 -
      <div className="data-container">
461 -
        <div className="header">
462 -
          <h1>Deadfellaz Carbon Offset</h1>
463 -
        </div>
464 -
        <div className="data-grid">
465 -
          <div className="data-cell">
466 -
            <h2>{co2} Kg</h2>
467 -
            <h3>CO2 Emissions</h3>
468 -
          </div>
469 -
          <div className="data-cell">
470 -
            <h2>{gas}</h2>
471 -
            <h3>Gas Used</h3>
472 -
          </div>
473 -
          <div className="data-cell">
474 -
            <h2>{transactions}</h2>
475 -
            <h3>Transactions</h3>
476 -
          </div>
477 -
          <div className="data-cell">
478 -
            <h2>${cost}</h2>
479 -
            <h3>Cost to Offset</h3>
480 -
          </div>
481 -
          <div className="data-cell">
482 -
            <h2>{creditsRemaining}</h2>
483 -
            <h3>Credits needed to offset</h3>
484 -
          </div>
485 -
          <div className="data-cell">
486 -
            <h2>{creditsPurchased}</h2>
487 -
            <h3>Credits Purchased so Far</h3>
488 -
          </div>
489 -
        </div>
490 -
        <a className="cta-button" target="_blank" rel="noreferrer" href="https://aerial.is/nft/0x2acab3dea77832c09420663b0e1cb386031ba17b">Offset</a>
491 -
    </div>
492 -
    )
493 -
  }
494 -
    
467 +
		return (
468 +
			<div className="data-container">
469 +
				<div className="header">
470 +
					<h1>Deadfellaz Carbon Offset</h1>
471 +
				</div>
472 +
				<div className="data-grid">
473 +
					<div className="data-cell">
474 +
						<h2>{co2} Kg</h2>
475 +
						<h3>CO2 Emissions</h3>
476 +
					</div>
477 +
					<div className="data-cell">
478 +
						<h2>{gas}</h2>
479 +
						<h3>Gas Used</h3>
480 +
					</div>
481 +
					<div className="data-cell">
482 +
						<h2>{transactions}</h2>
483 +
						<h3>Transactions</h3>
484 +
					</div>
485 +
					<div className="data-cell">
486 +
						<h2>${cost}</h2>
487 +
						<h3>Cost to Offset</h3>
488 +
					</div>
489 +
					<div className="data-cell">
490 +
						<h2>{creditsRemaining}</h2>
491 +
						<h3>Credits needed to offset</h3>
492 +
					</div>
493 +
					<div className="data-cell">
494 +
						<h2>{creditsPurchased}</h2>
495 +
						<h3>Credits Purchased so Far</h3>
496 +
					</div>
497 +
				</div>
498 +
				<a
499 +
					className="cta-button"
500 +
					target="_blank"
501 +
					rel="noreferrer"
502 +
					href="https://aerial.is/nft/0x2acab3dea77832c09420663b0e1cb386031ba17b"
503 +
				>
504 +
					Offset
505 +
				</a>
506 +
			</div>
507 +
		);
508 +
	};
495 509
496 -
  useEffect(()=> {
497 -
    getEmissionsData()
498 -
  }, [])
510 +
	useEffect(() => {
511 +
		getEmissionsData();
512 +
	}, []);
499 513
500 -
  return (
501 -
    <div className="aerial-container">
502 -
      {isLoading ? loading() : emissionsComponent()}
503 -
    </div>
504 -
  )
505 -
}
514 +
	return <div className="aerial-container">{isLoading ? loading() : emissionsComponent()}</div>;
515 +
};
506 516
507 517
export default Aerial;
508 518
```
src/content/post/my-developer-journey.md +9 −7
7 7
8 8
## It Started with Clickbait
9 9
10 -
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. 
10 +
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.
11 11
12 -
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. 
12 +
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.
13 13
14 -
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: 
14 +
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:
15 15
16 16
**[How I Learned to Code - And Got a Job in Less Than 3 Months](https://youtu.be/nupkQD_Mnhg)**
17 17
18 18
## The Grind
19 19
20 -
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 [Head First: HTML with CSS and XHTML](https://www.amazon.com/Head-First-HTML-CSS-Standards-Based/dp/0596159900) 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 [Head First Java](https://www.amazon.com/Head-First-Java-Brain-Learners/dp/0596004656?keywords=head+first+java&qid=1677605428&sr=8-5) 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. 
20 +
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 [Head First: HTML with CSS and XHTML](https://www.amazon.com/Head-First-HTML-CSS-Standards-Based/dp/0596159900) 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 [Head First Java](https://www.amazon.com/Head-First-Java-Brain-Learners/dp/0596004656?keywords=head+first+java&qid=1677605428&sr=8-5) 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.
21 21
22 -
The next year was spent grinding through some coursed by [Ed](https://developedbyed.com/), 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. 
22 +
The next year was spent grinding through some coursed by [Ed](https://developedbyed.com/), 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.
23 23
24 24
## The First Smart Contract
25 +
25 26
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 [Buildspace](https://buildspace.so) 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 [Pinata](https://pinata.cloud). 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!
26 27
27 28
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.
28 29
29 -
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: 
30 -
> 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. 
30 +
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:
31 +
32 +
> 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.
31 33
32 34
> Not everyone can become a great artist; but a great artist **can** come from **anywhere**.
src/content/post/resizing-ipfs-images.mdx +63 −59
6 6
ogImage: "https://miro.medium.com/v2/resize:fit:4800/format:webp/1*Tp42Ey9Uvdb6njsaXHBOTA.jpeg"
7 7
---
8 8
9 -
import medium from "../../assets/medium.png"
10 -
import OutLinkButton from "../../components/OutLinkButton.astro"
9 +
import medium from "../../assets/medium.png";
10 +
import OutLinkButton from "../../components/OutLinkButton.astro";
11 11
12 -
<OutLinkButton link="https://medium.com/pinata/resizing-ipfs-images-with-pinatas-image-optimization-tools-fb381bee58aa" site="Medium" image={medium} />
13 -
12 +
<OutLinkButton
13 +
	link="https://medium.com/pinata/resizing-ipfs-images-with-pinatas-image-optimization-tools-fb381bee58aa"
14 +
	site="Medium"
15 +
	image={medium}
16 +
/>
14 17
15 18
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?
16 19
64 67
65 68
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:
66 69
67 -
* DPR (Device Pixel Ratio)
68 -
* Image Fit — for scaling down, image positions and more!
69 -
* Image Quality — Set a scale from 1–100 to easily reduce a high quality image
70 -
* Auto Image Formatting — Use Webp where supported, but then fall back to jpeg or png
71 -
* Animation Still — Turn a gif into a still image
72 -
* On Error Redirect — Redirect to a different image if there is a problem
73 -
* Metadata Controls — Control what EXIF data is revealed with the image
70 +
- DPR (Device Pixel Ratio)
71 +
- Image Fit — for scaling down, image positions and more!
72 +
- Image Quality — Set a scale from 1–100 to easily reduce a high quality image
73 +
- Auto Image Formatting — Use Webp where supported, but then fall back to jpeg or png
74 +
- Animation Still — Turn a gif into a still image
75 +
- On Error Redirect — Redirect to a different image if there is a problem
76 +
- Metadata Controls — Control what EXIF data is revealed with the image
74 77
75 78
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 [developer docs](https://docs.pinata.cloud) to see them all.
76 79
92 95
93 96
```javascript
94 97
function App() {
95 -
return (
96 -
    <div className="App">
97 -
      <div className="grid">
98 -
99 -
      </div>
100 -
    </div>
101 -
  );
98 +
	return (
99 +
		<div className="App">
100 +
			<div className="grid"></div>
101 +
		</div>
102 +
	);
102 103
}
103 104
export default App;
104 105
```
111 112
112 113
Let’s break this down again so we know what’s going on.
113 114
114 -
We have our gateway url ```https://stevedsimkins.mypinata.cloud/ipfs/```, then we have our CID ```QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ```, then our dynamic image id ```${id}.jpg``` and finally our image optimization ```?img-width=1080&img-height=1080```. 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.
115 +
We have our gateway url `https://stevedsimkins.mypinata.cloud/ipfs/`, then we have our CID `QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ`, then our dynamic image id `${id}.jpg` and finally our image optimization `?img-width=1080&img-height=1080`. 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.
115 116
116 117
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.
117 118
118 119
```javascript
119 -
let imageIds = []
120 -
for (let id = 1; id <= 8; id++){
121 -
  imageIds.push(id)
120 +
let imageIds = [];
121 +
for (let id = 1; id <= 8; id++) {
122 +
	imageIds.push(id);
122 123
}
123 124
```
124 125
125 126
Now the fun part: generating the images! We’ll take our imageId array and map over it.
126 127
127 128
```javascript
128 -
{imageIds.map((id) => {
129 -
130 -
})}
129 +
{
130 +
	imageIds.map((id) => {});
131 +
}
131 132
```
132 133
133 134
Then we’ll declare our base URL with our dynamic image ID, as well as a name for the alt text later.
134 135
135 136
```javascript
136 -
{imageIds.map((id) => {
137 -
          let url = `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&img-height=1080`
138 -
          let name = `nft ${id}`
139 -
})}
137 +
{
138 +
	imageIds.map((id) => {
139 +
		let url = `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&img-height=1080`;
140 +
		let name = `nft ${id}`;
141 +
	});
142 +
}
140 143
```
141 144
142 145
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!
143 146
144 147
```javascript
145 -
{imageIds.map((id) => {
146 -
          let url = `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&img-height=1080`
147 -
          let name = `nft ${id}`
148 -
return (
149 -
    <div className="image-container">
150 -
      <img src={url} alt={name}/>
151 -
    </div>
152 -
  )
153 -
})}
148 +
{
149 +
	imageIds.map((id) => {
150 +
		let url = `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&img-height=1080`;
151 +
		let name = `nft ${id}`;
152 +
		return (
153 +
			<div className="image-container">
154 +
				<img src={url} alt={name} />
155 +
			</div>
156 +
		);
157 +
	});
158 +
}
154 159
```
155 160
156 161
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!
157 162
158 163
```javascript
159 -
import './App.css';
164 +
import "./App.css";
160 165
161 166
function App() {
167 +
	let imageIds = [];
162 168
163 -
  let imageIds = []
169 +
	for (let id = 1; id <= 8; id++) {
170 +
		imageIds.push(id);
171 +
	}
164 172
165 -
  for (let id = 1; id <= 8; id++){
166 -
    imageIds.push(id)
167 -
  }
173 +
	return (
174 +
		<div className="App">
175 +
			<div className="grid">
176 +
				{imageIds.map((id) => {
177 +
					let url = `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&img-height=1080`;
178 +
					let name = `nft ${id}`;
168 179
169 -
  return (
170 -
    <div className="App">
171 -
      <div className="grid">
172 -
        {imageIds.map((id) => {
173 -
          let url = `https://stevedsimkins.mypinata.cloud/ipfs/QmTwDNr6LyRzW8H3XorFDArfKEH3GRV1SkF6bAEBF3P4GJ/${id}.jpg?img-width=1080&img-height=1080`
174 -
          let name = `nft ${id}`
175 -
176 -
          return (
177 -
            <div className="image-container">
178 -
              <img src={url} alt={name}/>
179 -
            </div>
180 -
          )
181 -
        })}
182 -
      </div>
183 -
    </div>
184 -
  );
180 +
					return (
181 +
						<div className="image-container">
182 +
							<img src={url} alt={name} />
183 +
						</div>
184 +
					);
185 +
				})}
186 +
			</div>
187 +
		</div>
188 +
	);
185 189
}
186 190
187 191
export default App;
src/content/post/the-power-of-dedicated-gateways.mdx +11 −6
6 6
ogImage: "https://global-uploads.webflow.com/629e4fe96456f8219203e7f1/6410b46677b05b001afa5ff4_2022-02-10_The-Power-of_blog-img-tiny.png"
7 7
---
8 8
9 -
import pinnie from "../../assets/pinnie.png"
10 -
import OutLinkButton from "../../components/OutLinkButton.astro"
9 +
import pinnie from "../../assets/pinnie.png";
10 +
import OutLinkButton from "../../components/OutLinkButton.astro";
11 11
12 -
<OutLinkButton link="https://www.pinata.cloud/blog/the-power-of-dedicated-gateways" site="Pinata" image={pinnie} />
13 -
12 +
<OutLinkButton
13 +
	link="https://www.pinata.cloud/blog/the-power-of-dedicated-gateways"
14 +
	site="Pinata"
15 +
	image={pinnie}
16 +
/>
14 17
15 18
## What are IPFS Gateways?
16 19
17 20
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.
18 21
19 -
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: ```QmTz8mgtvkf8fG8es5i6vr4LX7dd9vnk1XVtB6ScVuCepr```. A content identifier, or CID for short, is how we can reference content on IPFS. The direct link to that file via IPFS is 
22 +
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: `QmTz8mgtvkf8fG8es5i6vr4LX7dd9vnk1XVtB6ScVuCepr`. A content identifier, or CID for short, is how we can reference content on IPFS. The direct link to that file via IPFS is
23 +
20 24
```
21 25
ipfs://QmTz8mgtvkf8fG8es5i6vr4LX7dd9vnk1XVtB6ScVuCepr
22 26
```
27 +
23 28
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?
24 29
25 30
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.
36 41
37 42
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!
38 43
39 -
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.
44 +
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.
40 45
41 46
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.
42 47
src/data/constants.ts +7 −7
11 11
		title: "Blog",
12 12
		path: "/posts",
13 13
	},
14 -
  {
15 -
    title: "Videos",
16 -
    path: "/videos",
17 -
  },
14 +
	{
15 +
		title: "Videos",
16 +
		path: "/videos",
17 +
	},
18 18
];
19 19
20 20
// ! Remember to add your own socials
21 21
export const SOCIAL_LINKS = {
22 22
	github: "https://github.com/stevedsimkins",
23 23
	twitter: "https://twitter.com/stevedsimkins",
24 -
  medium: "https://medium.com/@stevedsimkins",
25 -
  linkedin: "https://linkedin.com/in/steve-simkins",
26 -
  ethereum: "https://rainbow.me/stevedsimkins.eth",
24 +
	medium: "https://medium.com/@stevedsimkins",
25 +
	linkedin: "https://linkedin.com/in/steve-simkins",
26 +
	ethereum: "https://rainbow.me/stevedsimkins.eth",
27 27
};
src/layouts/Base.astro +3 −2
18 18
<html lang={siteConfig.lang}>
19 19
	<head>
20 20
		<!-- Google tag (gtag.js) -->
21 -
    <script type="text/partytown"src="https://www.googletagmanager.com/gtag/js?id=G-QT67QEFLTG"></script>
22 -
    <script type="text/partytown">
21 +
		<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-QT67QEFLTG"
22 +
		></script>
23 +
		<script type="text/partytown">
23 24
			window.dataLayer = window.dataLayer || [];
24 25
			function gtag() {
25 26
				dataLayer.push(arguments);
src/layouts/BlogPost.astro +1 −1
62 62
		<article class="flex-grow break-words">
63 63
			<div id="blog-hero"><BlogHero content={post} /></div>
64 64
			<div
65 -
          class="prose prose-sm prose-cactus mt-12 prose-headings:font-semibold prose-headings:before:absolute prose-headings:before:-ml-4 prose-headings:before:text-accent prose-headings:before:content-['#'] prose-th:before:content-none"
65 +
				class="prose prose-sm prose-cactus mt-12 prose-headings:font-semibold prose-headings:before:absolute prose-headings:before:-ml-4 prose-headings:before:text-accent prose-headings:before:content-['#'] prose-th:before:content-none"
66 66
			>
67 67
				<slot />
68 68
			</div>
src/pages/404.astro +1 −5
13 13
	<h1 class="title mb-6">404 | Oops something went wrong</h1>
14 14
	<p class="mb-8">Please use the navigation to find your way back</p>
15 15
	<div class="my-4 grid justify-center">
16 -
		<Image
17 -
			src={img}
18 -
			alt="A cartoon cactus looking at the 'Astro.build' logo"
19 -
			loading="eager"
20 -
		/>
16 +
		<Image src={img} alt="A cartoon cactus looking at the 'Astro.build' logo" loading="eager" />
21 17
	</div>
22 18
</PageLayout>
src/pages/about.astro +11 −7
21 21
				aria-label="Link to Pinata website"
22 22
				href="https://pinata.cloud">Pinata.</a
23 23
			>
24 -
      I help creators and developers in the Web3 space, and I like to help build solutions
25 -
			to their problems.
24 +
			I help creators and developers in the Web3 space, and I like to help build solutions to their problems.
26 25
		</p>
27 26
		<div class="flex justify-center">
28 27
			<Image
33 32
		</div>
34 33
		<p>Here's some more info about me:</p>
35 34
		<ul class="list-inside list-disc">
36 -
      <li>Currently in Chattanooga TN</li>
37 -
      <li>Pastimes include enjoying coffee shops, spending time with my wife and two sons, and tinkering with mechanical keyboards</li>
38 -
      <li>Taught myself frontend web development which led to being hooked on blockchain technology</li>
39 -
      <li>I have a killer waffle recipe handed down through my family</li>
35 +
			<li>Currently in Chattanooga TN</li>
36 +
			<li>
37 +
				Pastimes include enjoying coffee shops, spending time with my wife and two sons, and
38 +
				tinkering with mechanical keyboards
39 +
			</li>
40 +
			<li>
41 +
				Taught myself frontend web development which led to being hooked on blockchain technology
42 +
			</li>
43 +
			<li>I have a killer waffle recipe handed down through my family</li>
40 44
		</ul>
41 45
		<p>
42 -
			Feel free to 
46 +
			Feel free to
43 47
			<a
44 48
				class="cactus-link inline-block"
45 49
				href="mailto:hello@stevedsimkins.dev"
src/pages/index.astro +7 −10
8 8
const MAX_POSTS = 10;
9 9
const allPosts = await getCollection("post");
10 10
const allPostsByDate = sortMDByDate(allPosts).slice(0, MAX_POSTS);
11 -
12 11
---
13 12
14 13
<PageLayout meta={{ title: "Home" }}>
15 14
	<section>
16 15
		<h1 class="title mb-6">Hey there!</h1>
17 16
		<p class="mb-4">
18 -
      My name is Steve. I'm a developer, technical writer, and creator with a desire to help
19 -
			build the future of the web. Stay a while to see what I'm working on!
17 +
			My name is Steve. I'm a developer, technical writer, and creator with a desire to help build
18 +
			the future of the web. Stay a while to see what I'm working on!
20 19
		</p>
21 20
		<SocialList />
22 -
    <p>Or anywhere with my handle <span class="text-accent">@stevedsimkins</span></p>
21 +
		<p>Or anywhere with my handle <span class="text-accent">@stevedsimkins</span></p>
23 22
	</section>
24 23
	<section aria-label="Blog post list" class="mt-16">
25 24
		<h2 class="title mb-4 text-xl">Posts</h2>
38 37
		<ul class="space-y-4 sm:space-y-2">
39 38
			<li>
40 39
				<a
41 -
          href="https://stevedylanphoto.com"
40 +
					href="https://stevedylanphoto.com"
42 41
					target="_blank"
43 42
					rel="noopener noreferrer"
44 43
					class="cactus-link inline-block"
48 47
			</li>
49 48
			<li>
50 49
				<a
51 -
          href="https://pinata.cloud"
50 +
					href="https://pinata.cloud"
52 51
					target="_blank"
53 52
					rel="noopener noreferrer"
54 53
					class="cactus-link inline-block"
58 57
			</li>
59 58
			<li>
60 59
				<a
61 -
          href="https://medium.com/@stevedsimkins"
60 +
					href="https://medium.com/@stevedsimkins"
62 61
					target="_blank"
63 62
					rel="noopener noreferrer"
64 63
					class="cactus-link inline-block"
65 64
					>Medium
66 65
				</a>:
67 -
        <p class="inline-block sm:mt-2">
68 -
          Technical blog posts I've written for Pinata
69 -
        </p>
66 +
				<p class="inline-block sm:mt-2">Technical blog posts I've written for Pinata</p>
70 67
			</li>
71 68
		</ul>
72 69
	</section>
src/pages/og-image/[slug].png.ts +48 −38
7 7
import { getFormattedDate } from "@/utils";
8 8
9 9
const monoFontReg = await fetch(
10 -
  "https://api.fontsource.org/v1/fonts/roboto-mono/latin-400-normal.ttf"
10 +
	"https://api.fontsource.org/v1/fonts/roboto-mono/latin-400-normal.ttf"
11 11
);
12 12
13 13
const monoFontBold = await fetch(
14 -
  "https://api.fontsource.org/v1/fonts/roboto-mono/latin-700-normal.ttf"
14 +
	"https://api.fontsource.org/v1/fonts/roboto-mono/latin-700-normal.ttf"
15 15
);
16 16
17 17
const ogOptions: SatoriOptions = {
18 -
  width: 1200,
19 -
  height: 630,
20 -
  // debug: true,
21 -
  embedFont: true,
22 -
  fonts: [
23 -
    {
24 -
      name: "Roboto Mono",
25 -
      data: await monoFontReg.arrayBuffer(),
26 -
      weight: 400,
27 -
      style: "normal",
28 -
    },
29 -
    {
30 -
      name: "Roboto Mono",
31 -
      data: await monoFontBold.arrayBuffer(),
32 -
      weight: 700,
33 -
      style: "normal",
34 -
    },
35 -
  ],
18 +
	width: 1200,
19 +
	height: 630,
20 +
	// debug: true,
21 +
	embedFont: true,
22 +
	fonts: [
23 +
		{
24 +
			name: "Roboto Mono",
25 +
			data: await monoFontReg.arrayBuffer(),
26 +
			weight: 400,
27 +
			style: "normal",
28 +
		},
29 +
		{
30 +
			name: "Roboto Mono",
31 +
			data: await monoFontBold.arrayBuffer(),
32 +
			weight: 700,
33 +
			style: "normal",
34 +
		},
35 +
	],
36 36
};
37 37
38 38
const markup = (title: string, pubDate: string, description: string) => html`<div
46 46
	<div tw="flex items-center justify-between w-full p-10 border-t border-[#a6e3a1] text-xl">
47 47
		<div tw="flex items-center">
48 48
			<svg height="60" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
49 -
<path d="M20.1465 448.094H479L350.926 226.37L311.281 258.273L275.108 232.751L249.573 258.273L215.528 232.751L193.185 258.273L151.291 221.053L20.1465 448.094Z" fill="#74c7ec"/>
50 -
<path d="M249.573 50.9053L151.291 221.053L193.185 258.273L215.528 232.751L249.573 258.273L275.108 232.751L311.281 258.273L350.926 226.37L249.573 50.9053Z" fill="#EDEFF3"/>
51 -
<path d="M151.291 221.053L20.1465 448.094H479L350.926 226.37M151.291 221.053L249.573 50.9053L350.926 226.37M151.291 221.053L193.185 258.273L215.528 232.751L249.573 258.273L275.108 232.751L311.281 258.273L350.926 226.37" stroke="black" stroke-width="7"/>
52 -
<line x1="265.341" y1="167.541" x2="294.587" y2="218.169" stroke="black" stroke-width="5"/>
49 +
				<path
50 +
					d="M20.1465 448.094H479L350.926 226.37L311.281 258.273L275.108 232.751L249.573 258.273L215.528 232.751L193.185 258.273L151.291 221.053L20.1465 448.094Z"
51 +
					fill="#74c7ec"
52 +
				/>
53 +
				<path
54 +
					d="M249.573 50.9053L151.291 221.053L193.185 258.273L215.528 232.751L249.573 258.273L275.108 232.751L311.281 258.273L350.926 226.37L249.573 50.9053Z"
55 +
					fill="#EDEFF3"
56 +
				/>
57 +
				<path
58 +
					d="M151.291 221.053L20.1465 448.094H479L350.926 226.37M151.291 221.053L249.573 50.9053L350.926 226.37M151.291 221.053L193.185 258.273L215.528 232.751L249.573 258.273L275.108 232.751L311.281 258.273L350.926 226.37"
59 +
					stroke="black"
60 +
					stroke-width="7"
61 +
				/>
62 +
				<line x1="265.341" y1="167.541" x2="294.587" y2="218.169" stroke="black" stroke-width="5" />
53 63
			</svg>
54 64
			<p tw="ml-3 font-semibold text-3xl">${siteConfig.title}</p>
55 65
		</div>
57 67
</div>`;
58 68
59 69
export async function get({ params: { slug } }: APIContext) {
60 -
  const post = await getEntryBySlug("post", slug!);
61 -
  const title = post?.data.title ?? siteConfig.title;
62 -
  const postDate = getFormattedDate(post?.data.publishDate ?? Date.now(), {
63 -
    weekday: "long",
64 -
  });
65 -
  const description = post?.data.description ?? siteConfig.title;
66 -
  const svg = await satori(markup(title, postDate, description), ogOptions);
67 -
  const png = new Resvg(svg).render().asPng();
68 -
  return {
69 -
    body: png,
70 -
    encoding: "binary",
71 -
  };
70 +
	const post = await getEntryBySlug("post", slug!);
71 +
	const title = post?.data.title ?? siteConfig.title;
72 +
	const postDate = getFormattedDate(post?.data.publishDate ?? Date.now(), {
73 +
		weekday: "long",
74 +
	});
75 +
	const description = post?.data.description ?? siteConfig.title;
76 +
	const svg = await satori(markup(title, postDate, description), ogOptions);
77 +
	const png = new Resvg(svg).render().asPng();
78 +
	return {
79 +
		body: png,
80 +
		encoding: "binary",
81 +
	};
72 82
}
73 83
74 84
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
75 -
  const posts = await getCollection("post");
76 -
  return posts.filter(({ data }) => !data.ogImage).map(({ slug }) => ({ params: { slug } }));
85 +
	const posts = await getCollection("post");
86 +
	return posts.filter(({ data }) => !data.ogImage).map(({ slug }) => ({ params: { slug } }));
77 87
}
src/pages/videos.astro +8 −2
10 10
<PageLayout meta={meta}>
11 11
	<div class="space-y-6">
12 12
		<h1 class="title">Videos</h1>
13 -
    <p>Here are some samples of video content I've produced to help users!</p>
14 -
    <iframe class="w-full md:h-96 h-full" src="https://www.youtube.com/embed/YQMktd0llOo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
13 +
		<p>Here are some samples of video content I've produced to help users!</p>
14 +
		<iframe
15 +
			class="h-full w-full md:h-96"
16 +
			src="https://www.youtube.com/embed/YQMktd0llOo"
17 +
			title="YouTube video player"
18 +
			frameborder="0"
19 +
			allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
20 +
			allowfullscreen></iframe>
15 21
	</div>
16 22
</PageLayout>