Skip to content

File Manipulation

The purpose of this guide is to help you create a mental model of what pieces of data you'll need to extract from the file you intend to upload to DataHaven and how to retrieve it from the network. In this guide, you'll learn how to initialize the File Manager from your file, create the fingerprint and file key, issue a storage request, and retrieve the data for that request.

Prerequisites

  • Node.js v22+ installed
  • A TypeScript project

    Need a starter project?

    If you don't have an existing project, follow these steps to create a TypeScript project you can use to follow the guides in this section:

    1. Create a new project folder by executing the following command in the terminal:

      mkdir datahaven-project && cd datahaven-project
      
    2. Initialize a package.json file using the correct command for your package manager:

      pnpm init
      
      yarn init
      
      npm init --y
      
    3. Add the TypeScript and Node type definitions to your projects using the correct command for your package manager:

      pnpm add -D typescript tsx ts-node @types/node
      
      yarn add -D typescript tsx ts-node @types/node
      
      npm install -D typescript tsx ts-node @types/node
      
    4. Create a tsconfig.json file in the root of your project and paste the following configuration:

      tsconfig.json
      {
          "compilerOptions": {
              "target": "ES2022",
              "module": "nodenext",
              "moduleResolution": "NodeNext",
              "esModuleInterop": true,
              "strict": true,
              "skipLibCheck": true,
              "outDir": "dist",
              "declaration": true,
              "sourceMap": true
          },
          "include": ["src/**/*.ts"]
      }
      
    5. Initialize the src directory:

      mkdir src && touch src/index.ts
      
  • Dependencies installed

  • Clients initialized

  • A bucket created with the ID handy

  • A file to upload to DataHaven (any file type is accepted; the current testnet file size limit is 5 MB)

Initialize the Script Entry Point

First, create an index.ts file if you haven't already. Its run method will orchestrate all the logic in this guide, and you’ll replace the labelled placeholders with real code step by step. By now, your services folder (including the MSP and client helper services) should already be created. If not, see the Get Started guide.

Add the following code to your index.ts file:

index.ts
import '@storagehub/api-augment';
import { FileManager, initWasm, ReplicationLevel } from '@storagehub-sdk/core';
import {
  polkadotApi,
  storageHubClient,
  publicClient,
  account,
} from './services/clientService.js';
import { statSync, createReadStream } from 'fs';
import { Readable } from 'stream';
import { getMspInfo } from './services/mspService.js';
import { TypeRegistry } from '@polkadot/types';
import { AccountId20, H256 } from '@polkadot/types/interfaces';

async function run() {
  // For anything from @storagehub-sdk/core to work, initWasm() is required
  // on top of the file
  await initWasm();

  // Add your bucket ID here from the bucket you created earlier
  // Example (32byte hash):
  // 0xdd2148ff63c15826ab42953a9d214770e6c8a73b22b83d28819a1777ab9d1322
  const bucketId = 'INSERT_BUCKET_ID';

  // Specify the file name of the file to be uploaded
  const fileName = 'INSERT_FILE_NAME'; // Example: filename.jpeg
  const filePath = new URL(`./files/${fileName}`, import.meta.url).pathname;

  // --- File Manipulation ---
  // **PLACEHOLDER FOR STEP 1: INITIALIZE FILE MANAGER**
  // **PLACEHOLDER FOR STEP 2: CREATE FINGERPRINT AND REMAINING STORAGE REQUEST PARAMETERS**
  // **PLACEHOLDER FOR STEP 3: ISSUE STORAGE REQUEST**
  // **PLACEHOLDER FOR STEP 4: COMPUTE THE FILE KEY**
  // **PLACEHOLDER FOR STEP 5: RETRIEVE STORAGE REQUEST DATA**
  // **PLACEHOLDER FOR STEP 6: READ STORAGE REQUEST DATA**

  // Disconnect the Polkadot API at the very end
  await polkadotApi.disconnect();
}

await run();

Initialize File Manager

To initialize the File Manager, add the following code to your index.ts file:

index.ts // **PLACEHOLDER FOR STEP 1: INITIALIZE FILE MANAGER**
// Set up FileManager
const fileSize = statSync(filePath).size;
const fileManager = new FileManager({
  size: fileSize,
  stream: () =>
    Readable.toWeb(createReadStream(filePath)) as ReadableStream<Uint8Array>,
});

