Create a Bucket¶
Buckets are logical containers (folders) that group your files under a Main Storage Provider (MSP). Each bucket is tied to a specific MSP and value proposition, which together define where your data will be stored and at what price. Before you can issue storage requests or upload files to DataHaven, you must first create a bucket.
This guide walks you through creating your first bucket programmatically using the StorageHub SDK — from connecting to an MSP and initializing the SDK to deriving a bucket ID, creating the bucket on-chain, and verifying its data.
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:
-
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.
The index.ts snippet below also imports bucketOperations.ts, which is not in your project yet—that's expected, as you'll create it later in this guide.
Add the following code to your index.ts file:
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { polkadotApi } from './services/clientService.js';
import {
createBucket,
verifyBucketCreation,
} from './operations/bucketOperations.js';
import { HealthStatus } from '@storagehub-sdk/msp-client';
import { mspClient } from './services/mspService.js';
async function run() {
// For anything from @storagehub-sdk/core to work, initWasm() is required
// on top of the file
await initWasm();
// --- Bucket creating logic ---
// **PLACEHOLDER FOR STEP 1: CHECK MSP HEALTH**
// **PLACEHOLDER FOR STEP 2: CREATE BUCKET**
// **PLACEHOLDER FOR STEP 3: VERIFY BUCKET**
// Disconnect the Polkadot API at the very end
await polkadotApi.disconnect();
}
await run();
Check MSP Health¶
Next, since you are already connected to the MSP client, check its health status before creating a bucket.
-
Replace the placeholder
// **PLACEHOLDER FOR STEP 1: CHECK MSP HEALTH**with the following code: -
Check the health status by running the script:
The response should return a
healthystatus, like this:ts-node index.tsMSP Health Status: { status: 'healthy', version: '0.1.0', service: 'backend-title', components: { storage: { status: 'healthy' }, postgres: { status: 'healthy' }, rpc: { status: 'healthy' } } }
Create a Bucket¶
Buckets must be created under a specific MSP, and each MSP exposes one or more value propositions that describe what the storage fees under that MSP are going to look like.
Before creating a bucket you need to fetch those value props, choose the one you want to use, and then submit the bucket-creation transaction. To do all this, you are going to:
- Create a
getValuePropshelper method withinmspService.ts. - Create a
createBuckethelper method withinbucketOperations.ts. - Update the
index.tsfile to trigger the logic you've implemented.
Add Method to Get Value Props¶
Fetch the valueProps from the MSP you are connected to. An MSP's value prop is its storage fee. The files you will store within a certain bucket will cost you based on the value prop you choose.
To fetch valueProps from the MSP Client, take the following steps:
-
Add the following helper method to your
mspService.tsfile:mspService.ts// Retrieve MSP value propositions and select one for bucket creation const getValueProps = async (): Promise<`0x${string}`> => { const valueProps: ValueProp[] = await mspClient.info.getValuePropositions(); if (!Array.isArray(valueProps) || valueProps.length === 0) { throw new Error('No value propositions available from MSP'); } // For simplicity, select the first value proposition and return its ID const valuePropId = valueProps[0].id as `0x${string}`; console.log(`Chose Value Prop ID: ${valuePropId}`); return valuePropId; }; -
Add the
getValuePropsmethod to the export statement at the bottom of themspService.tsfile.
View complete mspService.ts file
import {
HealthStatus,
InfoResponse,
MspClient,
UserInfo,
ValueProp,
} from '@storagehub-sdk/msp-client';
import { HttpClientConfig } from '@storagehub-sdk/core';
import { address, walletClient } from './clientService.js';
const NETWORKS = {
devnet: {
id: 181222,
name: 'DataHaven Local Devnet',
rpcUrl: 'http://127.0.0.1:9666',
wsUrl: 'wss://127.0.0.1:9666',
mspUrl: 'http://127.0.0.1:8080/',
nativeCurrency: { name: 'StorageHub', symbol: 'SH', decimals: 18 },
},
testnet: {
id: 55931,
name: 'DataHaven Testnet',
rpcUrl: 'https://services.datahaven-testnet.network/testnet',
wsUrl: 'wss://services.datahaven-testnet.network/testnet',
mspUrl: 'https://deo-dh-backend.testnet.datahaven-infra.network/',
nativeCurrency: { name: 'Mock', symbol: 'MOCK', decimals: 18 },
},
};
// Configure the HTTP client to point to the MSP backend
const httpCfg: HttpClientConfig = { baseUrl: NETWORKS.testnet.mspUrl };
// Initialize a session token for authenticated requests (updated after authentication through SIWE)
let sessionToken: string | undefined = undefined;
// Provide session information to the MSP client whenever available
// Returns a token and user address if authenticated, otherwise undefined
const sessionProvider = async () =>
sessionToken
? ({ token: sessionToken, user: { address: address } } as const)
: undefined;
// Establish a connection to the Main Storage Provider (MSP) backend
const mspClient = await MspClient.connect(httpCfg, sessionProvider);
// Retrieve MSP metadata, including its unique ID and version, and log it to the console
const getMspInfo = async (): Promise<InfoResponse> => {
const mspInfo = await mspClient.info.getInfo();
console.log(`MSP ID: ${mspInfo.mspId}`);
return mspInfo;
};
// Retrieve and log the MSP’s current health status
const getMspHealth = async (): Promise<HealthStatus> => {
const mspHealth = await mspClient.info.getHealth();
console.log(`MSP Health: ${mspHealth}`);
return mspHealth;
};
// Authenticate the user via SIWE (Sign-In With Ethereum) using the connected wallet
// Once authenticated, store the returned session token and retrieve the user’s profile
const authenticateUser = async (): Promise<UserInfo> => {
console.log('Authenticating user with MSP via SIWE...');
// In development domain and uri can be arbitrary placeholders,
// but in production they must match your actual frontend origin.
const domain = 'localhost';
const uri = 'http://localhost';
const siweSession = await mspClient.auth.SIWE(walletClient, domain, uri);
console.log('SIWE Session:', siweSession);
sessionToken = (siweSession as { token: string }).token;
const profile: UserInfo = await mspClient.auth.getProfile();
return profile;
};
// Retrieve MSP value propositions and select one for bucket creation
const getValueProps = async (): Promise<`0x${string}`> => {
const valueProps: ValueProp[] = await mspClient.info.getValuePropositions();
if (!Array.isArray(valueProps) || valueProps.length === 0) {
throw new Error('No value propositions available from MSP');
}
// For simplicity, select the first value proposition and return its ID
const valuePropId = valueProps[0].id as `0x${string}`;
console.log(`Chose Value Prop ID: ${valuePropId}`);
return valuePropId;
};
// Export initialized client and helper functions for use in other modules
export { mspClient, getMspInfo, getMspHealth, authenticateUser, getValueProps };
Add Method to Create a Bucket¶
Bucket-related logic will live in a separate bucketOperations.ts file. To implement it, take the following steps:
-
Create a new folder called
operationswithin thesrcfolder (at the same level as theservicesfolder) like so: -
Create a new file within the
operationsfolder calledbucketOperations.ts -
Add the following code, which uses the
getValuePropshelper from the previous section increateBucket:bucketOperations.tsimport { storageHubClient, address, publicClient, polkadotApi, } from '../services/clientService.js'; import { getMspInfo, getValueProps } from '../services/mspService.js'; export async function createBucket(bucketName: string) { // Get basic MSP information from the MSP including its ID const { mspId } = await getMspInfo(); // Choose one of the value props retrieved from the MSP through the helper function const valuePropId = await getValueProps(); console.log(`Value Prop ID: ${valuePropId}`); // Derive bucket ID const bucketId = (await storageHubClient.deriveBucketId( address, bucketName )) as string; console.log(`Derived bucket ID: ${bucketId}`); // Check that the bucket doesn't exist yet const bucketBeforeCreation = await polkadotApi.query.providers.buckets( bucketId ); console.log('Bucket before creation is empty', bucketBeforeCreation.isEmpty); if (!bucketBeforeCreation.isEmpty) { throw new Error(`Bucket already exists: ${bucketId}`); } const isPrivate = false; // Create bucket on chain const txHash: `0x${string}` | undefined = await storageHubClient.createBucket( mspId as `0x${string}`, bucketName, isPrivate, valuePropId ); console.log('createBucket() txHash:', txHash); if (!txHash) { throw new Error('createBucket() did not return a transaction hash'); } // Wait for transaction receipt const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash, }); if (txReceipt.status !== 'success') { throw new Error(`Bucket creation failed: ${txHash}`); } return { bucketId, txReceipt }; }The
createBuckethelper handles the full lifecycle of a bucket-creation transaction:- It fetches the MSP ID and selects a value prop (required to create a bucket).
- It derives a deterministic bucket ID from your wallet address and chosen bucket name.
- Before sending any on-chain transaction, it checks whether the bucket already exists to prevent accidental overwrites.
Once the check passes, the
createBucketextrinsic is called via the StorageHub client, returning thebucketIdandtxReceipt.
Call the Create Bucket Helper Method¶
Now that you've extracted all the bucket creation logic into its own method, you'll update the index.ts file.
-
Replace the placeholder
// **PLACEHOLDER FOR STEP 2: CREATE BUCKET**with the following code:index.ts // **PLACEHOLDER FOR STEP 2: CREATE BUCKET**// Create a bucket const bucketName = 'init-bucket'; const { bucketId, txReceipt } = await createBucket(bucketName); console.log(`Created Bucket ID: ${bucketId}`); console.log(`createBucket() txReceipt: ${txReceipt}`);Note
You can also get a list of all your created buckets within a certain MSP using the
mspClient.buckets.listBuckets()function. Make sure you are authenticated before triggering this function. -
Execute the
createBucketmethod by running the script:The response should look something like this:
ts-node index.ts Derived bucket ID: 0x659ca967940ee656b10ea85813bb14f054137d330ec87f9914a2c46a981196f6 Bucket before creation is empty true createBucket() txHash: 0x2f370c2a7906b830d6351857449af201a5abe90f0ace8e8a6c972509ca579cc8 Created Bucket ID: 0x659ca967940ee656b10ea85813bb14f054137d330ec87f9914a2c46a981196f6 createBucket() txReceipt: { transactionHash: '0x2f370c2a7906b830d6351857449af201a5abe90f0ace8e8a6c972509ca579cc8', transactionIndex: 0, blockHash: '0x773b4139cd5b1dc4374e137dcf279b89869a3f86caec070ec3478ce898adfc33', from: '0x00fa35d84a43db75467d2b2c1ed8974aca57223e', to: '0x0000000000000000000000000000000000000404', blockNumber: 174629n, cumulativeGasUsed: 111152n, gasUsed: 111152n, contractAddress: null, logs: [ { address: '0x0000000000000000000000000000000000000404', topics: [Array], data: '0x', blockHash: '0x773b4139cd5b1dc4374e137dcf279b89869a3f86caec070ec3478ce898adfc33', blockNumber: 174629n, transactionHash: '0x2f370c2a7906b830d6351857449af201a5abe90f0ace8e8a6c972509ca579cc8', transactionIndex: 0, logIndex: 0, transactionLogIndex: '0x0', removed: false } ], logsBloom: '0x00000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000080040000000000000000000000000000000000040100000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000004000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000010000000800000000000000000000040000000000000000000000000000000010000000000000000000000000000080000', status: 'success', effectiveGasPrice: 1000000000n, type: 'legacy' }
Check if Bucket is On-Chain¶
The last step is to verify that the bucket was created successfully on-chain and to confirm its stored data. Just like with the createBucket method, you can extract all the bucket verification logic into its own verifyBucketCreation method.
-
Add the following code in your
bucketOperations.tsfile:bucketOperations.ts// Verify bucket creation on chain and return bucket data export async function verifyBucketCreation(bucketId: string) { const { mspId } = await getMspInfo(); const bucket = await polkadotApi.query.providers.buckets(bucketId); if (bucket.isEmpty) { throw new Error('Bucket not found on chain after creation'); } const bucketData = bucket.unwrap().toHuman(); console.log( 'Bucket userId matches initial bucket owner address', bucketData.userId === address ); console.log( `Bucket MSPId matches initial MSPId: ${bucketData.mspId === mspId}` ); return bucketData; } -
Update the
index.tsfile to trigger the helper method you just implemented:index.ts // **PLACEHOLDER FOR STEP 3: VERIFY BUCKET**// Verify bucket exists on chain const bucketData = await verifyBucketCreation(bucketId); console.log('Bucket data:', bucketData);The response should look something like this:
ts-node index.ts Bucket userId matches initial bucket owner address: true Bucket mspId matches initial mspId: trueBucket data: { root: '0x03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314', userId: '0x00FA35D84a43db75467D2B2c1ed8974aCA57223e', mspId: '0x0000000000000000000000000000000000000000000000000000000000000001', private: false, readAccessGroupId: null, size_: '0', valuePropId: '0x628a23c7aa64902e13f63ffdd0725e07723745f84cabda048d901020d200da1e' }
View complete bucketOperations.ts file
import {
storageHubClient,
address,
publicClient,
polkadotApi,
} from '../services/clientService.js';
import { getMspInfo, getValueProps } from '../services/mspService.js';
export async function createBucket(bucketName: string) {
// Get basic MSP information from the MSP including its ID
const { mspId } = await getMspInfo();
// Choose one of the value props retrieved from the MSP through the helper function
const valuePropId = await getValueProps();
console.log(`Value Prop ID: ${valuePropId}`);
// Derive bucket ID
const bucketId = (await storageHubClient.deriveBucketId(
address,
bucketName
)) as string;
console.log(`Derived bucket ID: ${bucketId}`);
// Check that the bucket doesn't exist yet
const bucketBeforeCreation = await polkadotApi.query.providers.buckets(
bucketId
);
console.log('Bucket before creation is empty', bucketBeforeCreation.isEmpty);
if (!bucketBeforeCreation.isEmpty) {
throw new Error(`Bucket already exists: ${bucketId}`);
}
const isPrivate = false;
// Create bucket on chain
const txHash: `0x${string}` | undefined = await storageHubClient.createBucket(
mspId as `0x${string}`,
bucketName,
isPrivate,
valuePropId
);
console.log('createBucket() txHash:', txHash);
if (!txHash) {
throw new Error('createBucket() did not return a transaction hash');
}
// Wait for transaction receipt
const txReceipt = await publicClient.waitForTransactionReceipt({
hash: txHash,
});
if (txReceipt.status !== 'success') {
throw new Error(`Bucket creation failed: ${txHash}`);
}
return { bucketId, txReceipt };
}
// Verify bucket creation on chain and return bucket data
export async function verifyBucketCreation(bucketId: string) {
const { mspId } = await getMspInfo();
const bucket = await polkadotApi.query.providers.buckets(bucketId);
if (bucket.isEmpty) {
throw new Error('Bucket not found on chain after creation');
}
const bucketData = bucket.unwrap().toHuman();
console.log(
'Bucket userId matches initial bucket owner address',
bucketData.userId === address
);
console.log(
`Bucket MSPId matches initial MSPId: ${bucketData.mspId === mspId}`
);
return bucketData;
}
View complete index.ts file
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { polkadotApi } from './services/clientService.js';
import {
createBucket,
verifyBucketCreation,
} from './operations/bucketOperations.js';
import { HealthStatus } from '@storagehub-sdk/msp-client';
import { mspClient } from './services/mspService.js';
async function run() {
// For anything from @storagehub-sdk/core to work, initWasm() is required
// on top of the file
await initWasm();
// --- Bucket creating logic ---
// Check MSP Health Status
const mspHealth: HealthStatus = await mspClient.info.getHealth();
console.log('MSP Health Status:', mspHealth);
// Create a bucket
const bucketName = 'init-bucket';
const { bucketId, txReceipt } = await createBucket(bucketName);
console.log(`Created Bucket ID: ${bucketId}`);
console.log(`createBucket() txReceipt: ${txReceipt}`);
// Verify bucket exists on chain
const bucketData = await verifyBucketCreation(bucketId);
console.log('Bucket data:', bucketData);
// Disconnect the Polkadot API at the very end
await polkadotApi.disconnect();
}
await run();
And that’s it. You’ve successfully created a bucket and verified it has successfully been created on-chain.
Next Steps¶
| Created: October 17, 2025