Inscribe Your First File
Store any file permanently on the Hedera network using HCS inscriptions. This tutorial will teach you how to inscribe files using the HCS-1 standard.
What are Inscriptions?
Inscriptions allow you to store data directly on the Hedera network using HCS topics. Unlike traditional storage methods:
- Permanent: Data lives forever on the network
- Decentralized: No single point of failure
- Verifiable: Cryptographic proof of existence
- No external dependencies: No IPFS, Arweave, or centralized servers
Prerequisites
- Environment setup completed
- Standards SDK installed
- Hedera testnet account with HBAR
How Inscriptions Work
The HCS-1 standard handles:
- Chunking: Splits large files into 1KB chunks
- Ordering: Maintains correct sequence
- Reassembly: Reconstructs the original file
- Verification: Ensures data integrity
Step 1: Basic Text Inscription
Start with inscribing a simple text message:
// inscribe-text.js
import { inscribe } from '@hashgraph-online/standards-sdk';
import * as fs from 'fs';
import * as dotenv from 'dotenv';
dotenv.config();
async function inscribeText() {
const clientConfig = {
accountId: process.env.HEDERA_ACCOUNT_ID,
privateKey: process.env.HEDERA_PRIVATE_KEY,
network: 'testnet'
};
console.log("📝 Inscribing text message...");
const result = await inscribe(
{
type: 'buffer',
buffer: Buffer.from('Hello, Hedera! This is my first inscription.'),
fileName: 'message.txt',
mimeType: 'text/plain'
},
clientConfig,
{
waitForConfirmation: true,
progressCallback: (data) => {
console.log(`Progress: ${data.percentage}%`);
}
}
);
console.log("✅ Inscription successful!");
console.log(`Topic ID: ${result.result.topicId}`);
console.log(`Transaction ID: ${result.result.transactionId}`);
// Save transaction ID for later retrieval
fs.writeFileSync('inscription-tx-id.txt', result.result.transactionId);
if (result.confirmed) {
console.log("Content confirmed on network:");
console.log(result.inscription.content);
}
}
inscribeText();
Step 2: Inscribe an Image
Inscribe images or any binary files:
// inscribe-image.js
import { inscribe } from '@hashgraph-online/standards-sdk';
import * as fs from 'fs';
import * as path from 'path';
import * as dotenv from 'dotenv';
dotenv.config();
async function inscribeImage() {
const clientConfig = {
accountId: process.env.HEDERA_ACCOUNT_ID,
privateKey: process.env.HEDERA_PRIVATE_KEY,
network: 'testnet'
};
// Read image file
const imagePath = './my-image.png';
const imageBuffer = fs.readFileSync(imagePath);
const fileName = path.basename(imagePath);
console.log(`📸 Inscribing ${fileName} (${imageBuffer.length} bytes)...`);
const result = await inscribe(
{
type: 'buffer',
buffer: imageBuffer,
fileName: fileName,
mimeType: 'image/png'
},
clientConfig,
{
waitForConfirmation: true,
progressCallback: (data) => {
console.log(`Chunks uploaded: ${data.chunksUploaded}/${data.totalChunks}`);
}
}
);
console.log("✅ Image inscribed successfully!");
console.log(`Topic ID: ${result.result.topicId}`);
console.log(`Transaction ID: ${result.result.transactionId}`);
console.log(`View at: hcs://1/${result.result.topicId}`);
// Save the transaction ID for later retrieval
fs.writeFileSync('inscription-tx-id.txt', result.result.transactionId);
// Save the HRL for reference
fs.writeFileSync('inscription-hrl.txt', `hcs://1/${result.result.topicId}`);
}
inscribeImage();
Step 3: Inscribe from URL
Inscribe content directly from a URL:
// inscribe-from-url.js
import { inscribe } from '@hashgraph-online/standards-sdk';
import * as dotenv from 'dotenv';
dotenv.config();
async function inscribeFromUrl() {
const clientConfig = {
accountId: process.env.HEDERA_ACCOUNT_ID,
privateKey: process.env.HEDERA_PRIVATE_KEY,
network: 'testnet'
};
const imageUrl = 'https://example.com/sample-image.jpg';
console.log(`🌐 Inscribing from URL: ${imageUrl}`);
const result = await inscribe(
{
type: 'url',
url: imageUrl
},
clientConfig,
{
waitForConfirmation: true,
metadata: {
name: 'Sample Image',
description: 'Image inscribed from URL',
source: imageUrl
}
}
);
console.log("✅ URL content inscribed!");
console.log(`Topic ID: ${result.result.topicId}`);
console.log(`HRL: hcs://1/${result.result.topicId}`);
}
inscribeFromUrl();
Step 4: Retrieve Inscribed Content
Retrieve and verify inscribed content:
// retrieve-inscription.js
import { retrieveInscription } from '@hashgraph-online/standards-sdk';
import * as fs from 'fs';
import * as dotenv from 'dotenv';
dotenv.config();
async function retrieveContent() {
// Use the transaction ID from the inscription result
const transactionId = '[email protected]'; // Replace with your transaction ID
console.log(`🔍 Retrieving inscription with transaction ID ${transactionId}...`);
const inscription = await retrieveInscription(
transactionId,
{
network: 'testnet',
apiKey: process.env.API_KEY // Optional: for faster retrieval
}
);
console.log("✅ Inscription retrieved!");
console.log(`Topic ID: ${inscription.topic_id}`);
console.log(`Content Type: ${inscription.content_type}`);
console.log(`Sequence Number: ${inscription.sequence_number}`);
// Save retrieved content
if (inscription.content_type.startsWith('text/')) {
// Text content
console.log("Content:", inscription.content);
} else if (inscription.url) {
// Binary content available via URL
console.log(`Content URL: ${inscription.url}`);
// You can fetch the content from the URL if needed
}
}
retrieveContent();
Understanding HRLs
Hedera Resource Locators (HRLs) uniquely identify inscribed content:
hcs://1/0.0.123456
hcs://
- Protocol identifier1
- HCS-1 standard version0.0.123456
- Topic ID containing the inscription
Inscription Metadata
Add metadata to your inscriptions:
const result = await inscribe(
{
type: 'buffer',
buffer: fileBuffer,
fileName: 'document.pdf',
mimeType: 'application/pdf'
},
clientConfig,
{
metadata: {
name: 'Important Document',
description: 'Q4 2024 Report',
author: 'John Doe',
tags: ['report', 'finance', '2024'],
customField: 'any-value'
}
}
);
Cost Estimation
Inscription costs depend on file size:
File Size | Approximate Cost |
---|---|
1 KB | ~$0.0001 |
100 KB | ~$0.01 |
1 MB | ~$0.10 |
10 MB | ~$1.00 |
Best Practices
1. File Size Optimization
// Compress before inscribing
import zlib from 'zlib';
const compressed = zlib.gzipSync(originalBuffer);
const result = await inscribe({
type: 'buffer',
buffer: compressed,
fileName: 'data.gz',
mimeType: 'application/gzip'
}, clientConfig);
2. Progress Tracking
await inscribe(data, clientConfig, {
progressCallback: (progress) => {
console.log(`Progress: ${progress.percentage}%`);
console.log(`Chunks: ${progress.chunksUploaded}/${progress.totalChunks}`);
console.log(`Bytes: ${progress.bytesUploaded}/${progress.totalBytes}`);
}
});
3. Error Handling
try {
const result = await inscribe(data, clientConfig);
console.log("Success:", result);
} catch (error) {
if ((error as any).code === 'INSUFFICIENT_BALANCE') {
console.error("Not enough HBAR for inscription");
} else if ((error as any).code === 'FILE_TOO_LARGE') {
console.error("File exceeds maximum size");
} else {
console.error("Inscription failed:", error);
}
}
Advanced: Batch Inscriptions
Inscribe multiple files efficiently:
async function batchInscribe(files) {
const results = [];
for (const file of files) {
console.log(`Inscribing ${file.name}...`);
const result = await inscribe(
{
type: 'buffer',
buffer: file.buffer,
fileName: file.name,
mimeType: file.mimeType
},
clientConfig,
{
waitForConfirmation: false // Don't wait for each one
}
);
results.push(result);
}
// Wait for all confirmations
const confirmed = await Promise.all(
results.map(r => waitForConfirmation(r.transactionId))
);
return confirmed;
}
Next Steps
- Create NFT with Inscriptions - Use inscriptions for NFT metadata
- HCS-5 Hashinals - Learn about the inscription standard
Troubleshooting
File too large error
- Maximum single topic message: 1024 bytes
- HCS-1 handles chunking automatically
- Very large files (>100MB) may timeout
- Solution: Compress files before inscribing or split into smaller parts
Inscription not found
- Wait 3-5 seconds after inscribing for consensus
- Verify correct topic ID format (e.g.,
0.0.123456
) - Check you're querying the correct network (testnet vs mainnet)
- Use the same network for inscribing and retrieving
Invalid MIME type
- Use standard MIME types only
- Common examples:
- Text:
text/plain
,text/html
,text/css
- Images:
image/png
,image/jpeg
,image/gif
- Documents:
application/json
,application/pdf
- Text:
- Check the file extension matches the MIME type
Transaction timeout
- Large files may take longer to inscribe
- Network congestion can cause delays
- Solution: Use
waitForConfirmation: false
and poll for status - Increase timeout in configuration if needed
Insufficient HBAR balance
- Each chunk costs approximately $0.0001
- Check your account balance before inscribing
- Formula:
cost ≈ (fileSize / 1024) * $0.0001
- Add buffer for network fees
Progress callback not working
- Ensure you're passing the callback correctly
- The callback is only triggered during chunking
- Small files (less than 1KB) may complete too quickly to trigger
- Check console for any error messages