Create Fingerprint and Remaining Storage Request Parameters

To issue a storage request, you need to prepare the following:

  • fingerprint of your file (from FileManager)
  • fileSize in BigInt format
  • mspId of the target MSP
  • peerId extracted from the MSP’s multiaddresses
  • replicationLevel that defines how redundancy is applied
  • replicas indicating how many copies to request
  • bucketId created earlier (already passed as a parameter in uploadFile method)
  • fileName you plan to store (already passed as a parameter in uploadFile method)

Add the following code to gather these values:

index.ts // **PLACEHOLDER FOR STEP 2: CREATE FINGERPRINT AND REMAINING STORAGE REQUEST PARAMETERS**
// Get file details

const fingerprint = await fileManager.getFingerprint();
console.log(`Fingerprint: ${fingerprint.toHex()}`);

const fileSizeBigInt = BigInt(fileManager.getFileSize());
console.log(`File size: ${fileSize} bytes`);

// Get MSP details

// Fetch MSP details from the backend (includes its on-chain ID and libp2p addresses)
const { mspId, multiaddresses } = await getMspInfo();
// Ensure the MSP exposes at least one multiaddress (required to reach it over libp2p)
if (!multiaddresses?.length) {
  throw new Error('MSP multiaddresses are missing');
}
// Extract the MSP’s libp2p peer IDs from the multiaddresses
// Each address should contain a `/p2p/<peerId>` segment
const peerIds: string[] = extractPeerIDs(multiaddresses);
// Validate that at least one valid peer ID was found
if (peerIds.length === 0) {
  throw new Error('MSP multiaddresses had no /p2p/<peerId> segment');
}

// Extracts libp2p peer IDs from a list of multiaddresses.
// A multiaddress commonly ends with `/p2p/<peerId>`, so this function
// splits on that delimiter and returns the trailing segment when present.
function extractPeerIDs(multiaddresses: string[]): string[] {
  return (multiaddresses ?? [])
    .map((addr) => addr.split('/p2p/').pop())
    .filter((id): id is string => !!id);
}

// Set the redundancy policy for this request.
// Custom replication allows the client to specify an exact replica count.
const replicationLevel = ReplicationLevel.Custom;
const replicas = 1;

Issue Storage Request

Issue the storage request by adding the following code:

Note

After issuing a storage request, it is crucial to wait for the transaction receipt, as shown in the code below. If writing custom storage-request-creation logic, make sure to include that step; otherwise, you will fetch storage request data before it is available.

index.ts // **PLACEHOLDER FOR STEP 3: ISSUE STORAGE REQUEST**
// Issue storage request
const txHash: `0x${string}` | undefined =
  await storageHubClient.issueStorageRequest(
    bucketId as `0x${string}`,
    fileName,
    fingerprint.toHex() as `0x${string}`,
    fileSizeBigInt,
    mspId as `0x${string}`,
    peerIds,
    replicationLevel,
    replicas
  );
console.log('issueStorageRequest() txHash:', txHash);
if (!txHash) {
  throw new Error('issueStorageRequest() did not return a transaction hash');
}

// Wait for storage request transaction
// Don't proceed until receipt is confirmed on chain
const receipt = await publicClient.waitForTransactionReceipt({
  hash: txHash,
});
if (receipt.status !== 'success') {
  throw new Error(`Storage request failed: ${txHash}`);
}
console.log('issueStorageRequest() txReceipt:', receipt);

Upon a successful storage request, the output will look something like this:

ts-node index.ts issueStorageRequest() txHash: 0x1cb9446510d9f204c93f1c348e0a13422adef91f1740ea0fdb1534e3ccb232ef
issueStorageRequest() txReceipt: {
  transactionHash: '0x1cb9446510d9f204c93f1c348e0a13422adef91f1740ea0fdb1534e3ccb232ef',
  transactionIndex: 0,
  blockHash: '0x0cd98b5d6050b926e6876a5b09124d1840e2c94d95faffdd6668a659e3c5c6a7',
  from: '0x00fa35d84a43db75467d2b2c1ed8974aca57223e',
  to: '0x0000000000000000000000000000000000000404',
  blockNumber: 98684n,
  cumulativeGasUsed: 239712n,
  gasUsed: 239712n,
  contractAddress: null,
  logs: [
    {
      address: '0x0000000000000000000000000000000000000404',
      topics: [Array],
      data: '0x',
      blockHash: '0x0cd98b5d6050b926e6876a5b09124d1840e2c94d95faffdd6668a659e3c5c6a7',
      blockNumber: 98684n,
      transactionHash: '0xfb344dc05359ee4d13189e65fc3230a1998a1802d3a0cf929ffb80a0670d7ce0',
      transactionIndex: 0,
      logIndex: 0,
      transactionLogIndex: '0x0',
      removed: false
    }
  ],
  logsBloom: '0x00000000000000040000000000000000000000000000000000000000000000040000000000000000000000000001000000000000000000000000080000000000000000040000000000000000000000000000000000000140000000000000000000000000000000000000000000000400000000100000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800800000000000000000000000000200000000000000000000000010000000000000000000000000000080000',
  status: 'success',
  effectiveGasPrice: 1000000000n,
  type: 'legacy'
}

