Skip to content

Revoke a Storage Request via Smart Contracts

After issuing a storage request, you may need to cancel it before the MSP fully confirms or replicates the file. This guide shows you how to revoke a pending storage request by calling the revokeStorageRequest function on the FileSystem Precompile directly via walletClient.writeContract.

This is useful if you submitted a request by mistake, need to change the file or bucket, or want to cancel before incurring storage fees.

SDK support not yet available

The revokeStorageRequest operation is not currently available through the StorageHub SDK. In this guide, to revoke a pending storage request, the FileSystem Precompile is called directly, instead of using the storageHubClient.

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,
              "resolveJsonModule": 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 use for issuing a storage request (any file type is accepted; the current testnet file size limit is 5 MB)
  • Familiarity with issuing storage requests
  • The FileSystem Precompile's ABI handy

Initialize the Script Entry Point

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 { initWasm, FileManager, ReplicationLevel } from '@storagehub-sdk/core';
import { createReadStream, statSync } from 'node:fs';
import { Readable } from 'node:stream';
import { TypeRegistry } from '@polkadot/types';
import { AccountId20, H256 } from '@polkadot/types/interfaces';
import {
  account,
  publicClient,
  walletClient,
  polkadotApi,
  chain,
} from './services/clientService.js';
import { getMspInfo } from './services/mspService.js';
import { toHex } from 'viem';
import fileSystemAbi from './abis/FileSystemABI.json' with { type: 'json' };
import { NETWORK } from './config/networks.js';
import { revokeStorageRequest } from './operations/fileOperations.js';

async function run() {
  await initWasm();

  const bucketId = 'INSERT_BUCKET_ID'; // `0x${string}`

  // **PLACEHOLDER FOR STEP 1: ISSUE A STORAGE REQUEST**
  // **PLACEHOLDER FOR STEP 2: COMPUTE THE FILE KEY**
  // **PLACEHOLDER FOR STEP 3: VERIFY STORAGE REQUEST EXISTS**
  // **PLACEHOLDER FOR STEP 4: REVOKE THE STORAGE REQUEST**
  // **PLACEHOLDER FOR STEP 5: VERIFY REVOCATION**

  await polkadotApi.disconnect();
}

await run();

Issue a Storage Request

Before you can revoke a storage request, one must exist on-chain. In this step, you'll issue a storage request without uploading the file to the MSP—just enough to register the intent on-chain so it can be revoked.

Replace the placeholder // **PLACEHOLDER FOR STEP 1: ISSUE A STORAGE REQUEST** with the following code:

index.ts // **PLACEHOLDER FOR STEP 1: ISSUE A STORAGE REQUEST**
// 1. Issue a storage request (without uploading the file to the MSP)
const fileName = 'helloworld.txt';
const filePath = new URL(`./files/${fileName}`, import.meta.url).pathname;

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

const fingerprint = await fileManager.getFingerprint();
const fileSizeBigInt = BigInt(fileManager.getFileSize());
const { mspId, multiaddresses } = await getMspInfo();

if (!multiaddresses?.length) {
  throw new Error('MSP multiaddresses are missing');
}
const peerIds: string[] = (multiaddresses ?? [])
  .map((addr: string) => addr.split('/p2p/').pop())
  .filter((id): id is string => !!id);
if (peerIds.length === 0) {
  throw new Error('MSP multiaddresses had no /p2p/<peerId> segment');
}

const replicationLevel = ReplicationLevel.Custom;
const replicas = 1;

// Issue storage request by calling the FileSystem precompile directly
const txHash = await walletClient.writeContract({
  account,
  address: NETWORK.filesystemContractAddress,
  abi: fileSystemAbi,
  functionName: 'issueStorageRequest',
  args: [
    bucketId as `0x${string}`,
    toHex(fileName),
    fingerprint.toHex() as `0x${string}`,
    fileSizeBigInt,
    mspId as `0x${string}`,
    peerIds.map((id) => toHex(id)),
    replicationLevel,
    replicas,
  ],
  chain: chain,
});
console.log('issueStorageRequest() txHash:', txHash);

const receipt = await publicClient.waitForTransactionReceipt({
  hash: txHash,
});
if (receipt.status !== 'success') {
  throw new Error(`Storage request failed: ${txHash}`);
}

Note

This step issues a storage request by calling issueStorageRequest on the FileSystem Precompile directly. The file is not uploaded to the MSP—only the on-chain request is created. This is intentional, since the goal of this guide is to demonstrate revocation.

Compute the File Key

The file key is a deterministic identifier derived from the owner address, bucket ID, and file name. You'll need it to both verify the request on-chain and to revoke it.

Replace the placeholder // **PLACEHOLDER FOR STEP 2: COMPUTE THE FILE KEY** with the following code:

