4EVERLAND’s Ultimate NFT Storage Solution: Using Path Manifests on Arweave

4EVERLAND
4EVERLAND
Published in
5 min readSep 5, 2023

Why Use Path Manifests?

When publishing NFTs on IPFS, the images and metadata get uploaded to the IPFS network to obtain a Root CID, which looks like this:

ipfs://bafybeic36ik6cngu37xbzmpytuvyo7z3lyeen44clkkxq5d263zj4nkzr4

Using this Root CID, the image path for each NFT can be defined in a format like root cid/'file name'. For example, the image path in the metadata for an NFT could look like this: ipfs://bafybeie7byrnb3vo2lc2lwaqm5lox6jiow5ntzod3aoquki36gtirdhodm/0.png

In a similar vein, Arweave allows the use of Path Manifests to bundle NFTs, providing the benefit of path-based access. For instance: https://arweave.net/Bgw5-GwpymUoe5VMeb-No9WWXpjWsq_8g4oeiGP5RnA/0.png This approach is more organized than using individual IDs for each image stored on Arweave, which would look like this: https://arweave.net/ISvPQyG8qWzJ1Pv5em5xK8Ht38HZ3ub1fPHbFEqDPK0

How to Create NFTs

The following explains how to use 4EVERLAND Bucket + Path Manifests to store NFT files on the Arweave network.

Set Up a Bucket and Upload NFT Files

Initialize a Bucket: Navigate to the 4everland dashboard and create a new bucket. Don’t forget to enable the ‘Sync to AR’ button.

Upload NFT Files: Then, upload your NFT files folder into the Bucket and obtain the TxID for all the NFTs. Besides manually operating within the dashboard, you can also use the S3 Compatible API for uploading and obtaining the TxIDs.

Initialize S3 Client

To interact with the storage, initialize an S3 client as shown below.

// initS3Client.js
import { S3 } from '@aws-sdk/client-s3'
export const client = new S3({
endpoint: 'https://endpoint.4everland.co',
credentials:{
accessKeyId: '',
secretAccessKey: '',
sessionToken: '',
},
forcePathStyle: false,
region: 'eu-west-2'
})

Bulk Upload NFTs

Use the following script to bulk upload NFTs into your bucket.

import { Upload } from '@aws-sdk/lib-storage'
import { clinet } from './initS3Client.js'
let files = [] // File Arrary
let Bucket = 'NFT_Bucket'
files.forEach(aysnc(file) => {
try{
const task = new Upload({
client,
Bucket,
Key: 'YOUR_folder' + '/' + file.name,
Body: file,
ContentType: file.type
})
await task.done()
}catch(error){
console.log(error)
}
})


// Syncing to Arweave can take some time. Wait a few minutes before proceeding to fetch the AR Hash (Tx ID) list:

let arHashPath = {}
files.forEach(aysnc(file) => {
try{
const data = await client.headObject({
Bucket,
Key: 'YOUR_folder' + '/' + file.name,
})
const meta = data.Metadata;
arHashPath[file.name] = {id: meta["arweave-hash"] ?? ''}
}catch(error){
console.log(error)
}
})

Generate Image Manifests ID

After obtaining each NFT’s TxID, compile them into a JSON file as shown below, adhering to the Path Manifests standard. For the standard details, refer to the official documentation.

//manifest-files.json 
{
"manifest": "arweave/paths",
"version": "0.1.0",
"paths": {
"01.jpeg": {
"id": "ItVxP8RILEsBjTObeO_piBUME31mj9fa8XJ00v_A94o"
},
"02.jpeg": {
"id": "eiCwWO60Qd8J6wz1ndB82jNgSDotAC8peN38ZAVonl4"
},
"03.jpeg": {
"id": "YUHwbOdf2sSKdGps03qH9LJHX4caYKM5_BOdBpMXOxc"
},
"04.jpeg": {
"id": "kD8Eft7JP82_u9whX710vzsSntn_WP07TgrSB2bGEso"
},
"05.jpeg": {
"id": "KSeYJwZORk3BzelAazsmd6laGAAugHPMqHoXTZ0V4BE"
}
}
}

Upload this manifest-files.json file to your bucket as well. Ensure the Content-Type tag is set to application/x.arweave-manifest+json

let manifestFile = {
manifest: 'arweave/paths',
version: '0.1.0',
paths: arHashPath
}

let blob = new Blob([JSON.stringify(manifestFile)], {
type: "application/json",
});

async function upload() {
try {
const task = new Upload({
client,
Bucket,
Key: 'YOUR_folder' + '/' + 'manifest-files.json',
Body: blob,
ContentType: 'application/x.arweave-manifest+json'
})
await task.done()
} catch (error) {
console.log(error)
}
}

// Execute upload
await upload()

Retrieve the AR Hash (Tx ID) for the manifest file.