Compute the File Key

To compute the deterministic file key, derive it from the owner (AccountId20), bucket ID, and file name:

index.ts // **PLACEHOLDER FOR STEP 4: COMPUTE THE FILE KEY**
// Compute file key
const registry = new TypeRegistry();
const owner = registry.createType(
  'AccountId20',
  account.address
) as AccountId20;
const bucketIdH256 = registry.createType('H256', bucketId) as H256;
const fileKey = await fileManager.computeFileKey(
  owner,
  bucketIdH256,
  fileName
);

Retrieve Storage Request Data

To retrieve storage request data, query fileSystem.storageRequests and pass in the computed file key:

index.ts // **PLACEHOLDER FOR STEP 5: RETRIEVE STORAGE REQUEST DATA**
// Verify storage request on chain
const storageRequest = await polkadotApi.query.fileSystem.storageRequests(
  fileKey
);
if (!storageRequest.isSome) {
  throw new Error('Storage request not found on chain');
}

Read Storage Request Data

To read storage request data, it first must be unwrapped as follows:

index.ts // **PLACEHOLDER FOR STEP 6: READ STORAGE REQUEST DATA**
// Read the storage request data
const storageRequestData = storageRequest.unwrap().toHuman();
console.log('Storage request data:', storageRequestData);
console.log(
  'Storage request bucketId matches initial bucketId:',
  storageRequestData.bucketId === bucketId
);
console.log(
  'Storage request fingerprint matches initial fingerprint: ',
  storageRequestData.fingerprint === fingerprint.toString()
);

Upon successful storage request verification, you'll see a message like:

ts-node index.ts
 Storage request data: {
  requestedAt: '387,185',
  expiresAt: '387,295',
  owner: '0x00FA35D84a43db75467D2B2c1ed8974aCA57223e',
  bucketId: '0x8009cc4028ab4c8e333b13d38b840107f8467e27be11e9624e3b0d505314a5da',
  location: 'helloworld.txt',
  fingerprint: '0x1bc3a71173c16c1eee04f7e7cf2591678b0b6cdf08eb81c638ae60a38b706aad',
  size_: '18',
  msp: [
    '0x0000000000000000000000000000000000000000000000000000000000000001',
    false
  ],
  userPeerIds: [
    '12D3KooWNEor6iiEAbZhCXqJbXibdjethDY8oeDoieVVxpZhQcW1',
    '12D3KooWNEor6iiEAbZhCXqJbXibdjethDY8oeDoieVVxpZhQcW1',
    '12D3KooWNEor6iiEAbZhCXqJbXibdjethDY8oeDoieVVxpZhQcW1'
  ],
  bspsRequired: '1',
  bspsConfirmed: '0',
  bspsVolunteered: '0',
  depositPaid: '1,000,010,114,925,524,930'
}
View complete index.ts
index.ts
import '@storagehub/api-augment';
import { FileManager, initWasm, ReplicationLevel } from '@storagehub-sdk/core';
import {
  polkadotApi,
  storageHubClient,
  publicClient,
  account,
} from './services/clientService.js';
import { statSync, createReadStream } from 'fs';
import { Readable } from 'stream';
import { getMspInfo } from './services/mspService.js';
import { TypeRegistry } from '@polkadot/types';
import { AccountId20, H256 } from '@polkadot/types/interfaces';