index.ts // **PLACEHOLDER FOR STEP 2: COMPUTE THE FILE KEY**
// 2. Compute the file key so we can revoke
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,
);
console.log(`Computed file key: ${fileKey.toHex()}`);

Verify the Storage Request Exists

Before revoking, confirm that the storage request was successfully recorded on-chain by querying the runtime state:

Replace the placeholder // **PLACEHOLDER FOR STEP 3: VERIFY STORAGE REQUEST EXISTS** with the following code:

index.ts // **PLACEHOLDER FOR STEP 3: VERIFY STORAGE REQUEST EXISTS**
// 3. Verify the storage request exists on chain before revoking
const storageRequest =
  await polkadotApi.query.fileSystem.storageRequests(fileKey);
if (!storageRequest.isSome) {
  throw new Error('Storage request not found on chain — nothing to revoke');
}
console.log('Storage request confirmed on chain — proceeding to revoke\n');

At this point, the output should confirm the storage request exists on-chain:

ts-node index.ts
issueStorageRequest() txHash: 0x4a2b3c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b
Computed file key: 0x7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e
Storage request confirmed on chain — proceeding to revoke

Add the Revoke Storage Request Helper

Create a fileOperations.ts file within your operations folder (or add to an existing one). This helper calls walletClient.writeContract with functionName: 'revokeStorageRequest', passing the file key as the only argument:

operations/fileOperations.ts
import {
  walletClient,
  publicClient,
  account,
  chain,
} from '../services/clientService.js';
import fileSystemAbi from '../abis/FileSystemABI.json' with { type: 'json' };
import { NETWORK } from '../config/networks.js';

export async function revokeStorageRequest(fileKey: string): Promise<boolean> {
  // Revoke a pending storage request by calling the FileSystem precompile directly
  const txHash = await walletClient.writeContract({
    account,
    address: NETWORK.filesystemContractAddress,
    abi: fileSystemAbi,
    functionName: 'revokeStorageRequest',
    args: [fileKey as `0x${string}`],
    chain: chain,
  });
  console.log('revokeStorageRequest() txHash:', txHash);
  if (!txHash) {
    throw new Error('revokeStorageRequest() did not return a transaction hash');
  }

  // Wait for transaction receipt
  const receipt = await publicClient.waitForTransactionReceipt({
    hash: txHash,
  });
  console.log('revokeStorageRequest() txReceipt:', receipt);
  if (receipt.status !== 'success') {
    throw new Error(`Storage request revocation failed: ${txHash}`);
  }

  console.log(`Storage request for file key ${fileKey} revoked successfully`);
  return true;
}

Revoke the Storage Request

Replace the placeholder // **PLACEHOLDER FOR STEP 4: REVOKE THE STORAGE REQUEST** with the following code:

index.ts // **PLACEHOLDER FOR STEP 4: REVOKE THE STORAGE REQUEST**
// 4. Revoke the storage request
await revokeStorageRequest(fileKey.toHex());

If the revocation succeeds, you'll see the transaction hash and receipt:

ts-node index.ts
revokeStorageRequest() txHash: 0x1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a
revokeStorageRequest() txReceipt: {
  transactionHash: '0x1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a',
  transactionIndex: 0,
  blockHash: '0xb2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3',
  from: '0x00fa35d84a43db75467d2b2c1ed8974aca57223e',
  to: '0x0000000000000000000000000000000000000404',
  blockNumber: 175089n,
  cumulativeGasUsed: 24800n,
  gasUsed: 24800n,
  contractAddress: null,
  logs: [],
  status: 'success',
  effectiveGasPrice: 1000000000n,
  type: 'legacy'
}
Storage request for file key 0x7d8e...7d8e revoked successfully

Verify the Revocation

Finally, confirm that the storage request no longer exists on-chain by querying the runtime state again:

Replace the placeholder // **PLACEHOLDER FOR STEP 5: VERIFY REVOCATION** with the following code:

index.ts // **PLACEHOLDER FOR STEP 5: VERIFY REVOCATION**
// 5. Verify the storage request no longer exists on chain
const storageRequestAfter =
  await polkadotApi.query.fileSystem.storageRequests(fileKey);
if (storageRequestAfter.isNone) {
  console.log(
    'Storage request successfully removed from chain after revocation',
  );
} else {
  throw new Error(
    'Storage request revocation failed — request still exists on chain',
  );
}

If the revocation was successful, the output should confirm the request was removed:

ts-node index.ts Storage request successfully removed from chain after revocation
View complete fileOperations.ts
operations/fileOperations.ts
import {
  walletClient,
  publicClient,
  account,
  chain,
} from '../services/clientService.js';
import fileSystemAbi from '../abis/FileSystemABI.json' with { type: 'json' };
import { NETWORK } from '../config/networks.js';

