storage-plus-chain
Skill15 starsChanged 57 days agoMITView on GitHub
- Category: cross-layer - SDK: @0gfoundation/0g-storage-ts-sdk ^1.2.12, ethers 6.13.1 - Activation Triggers: "on-chain reference", "NFT metadata on 0G", "store hash on-chain", "registry contract", "chain and storage" Combine 0G Storage with 0G Chain smart contracts to create on-chain references to off-chain data. Common patterns include NFT metadata storage, content registries, and verifiable document systems.
- Reads credentials
In this repository26 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- agent-accountsSkill
- agent-reputationSkill
- deploy-agentSkill
- interact-agentSkill
- manage-agentSkill
- deploy-contractSkill
- interact-contractSkill
- scaffold-projectSkill
- account-managementSkill
- embeddingsSkill
- fine-tuningSkill
- image-editingSkill
- provider-discoverySkill
- speech-to-textSkill
- streaming-chatSkill
- text-to-imageSkill
- video-generationSkill
- compute-plus-storageSkill
- 0g-pc-setupSkill
- 0g-pc-switch-modelSkill
- 0g-pc-uninstallSkill
- download-fileSkill
- merkle-verificationSkill
- upload-fileSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 2 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 36
Use evmVersion: "cancun" for contract compilation
- Distinctive: line 37
Verify downloaded data matches on-chain root hash
- Distinctive: line 38
Use ethers v6 syntax
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Storage + Chain Integration
Metadata
- Category: cross-layer
- SDK:
@0gfoundation/0g-storage-ts-sdk^1.2.12,ethers6.13.1 - Activation Triggers: "on-chain reference", "NFT metadata on 0G", "store hash on-chain", "registry contract", "chain and storage"
Purpose
Combine 0G Storage with 0G Chain smart contracts to create on-chain references to off-chain data. Common patterns include NFT metadata storage, content registries, and verifiable document systems.
Prerequisites
- Node.js >= 18
@0gfoundation/0g-storage-ts-sdkandethersinstalled- Hardhat configured with
evmVersion: "cancun" - Funded wallet with 0G tokens
.envwithPRIVATE_KEY,RPC_URL,STORAGE_INDEXER
Quick Workflow
- Upload data to 0G Storage (get root hash)
- Deploy or interact with registry contract on 0G Chain
- Store the root hash on-chain
- Later: read root hash from chain, download from storage
Core Rules
ALWAYS
- Upload to storage BEFORE registering on-chain
- Store the root hash on-chain (not the full data)
- Use
evmVersion: "cancun"for contract compilation - Verify downloaded data matches on-chain root hash
- Use ethers v6 syntax
NEVER
- Store large data directly on-chain (expensive)
- Register a root hash before upload completes
- Hardcode private keys
- Skip Merkle verification when downloading referenced data
Code Examples
Registry Contract
// contracts/StorageRegistry.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract StorageRegistry {
struct FileRecord {
bytes32 rootHash;
address uploader;
uint256 timestamp;
string metadata;
}
mapping(uint256 => FileRecord) public files;
uint256 public fileCount;
event FileRegistered(uint256 indexed id, bytes32 rootHash, address uploader);
function registerFile(bytes32 rootHash, string calldata metadata) external returns (uint256) {
uint256 id = fileCount++;
files[id] = FileRecord({
rootHash: rootHash,
uploader: msg.sender,
timestamp: block.timestamp,
metadata: metadata
});
emit FileRegistered(id, rootHash, msg.sender);
return id;
}
function getFile(uint256 id) external view returns (FileRecord memory) {
return files[id];
}
function verifyUploader(uint256 id, address uploader) external view returns (bool) {
return files[id].uploader == uploader;
}
}Upload and Register
import { ZgFile, Indexer } from '@0gfoundation/0g-storage-ts-sdk';
import { ethers } from 'ethers';
import 'dotenv/config';
async function uploadAndRegister(
filePath: string,
metadata: string,
registryAddress: string,
registryAbi: any[],
): Promise<{ rootHash: string; fileId: number }> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
// Step 1: Upload to 0G Storage
const indexer = new Indexer(process.env.STORAGE_INDEXER!);
const file = await ZgFile.fromFilePath(filePath);
let rootHash: string;
try {
const [tree, err] = await file.merkleTree();
if (err) throw new Error(`Merkle tree error: ${err}`);
rootHash = tree!.rootHash();
const [, uploadErr] = await indexer.upload(file, process.env.RPC_URL!, wallet);
if (uploadErr) throw new Error(`Upload failed: ${uploadErr.message}`);
console.log('Uploaded to storage, root hash:', rootHash);
} finally {
await file.close();
}
// Step 2: Register on-chain
const registry = new ethers.Contract(registryAddress, registryAbi, wallet);
const tx = await registry.registerFile(rootHash, metadata);
const receipt = await tx.wait();
// Extract file ID from event
const event = receipt.logs.find((log: any) => log.fragment?.name === 'FileRegistered');
const fileId = Number(event?.args?.[0] ?? 0);
console.log('Registered on-chain, file ID:', fileId);
return { rootHash, fileId };
}Retrieve from Chain + Storage
async function retrieveFile(
fileId: number,
outputPath: string,
registryAddress: string,
registryAbi: any[],
): Promise<void> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const registry = new ethers.Contract(registryAddress, registryAbi, provider);
// Step 1: Get root hash from chain
const record = await registry.getFile(fileId);
const rootHash = record.rootHash;
console.log('Root hash from chain:', rootHash);
console.log('Uploader:', record.uploader);
console.log('Timestamp:', new Date(Number(record.timestamp) * 1000));
// Step 2: Download from storage with verification
// Note: download() can throw or return errors — handle both
const indexer = new Indexer(process.env.STORAGE_INDEXER!);
try {
const dlErr = await indexer.download(rootHash, outputPath, true);
if (dlErr) throw dlErr;
} catch (error: any) {
throw new Error(`Download failed: ${error.message}`);
}
console.log('Downloaded and verified:', outputPath);
}NFT Metadata Pattern
async function storeNFTMetadata(
imageFilePath: string,
name: string,
description: string,
): Promise<string> {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const indexer = new Indexer(process.env.STORAGE_INDEXER!);
// Upload image to 0G Storage
const imageFile = await ZgFile.fromFilePath(imageFilePath);
let imageRootHash: string;
try {
const [tree, err] = await imageFile.merkleTree();
if (err) throw err;
imageRootHash = tree!.rootHash();
const [, imgErr] = await indexer.upload(imageFile, process.env.RPC_URL!, wallet);
if (imgErr) throw new Error(`Image upload failed: ${imgErr.message}`);
} finally {
await imageFile.close();
}
// Create metadata JSON pointing to 0G Storage
const metadata = {
name,
description,
image: `0g://${imageRootHash}`,
properties: {
storageLayer: '0G',
imageRootHash,
},
};
// Upload metadata to 0G Storage
const fs = await import('fs');
const os = await import('os');
const path = await import('path');
const tempPath = path.join(os.tmpdir(), `metadata-${Date.now()}.json`);
fs.writeFileSync(tempPath, JSON.stringify(metadata, null, 2));
const metadataFile = await ZgFile.fromFilePath(tempPath);
let metadataRootHash: string;
try {
const [tree, err] = await metadataFile.merkleTree();
if (err) throw err;
metadataRootHash = tree!.rootHash();
const [, metaErr] = await indexer.upload(metadataFile, process.env.RPC_URL!, wallet);
if (metaErr) throw new Error(`Metadata upload failed: ${metaErr.message}`);
} finally {
await metadataFile.close();
fs.unlinkSync(tempPath);
}
console.log('Image root hash:', imageRootHash);
console.log('Metadata root hash:', metadataRootHash);
return metadataRootHash;
}Architecture
┌──────────────────────┐ ┌──────────────────────┐
│ 0G Chain │ │ 0G Storage │
│ │ │ │
│ Registry Contract │────▶│ Files (root hashes) │
│ - rootHash (bytes32) │ │ - Data chunks │
│ - uploader (address) │ │ - Merkle proofs │
│ - metadata (string) │ │ │
└──────────────────────┘ └──────────────────────┘
On-chain Off-chain
(small refs) (large data)Anti-Patterns
// BAD: Storing full file data on-chain
await contract.storeFile(fileBuffer); // Extremely expensive!
// BAD: Registering before upload completes
const rootHash = tree.rootHash();
await registry.registerFile(rootHash, metadata);
await indexer.upload(file, process.env.RPC_URL!, wallet); // Upload AFTER register — data not available!
// BAD: Skipping verification on download
await indexer.download(rootHash, outputPath, false); // Unverified!Common Errors & Fixes
| Error | Cause | Fix |
invalid opcode | Wrong evmVersion | Set evmVersion: "cancun" |
file not found | Registered before upload | Upload first, then register |
verification failed | Data tampered | Re-download from 0G Storage |
insufficient funds | Wallet empty | Fund wallet |
Related Skills
- Upload File — storage upload
- Download File — storage download
- Deploy Contract — deploy registry
- Interact Contract — read/write registry
References
- Storage Patterns
- Chain Patterns
- Security Patterns
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