async function run() {
  // For anything from @storagehub-sdk/core to work, initWasm() is required
  // on top of the file
  await initWasm();

  // Add your bucket ID here from the bucket you created earlier
  // Example (32byte hash):
  // 0xdd2148ff63c15826ab42953a9d214770e6c8a73b22b83d28819a1777ab9d1322
  const bucketId = 'INSERT_BUCKET_ID';

  // Specify the file name of the file to be uploaded
  const fileName = 'INSERT_FILE_NAME'; // Example: filename.jpeg
  const filePath = new URL(`./files/${fileName}`, import.meta.url).pathname;

  // --- File Manipulation ---

  // Step 1: Initialize FileManager
  const fileSize = statSync(filePath).size;
  const fileManager = new FileManager({
    size: fileSize,
    stream: () =>
      Readable.toWeb(createReadStream(filePath)) as ReadableStream<Uint8Array>,
  });

  // Step 2: Create Fingerprint and Remaining Storage Request Parameters

  // Get file details

  const fingerprint = await fileManager.getFingerprint();
  console.log(`Fingerprint: ${fingerprint.toHex()}`);

  const fileSizeBigInt = BigInt(fileManager.getFileSize());
  console.log(`File size: ${fileSize} bytes`);

  // Get MSP details

  // Fetch MSP details from the backend (includes its on-chain ID and libp2p addresses)
  const { mspId, multiaddresses } = await getMspInfo();
  // Ensure the MSP exposes at least one multiaddress (required to reach it over libp2p)
  if (!multiaddresses?.length) {
    throw new Error('MSP multiaddresses are missing');
  }
  // Extract the MSP’s libp2p peer IDs from the multiaddresses
  // Each address should contain a `/p2p/<peerId>` segment
  const peerIds: string[] = extractPeerIDs(multiaddresses);
  // Validate that at least one valid peer ID was found
  if (peerIds.length === 0) {
    throw new Error('MSP multiaddresses had no /p2p/<peerId> segment');
  }

  // Extracts libp2p peer IDs from a list of multiaddresses.
  // A multiaddress commonly ends with `/p2p/<peerId>`, so this function
  // splits on that delimiter and returns the trailing segment when present.
  function extractPeerIDs(multiaddresses: string[]): string[] {
    return (multiaddresses ?? [])
      .map((addr) => addr.split('/p2p/').pop())
      .filter((id): id is string => !!id);
  }

  // Set the redundancy policy for this request.
  // Custom replication allows the client to specify an exact replica count.
  const replicationLevel = ReplicationLevel.Custom;
  const replicas = 1;

  // Step 3: Issue Storage Request
  const txHash: `0x${string}` | undefined =
    await storageHubClient.issueStorageRequest(
      bucketId as `0x${string}`,
      fileName,
      fingerprint.toHex() as `0x${string}`,
      fileSizeBigInt,
      mspId as `0x${string}`,
      peerIds,
      replicationLevel,
      replicas,
    );
  console.log('issueStorageRequest() txHash:', txHash);
  if (!txHash) {
    throw new Error('issueStorageRequest() did not return a transaction hash');
  }

  // Wait for storage request transaction
  // Don't proceed until receipt is confirmed on chain
  const receipt = await publicClient.waitForTransactionReceipt({
    hash: txHash,
  });
  if (receipt.status !== 'success') {
    throw new Error(`Storage request failed: ${txHash}`);
  }
  console.log('issueStorageRequest() txReceipt:', receipt);

  // Step 4: Compute the File Key
  const registry = new TypeRegistry();
  const owner = registry.createType(
    'AccountId20',
    account.address,
  ) as AccountId20;
  const bucketIdH256 = registry.createType('H256', bucketId) as H256;
  const fileKey = await fileManager.computeFileKey(
    owner,
    bucketIdH256,
    fileName,
  );

  // Step 5: Retrieve Storage Request Data
  const storageRequest =
    await polkadotApi.query.fileSystem.storageRequests(fileKey);
  if (!storageRequest.isSome) {
    throw new Error('Storage request not found on chain');
  }

  // Step 6: Read the storage request data
  const storageRequestData = storageRequest.unwrap().toHuman();
  console.log('Storage request data:', storageRequestData);

  // Disconnect the Polkadot API at the very end
  await polkadotApi.disconnect();
}

await run();

Now you've successfully learned how to manipulate the file you intend to upload to the DataHaven network by using the StorageHub SDK.

Next Steps

Last update: January 29, 2026
| Created: January 29, 2026