export async function revokeStorageRequest(fileKey: string): Promise<boolean> {
  // Revoke a pending storage request by calling the FileSystem precompile directly
  const txHash = await walletClient.writeContract({
    account,
    address: NETWORK.filesystemContractAddress,
    abi: fileSystemAbi,
    functionName: 'revokeStorageRequest',
    args: [fileKey as `0x${string}`],
    chain: chain,
  });
  console.log('revokeStorageRequest() txHash:', txHash);
  if (!txHash) {
    throw new Error('revokeStorageRequest() did not return a transaction hash');
  }

  // Wait for transaction receipt
  const receipt = await publicClient.waitForTransactionReceipt({
    hash: txHash,
  });
  console.log('revokeStorageRequest() txReceipt:', receipt);
  if (receipt.status !== 'success') {
    throw new Error(`Storage request revocation failed: ${txHash}`);
  }

  console.log(`Storage request for file key ${fileKey} revoked successfully`);
  return true;
}
View complete index.ts
index.ts
import '@storagehub/api-augment';
import { initWasm, FileManager, ReplicationLevel } from '@storagehub-sdk/core';
import { createReadStream, statSync } from 'node:fs';
import { Readable } from 'node:stream';
import { TypeRegistry } from '@polkadot/types';
import { AccountId20, H256 } from '@polkadot/types/interfaces';
import {
  account,
  publicClient,
  walletClient,
  polkadotApi,
  chain,
} from './services/clientService.js';
import { getMspInfo } from './services/mspService.js';
import { toHex } from 'viem';
import fileSystemAbi from './abis/FileSystemABI.json' with { type: 'json' };
import { NETWORK } from './config/networks.js';
import { revokeStorageRequest } from './operations/fileOperations.js';

async function run() {
  await initWasm();

  const bucketId = 'INSERT_BUCKET_ID'; // `0x${string}`

  // 1. Issue a storage request (without uploading the file to the MSP)
  const fileName = 'helloworld.txt';
  const filePath = new URL(`./files/${fileName}`, import.meta.url).pathname;

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

  const fingerprint = await fileManager.getFingerprint();
  const fileSizeBigInt = BigInt(fileManager.getFileSize());
  const { mspId, multiaddresses } = await getMspInfo();

  if (!multiaddresses?.length) {
    throw new Error('MSP multiaddresses are missing');
  }
  const peerIds: string[] = (multiaddresses ?? [])
    .map((addr: string) => addr.split('/p2p/').pop())
    .filter((id): id is string => !!id);
  if (peerIds.length === 0) {
    throw new Error('MSP multiaddresses had no /p2p/<peerId> segment');
  }

  const replicationLevel = ReplicationLevel.Custom;
  const replicas = 1;

  // Issue storage request by calling the FileSystem precompile directly
  const txHash = await walletClient.writeContract({
    account,
    address: NETWORK.filesystemContractAddress,
    abi: fileSystemAbi,
    functionName: 'issueStorageRequest',
    args: [
      bucketId as `0x${string}`,
      toHex(fileName),
      fingerprint.toHex() as `0x${string}`,
      fileSizeBigInt,
      mspId as `0x${string}`,
      peerIds.map((id) => toHex(id)),
      replicationLevel,
      replicas,
    ],
    chain: chain,
  });
  console.log('issueStorageRequest() txHash:', txHash);

  const receipt = await publicClient.waitForTransactionReceipt({
    hash: txHash,
  });
  if (receipt.status !== 'success') {
    throw new Error(`Storage request failed: ${txHash}`);
  }

  // 2. Compute the file key so we can revoke
  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,
  );
  console.log(`Computed file key: ${fileKey.toHex()}`);

  // 3. Verify the storage request exists on chain before revoking
  const storageRequest =
    await polkadotApi.query.fileSystem.storageRequests(fileKey);
  if (!storageRequest.isSome) {
    throw new Error('Storage request not found on chain — nothing to revoke');
  }
  console.log('Storage request confirmed on chain — proceeding to revoke\n');

  // 4. Revoke the storage request
  await revokeStorageRequest(fileKey.toHex());

  // 5. Verify the storage request no longer exists on chain
  const storageRequestAfter =
    await polkadotApi.query.fileSystem.storageRequests(fileKey);
  if (storageRequestAfter.isNone) {
    console.log(
      'Storage request successfully removed from chain after revocation',
    );
  } else {
    throw new Error(
      'Storage request revocation failed — request still exists on chain',
    );
  }

  await polkadotApi.disconnect();
}

await run();

Next Steps

Last update: February 24, 2026
| Created: February 24, 2026