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:
-
Create a new project folder by executing the following command in the terminal:
-
Initialize a
package.jsonfile using the correct command for your package manager: -
Add the TypeScript and Node type definitions to your projects using the correct command for your package manager:
-
Create a
tsconfig.jsonfile in the root of your project and paste the following configuration: -
Initialize the
srcdirectory:
-
-
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:
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:
// 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:
// 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:
// 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:
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:
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:
// 4. Revoke the storage request
await revokeStorageRequest(fileKey.toHex());
If the revocation succeeds, you'll see the transaction hash and receipt:
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:
// 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:
View complete 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
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¶
-
Manage Files and Buckets via Smart Contracts
Learn how to get file info, request file removal from the network, and how to delete buckets via smart contracts.
-
Build a Data Workflow End-to-End via Smart Contracts
Learn step-by-step how to store a file on DataHaven via smart contracts and retrieve it from the network.
| Created: February 24, 2026