Onchain Folders
Onchain folders are powerful way to organize transactions on Irys. Use them to reference onchain data by logical names instead of transaction IDs.
Why Use Onchain Folders?
- Logical Grouping: Create organized and readable structures for onchain data.
- Human-Readable Referencing: Replace transaction IDs with logical names, improving accessibility.
- Cross-Ownership Grouping: Include any transactions on Irys, even if they weren’t created by you.
- Flexibility: Add new files to existing folders at any time.
How The Irys Gateway Resolves Onchain Folders
Each onchain folder is uniquely identifiable by a manifest ID. To download transactions in an onchain folder, request them from the Irys gateway using a URL formatted as:
https://gateway.irys.xyz/:manifestId/:pathName
The gateway then:
- Looks up the manifest by ID.
- Looks in the manifest to see if the path exists.
- Returns the transaction associated with the path if found.
- Returns 404 if not found.
For example, if you have a manifest with ID 8eNpkShMwdbiNBtGuVGBKp8feDZCa21VppX2eDi3eLME
containing the following:
Tx ID | Path Name |
---|---|
DTMcqFqwaDukaYxs7iK2fa6CuMtyi7sN93rBGSAa13Ug | foo1.png |
5TQU2ETHGRPjJKPoeQkkgMB6zRpK8ptheWF8jdkbtJHR | foo2.png |
8nond6kkdYS14QjA5tZNCRDQQrgVNd7gdhx3L4XRJD1b | foo3.png |
https://gateway.irys.xyz/8eNpkShMwdbiNBtGuVGBKp8feDZCa21VppX2eDi3eLME/foo1.png
Creating Onchain Folders
Automatically
When you upload groups of files using the Irys SDK's uploadFolder()
function or the CLI's upload-dir
command, an onchain folder for you is automatically created for you. The return value contains the manifest ID, which can be combined with the original file names as above.
Manually
To manually create an onchain folder:
- Create a JavaScript
Map
object where each entry maps a unique transaction ID to a unique path. (Paths are arbitrary; you can use anything that conforms to valid URL syntax.) - Create a
Manifest
object by passing theMap
object toirys.uploader.generateFolder()
. - Upload the
Manifest
object to Irys.
const createOnchainFolder = async () => {
const irysUploader = await getIrysUploader();
// You can map ANY logical name to ANY transaction ID
const map = new Map();
map.set("image-1.png", "DTMcqFqwaDukaYxs7iK2fa6CuMtyi7sN93rBGSAa13Ug");
map.set("image-2.png", "5TQU2ETHGRPjJKPoeQkkgMB6zRpK8ptheWF8jdkbtJHR");
map.set("image-3.png", "8nond6kkdYS14QjA5tZNCRDQQrgVNd7gdhx3L4XRJD1b");
const manifest = await irysUploader.uploader.generateFolder({ items: map });
console.log({ manifest });
const tags = [
{ name: "Type", value: "manifest" },
{ name: "Content-Type", value: "application/x.irys-manifest+json" },
];
const receipt = await irysUploader.upload(JSON.stringify(manifest), { tags });
console.log(`Manifest uploaded to https://gateway.irys.xyz/${receipt.id}`);
console.log(`File 1 available at https://gateway.irys.xyz/${receipt.id}/image-1.png`);
console.log(`File 2 available at https://gateway.irys.xyz/${receipt.id}/image-2.png`);
console.log(`File 3 available at https://gateway.irys.xyz/${receipt.id}/image-3.png`);
};
Mutable Onchain Folders
Mutable onchain folders let you add new files to an existing folder after it’s created. By using a single, static URL that always points to the most recent version, you can expand an NFT collection, add updated documents, or manage evolving datasets without needing to change the original reference. This approach ensures consistency while allowing dynamic updates over time, with the security that only the original creator can modify the folder's contents.
How to Create Mutable Onchain Folders
-
Upload the Initial Folder: Use the SDK or CLI to upload your initial set of files. This will create a base manifest ID that uniquely identifies the folder.
-
Reference Files Using a Mutable URL: Reference files in this folder using a URL formatted as
https://gateway.irys.xyz/mutable/:manifestId/:fileName
. This URL will always resolve to the most recent version of the folder. -
Upload New Files: When you need to add new files, upload them individually using the SDK or CLI.
-
Create a Onchain Folder: Manually create a new onchain folder that includes both the original files and any new files as outlined above.
-
Tag the New Manifest: Upload the new onchain folder to Irys, tagging it with
Root-TX
equal to the original manifest ID. This links the new onchain folder to the original folder; the "mutable" URL remains consistent. -
Access the Updated Folder: The URL
https://gateway.irys.xyz/mutable/:manifestId/:fileName
will now point to the latest version of the folder, including all newly added files.
import { Uploader } from "@irys/upload";
import { Ethereum } from "@irys/upload-ethereum";
import "dotenv/config";
import fetch from "node-fetch";
const getIrysUploader = async () => {
const irysUploader = await Uploader(Ethereum).withWallet(process.env.PRIVATE_KEY);
return irysUploader;
};
const downloadOriginalManifest = async (originalManifestId: string) => {
try {
const response = await fetch(`https://gateway.irys.xyz/${originalManifestId}`);
if (!response.ok) throw new Error("Failed to fetch original manifest");
return response.json();
} catch (error) {
console.error("Error downloading original manifest", error);
throw error;
}
};
const appendToManifest = (originalManifest: any, newFiles: Map<string, string>) => {
newFiles.forEach((txId, fileName) => {
originalManifest.paths[fileName] = { id: txId };
});
return originalManifest;
};
const uploadManifest = async (manifest: any, originalManifestId: string): Promise<void> => {
const irysUploader = await getIrysUploader();
const manifestTags = [
{ name: "Type", value: "manifest" },
{ name: "Content-Type", value: "application/x.irys-manifest+json" },
{ name: "Root-TX", value: originalManifestId },
];
try {
const manifestResponse = await irysUploader.upload(JSON.stringify(manifest), { tags: manifestTags });
console.log(`Manifest uploaded ==> https://gateway.irys.xyz/mutable/${originalManifestId}`);
} catch (e) {
console.error("Error uploading manifest", e);
}
};
const main = async () => {
try {
// Your original manifest ID
const originalManifestId = "8eNpkShMwdbiNBtGuVGBKp8feDZCa21VppX2eDi3eLME";
// Step 1: Download the original manifest
const originalManifest = await downloadOriginalManifest(originalManifestId);
// Step 2: Prepare new files to add to the manifest
const newFiles = new Map<string, string>();
newFiles.set("new-1.png", "4pTiwGwur38s4vyVD8EERxDYgAGDM8kyzEh9c5QPF9Zw");
newFiles.set("new-2.png", "BieiKJE1Nh6ydCYqxmDjHFGzuG7enRr6NYmKisqSwUQo");
// Step 3: Append new files to the manifest
const updatedManifest = appendToManifest(originalManifest, newFiles);
// Step 4: Upload the updated manifest
await uploadManifest(updatedManifest, originalManifestId);
} catch (e) {
console.error("Error in main execution", e);
}
};
main();