How to use Arweave to store and access NFT metadata, part 1

See https://github.com/arcticmatt/arweave-simple-examples for the full code examples

Part 2 is live! I strongly advise you read it after reading this post, since it goes over a more robust solution for storing data on Arweave.

Why Arweave?

First, let’s address a simple question: why use Arweave to store NFT metadata? The simple answer: data on Arweave cannot be deleted or changed.

We can also ask this question another way: why not use IPFS or Amazon S3?

IPFS is not ideal—even though it uses content addressing to ensure NFT metadata can’t be changed, it does nothing to guarantee that your data doesn’t get deleted. For example, if you use Infura’s IPFS pinning service, your data gets deleted if it hasn’t been accessed in 6 months. For NFTs, that’s a total dealbreaker. As another example, nft.storage promises “indefinite” storage, but says the following in its terms & conditions.

Data will be stored at no cost to the user on IPFS for as long as Protocol Labs, Inc. continues to offer free storage for NFT’s. Protocol Labs, Inc. reserves the right to terminate NFT.storage at its sole discretion.

Data will continue to be persisted ad infinitum or until Protocol Labs decides to conclude the NFT.storage project. Prior to termination, Protocol Labs will provide 90 days notice to users via email to allow users enough time to make arrangements for storing their data by other means.

Pinata is another popular IPFS pinning service. I asked them about data persistence, and this is what they said:

As long as your content remains in the “pinned” state on Pinata, and your account does not fall behind on multiple payments, your content will not be deleted.

However if multiple payments are failed and no response is provided after multiple attempts to reach out, then your content can be deleted.

This means the free plan can pin 1GB of content indefinitely, which should be good enough for most small photo-based projects. Still—in general, persistence is not guaranteed!

In general, if you use an IPFS pinning service, you’re relying on a centralized service to keep your data persisted.

With S3, there are no guarantees that your data won’t be deleted or changed. The owner of the S3 bucket has free rein to do what they want.

Uploading data to Arweave, reading data from Arweave

In this section, I’ll go over how to upload data to Arweave with their JavaScript SDK. In order to run this code yourself, you need to generate a new Arweave wallet. Follow these instructions in order to do that.

Now let’s look at the code. I’ll walk through it in more detail below.

Most of the code is pretty straightforward. First, we initialize an Arweave client with Arweave.init. Then, we create a transaction that will upload the data with arweave.createTransaction. Finally, we sign and submit the transaction, and then read the data back.

There are two tricky things about this code:

First, transaction.id only gets populated after you call arweave.transactions.post. Right after creating the transaction, transaction.id === "".

Second, if arweave.transactions.post succeeds, it doesn’t mean the transaction has been confirmed and mined! From the docs:

N.B. This 200 response does not mean that the transaction has mined & confirmed, and that a txid can be used as if it's immutable. It just means that a node has received your transaction. See Get a transaction status for more detail on how to correctly determine that your transaction has been mined & confirmed. This also applies to the uploader method.

In plain english—arweave.transactions.post returns a response. Just because the response’s status is 200 doesn’t mean the transaction has been mined and confirmed. In other words, the transaction won’t show up on https://viewblock.io/arweave/address/YOUR_ADDRESS. However, you can still read the data using arweave.transactions.getData or by going to https://arweave.net/TX_ID.

Cost

You can use the Arweave fee calculator to get a price estimate for storing a certain number of bytes. The data we stored in our example is 1273 bytes, for which the calculator spits out a fee of 0.000002001211 AR. This is consistent with the fee I see on https://viewblock.io/arweave. As I’m writing this, the price of AR is $38.54, which means it costs ~$0.000077 to store the example data. That means with just $1, I can store over 10,000 files like this!

A word of caution

The documentation issues this warning:

N.B. We strongly advise that you check the status and number of confirmations for a given txid before integrating it elsewhere (for example, if you plan to integrate a txid into an NFT contract), even if you have received a ‘200’ status response.

In other words, even if await arweave.transactions.post(transaction) succeeds, it’s recommended that you wait until await arweave.transactions.getStatus(txId) returns a 200 status response before using the transaction ID. This isn’t a big deal you’re using lazy minting, but it’s impractical for the simple use case of uploading some metadata to Arweave and then minting an NFT that links to the metadata.

I’m not sure how often transactions fail to get mined and confirmed (although it can happen, e.g. because the block difficulty changes and the transaction fee is no longer sufficient, because you send too many transactions at once and they go stale, because you send too many transactions and they start getting rate limited, because your wallet is out of balance, etc.). I’m also not sure what happens when you go to https://arweave.net/TX_ID if the corresponding transaction failed to get mined and confirmed. I’ll update this post if I find out.

UPDATE: instead of explaining a solution to this problem here, I wrote a follow-up post. Check it out to see how to build a more robust solution that takes transaction failures into account.

That’s it! If you have any comments or questions, you can leave them here or ask me on Twitter.