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

Matt Lim
5 min readSep 29, 2021


Alternatively, how arbundles works!

See for the full code examples

The Problem

We covered the basics of how to use Arweave to store and access NFT metadata in part 1. Here, we’ll dive deeper into how to address this warning that the documentation provides:

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.

Restating the problem, if you use, the transaction is not guaranteed to be confirmed and mined! This could happen for a variety of reasons (note: I got this info in the Arweave Dev Talk Discord):

  1. A fork reorg (block+txs become orphaned)
  2. The block difficulty changed and the tx fee is no longer sufficient
  3. You sent too many txs at once and they hung around mempool until they went stale
  4. You sent too many txs and they started getting rate-limited by some server/node
  5. Wallet out of balance

If the transaction FAILS to be confirmed and mined, then will eventually stop working! More info from Discord:

if you are uploading your own tx it will be optimistically cached in the gateway, if mining turns out to be unsuccessful it gets removed in a garbage collection run after some time. so you need to be checking /tx/{txid}/status a.k.a. getStatus

Long story short—using without checking transaction status via arweave.transactions.getStatus may cause things to break! For the simple use case of uploading some metadata and storing the metadata URL in an NFT, this is rather annoying—I don’t want to have to wait forarweave.transactions.getStatus before using a transaction ID, especially since it takes a few minutes for a transaction to be confirmed and mined. So how can we solve this in a better way?

Arbundles to the rescue

Arbundles lets you submit a transaction and use it instantly (i.e. instantly access Most importantly, it guarantees that your transaction ID will always be usable. In other words, once your data is available at, it will always be available.

Let’s look at how to use arbundles.

These three lines are the most important:

const item = createData("hello", signer);
await item.sign(signer);
const response = await item.sendToBundler("");

This code creates a DataItem, which is an object that contains the data to be submitted to Arweave. will be populated after running await item.sign(signer), and is the data item’s transaction ID. This transaction ID is guaranteed to always be usable, and is instantly accessible. Explaining exactly how this works is out of scope for this post, but here’s a quick explanation from Discord:

arbundles guarantees dataItem txids, as it handles the L1 enclosing tx for you. it checks for mining and data availabilty, and re-uploads or does whatever is necessary for your txs to be available so you do not need to get into all of that yourself

dataItem is treated like a normal tx, but they are actually enclosed in an Bundlr tx and sent as 1 L1 tx

Put another way— does not refer to a L1 transaction (although it can be accessed like one). This is because L1 transactions can fail to be mined and confirmed, like we discussed above. arbundles handles submitting and re-submitting (if necessary) the L1 transaction for you, so you don’t have to worry about it.

Reading data uploaded with arbundles

This is simple, and is covered in the code above—just run const data = await axios.get(`${}`);.

There’s one tricky thing to note here. While data is instantly available at, it is NOT instantly available via arweave.transactions.getData. Calling that function right after calling sendToBundler will result in a “transaction pending” error. Here’s why, again from Discord:

it’s a bit difficult to explain
but essentially if /{txid} returns 202
it’s “pending”
which with regular txs is true
but it also returns 202 for unseeded Bundlr txs
so the data exists in Bundlr — but not on L1 (Arweave)

Practically, this issue doesn’t matter much—just fetch the data with the HTTP API and you have nothing to worry about.

Is arbundles production ready?

There’s a slightly worrisome section in the repo’s README:

This API is experimental so avoid use in production. There’s one issue that exists that may affect it’s overall functionality and could lead to breaking changes.

The file API stores the items in the filesystem meaning you can bundle more items without hitting the NodeJS memory limit.

Docs coming soon…

I clarified with the repo’s owner ( that this only refers to the file API, and does not affect the regular API (which is what we used in the example above).

I also directly asked if it’s production ready, and got this response:

So the answer is—yes! Well, unless you’re using the file API.

Cost / Fees

At the time of writing (9/29/2021), using arbundles is free! The team is currently covering costs while they stabilize the testnet.

In addition to Arweave fees, bundlers will charge a fee. They will be able to set their own fees, creating a competitive marketplace. Once the testnet is fully out (which should be in the next week or two) you’ll be able to query the fees. Once bundlers start charging, the total cost will look like this:

total_cost = arweave_storage_cost + bundler_base_fee + bundler_item_size_fee(item_num_bytes - 64)

In plain english, on top of the Arweave storage cost, bundlers will charge two fees. The first is a base fee, which covers 64 bytes of storage. The second is based on the size of the item, and will be paid if the size of the item exceeds 64 bytes.

Note that right now, there’s only one bundler, which has a URL of (we used this in the example above). In the future there will be more.

That’s it! If you have any comments or questions, you can leave them here or ask me on Twitter. Lastly, big thanks to joshbenaron and Ros 🌭 for helping me out in the Bundlr and Arweave Dev Talk Discord servers! Basically all this info is from them, I just consolidated it.



Matt Lim

Software Engineer. Tweeting @pencilflip. Mediocre boulderer, amateur tennis player, terrible at Avalon.