async function getManifestArHash() {
try {
const data = await client.headObject({
Bucket,
Key: 'YOUR_folder' + '/' + 'manifest-files.json',
})
const meta = data.Metadata;
if (meta) {
return meta["arweave-hash"];
}
return "";
} catch (error) {
console.log(error)
}
}

// Since syncing to Arweave takes time, you may need to wait a few minutes before executing the following:
const arHash = await getManifestArHash()
console.log(arHash)

After executing the code, you should get a Tx ID similar to udKzWCDO2PFvxHjiTRhNXuDslsE4jiKlu9A2gY1b1WE.

Pro Tip

If you have many images to handle, 4EVERLAND’s Hosting module can be quite handy to generate manifest IDs.

  1. Select your NFT folder in the bucket, click ‘Snapshot’, and switch to the snapshot list to publish the folder.
  2. Copy the IPFS CID from the snapshot and go to the hosting page. Enter the CID into the ‘IPFS Deploy’ field.
  3. Choose ‘Arweave’ as the deployment platform. The AR Hash you get after successful deployment will serve as the Manifest ID for these images.

Create Metadata for Each NFT with Manifests ID

Generate a metadata.json for each NFT, taking 01.jpeg as an example.

// File name: 0
{
"name": "nft-ocean",
"attributes": [
{
"trait_type": "tokenID",
"value": "0"
}
],
"description": "nft-ocean image",
//insert image's ManifestID + '/'+ file name
"image": "ar://QmZ3Y31SwLU77CDfBoL5MphuSmrv414d2ZyunVcbNAJQRQ/01.jpeg"
}

Following the steps above for uploading images, upload each NFT’s metadata.json file to the bucket. Name each metadata file according to its TokenID.

After uploading, collect the obtained Transaction IDs (TxIDs) and generate another JSON file in Manifest format.

{
"manifest":"arweave/paths",
"version":"0.1.0",
"paths":{
"0":{
"id":"JcFZfJEJrDKudQsRfN2JnsBPUQTynk0i5XfUqCBcERw"
},
"1":{
"id":"vEUOeIky5hv2GlD2SP2d9TYAua2pkxYxehOczqTGqfU"
},
"2":{
"id":"hd6bI2-c0gr-9ZZsoO8jn_CTRFqWIRwRj5WckgfbmEY"
},
"3":{
"id":"PVAOd8D2JGCW5oOf4bulbQn2npcaaDAexY8sJKzZiEU"
},
"4":{
"id":"lT4RbJEIGsqwwEZB0gxeT1x0nov40crEtBWDHgKQx80"
}
}
}

Upload this manifest-metadata.json file to the bucket, specifying the content type as application/x.arweave-manifest+json. Retrieve the ArHash (TxID) for this file. This ID will serve as the BaseURI to be set in the contract.

Example: In this case, the obtained TxID is: 2svkHmAC3So_M-LUtDlcDeqPZEwSZsY64AB9L8cA-Uk

Deploy NFT to Contract

For specific steps, refer to the NFT contract deployment tutorial. When you get to the “Resource Preparation” step, skip it and go directly to the BaseURI setting step.

setBaseURI.js

require("dotenv").config()
const hre = require("hardhat");
const PRIVATE_KEY = process.env.PRIVATE_KEY
const NETWORK = process.env.NETWORK
const API_KEY = process.env.API_KEY


const provider = new hre.ethers.providers.InfuraProvider(NETWORK, API_KEY);
// Auto-generated after contract compilation
const abi = require("../artifacts/contracts/NFT_WEB3_EXPLORER.sol/NFT_WEB3_EXPLORER.json").abi
const contractAddress = "contract address"
const contract = new hre.ethers.Contract(contractAddress, abi, provider)
const wallet = new hre.ethers.Wallet(PRIVATE_KEY, provider)
const baseURI = "ar://2svkHmAC3So_M-LUtDlcDeqPZEwSZsY64AB9L8cA-Uk/" //metadata 的Manifest ID

async function main() {
const contractWithSigner = contract.connect(wallet);
// Method to call setBaseURI
const tx = await contractWithSigner.setBaseURI(baseURI)
console.log(tx.hash);
await tx.wait();
console.log("setBaseURL success");
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

Final Steps: The subsequent steps will be the same as in the tutorial. Once minting is complete, check your NFT on OpenSea.

About 4EVERLAND

4EVERLAND is a Web3 infrastructure powered by blockchain technology that provides developers with convenient, efficient, and low-cost storage, network, and computing capabilities. It is committed to helping developers smoothly transition from Web2.0 to Web3.0 and building a Web3.0 cloud computing platform friendly to Web2.0.

Website | Twitter | Telegram | Discord | Reddit | Medium | Email

--

--

4EVERLAND
4EVERLAND

4EVERLAND is a Web3.0 cloud computing platform with global acceleration, privacy protection, distributed storage, and other technical features.