Skip to content

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:

    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 ts-node @types/node
      
      yarn add -D typescript ts-node @types/node
      
      npm install -D typescript 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
      

Install Dependencies

pnpm add @storagehub-sdk/core @storagehub-sdk/msp-client @storagehub/types-bundle @polkadot/api @storagehub/api-augment viem
yarn add @storagehub-sdk/core @storagehub-sdk/msp-client @storagehub/types-bundle @polkadot/api @storagehub/api-augment viem
npm install @storagehub-sdk/core @storagehub-sdk/msp-client @storagehub/types-bundle @polkadot/api @storagehub/api-augment viem
Why do I need these dependencies?
  • @storagehub/types-bundle: Describes DataHaven's custom on-chain types.

  • @polkadot/api: The core JavaScript library used to talk to any Substrate-based blockchain, which in our case is DataHaven.

  • @storagehub/api-augment: Extends @polkadot/api with DataHaven's custom pallets and RPC methods.

  • viem: Lightweight library for building Ethereum-compatible applications.

Initialize Clients

First, you'll need to set up the necessary clients to connect to the DataHaven network, which runs on a dual-protocol architecture (Substrate for core logic and EVM for compatibility).

Create an index.ts file and add the following code:

Note

The code below uses DataHaven Testnet configuration values, which include the Chain ID, RPC URL, WSS URL, MSP URL, and token metadata. If you’re running a local devnet, make sure to replace these with your local configuration parameters. You can find all the relevant local devnet values in the Starter Kit.

index.ts
import '@storagehub/api-augment';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { types } from '@storagehub/types-bundle';
import {
  HttpClientConfig,
  StorageHubClient,
  initWasm,
} from '@storagehub-sdk/core';
import {
  HealthStatus,
  InfoResponse,
  MspClient,
  ValueProp,
} from '@storagehub-sdk/msp-client';
import {
  Chain,
  PublicClient,
  WalletClient,
  createPublicClient,
  createWalletClient,
  defineChain,
  http,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

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

  // --- viem setup ---
  // Define DataHaven chain, as expected by viem
  const chain: Chain = defineChain({
    id: 55931,
    name: 'DataHaven Testnet',
    nativeCurrency: { name: 'Mock', symbol: 'MOCK', decimals: 18 },
    rpcUrls: {
      default: { http: ['https://services.datahaven-testnet.network/testnet'] },
    },
  });

  // Define account from a private key
  const account = privateKeyToAccount('INSERT_PRIVATE_KEY' as `0x${string}`);
  const address = account.address;

  // Create a wallet client using the defined chain, account, and RPC URL
  const walletClient: WalletClient = createWalletClient({
    chain,
    account,
    transport: http('https://services.datahaven-testnet.network/testnet'),
  });

  // Create a public client using the defined chain and RPC URL
  const publicClient: PublicClient = createPublicClient({
    chain,
    transport: http('https://services.datahaven-testnet.network/testnet'),
  });

  // --- Polkadot.js API setup ---
  const provider = new WsProvider(
    'wss://services.datahaven-testnet.network/testnet'
  );
  const polkadotApi: ApiPromise = await ApiPromise.create({
    provider,
    typesBundle: types,
    noInitWarn: true,
  });

  // --- Bucket creating logic ---
  // **PLACEHOLDER FOR STEP 1: CONNECT TO MSP CLIENT & CHECK HEALTH**
  // **PLACEHOLDER FOR STEP 2: CREATE STORAGEHUB CLIENT**
  // **PLACEHOLDER FOR STEP 3: DERIVE BUCKET ID**
  // **PLACEHOLDER FOR STEP 4: CHECK IF BUCKET EXISTS**
  // **PLACEHOLDER FOR STEP 5: GET MSP PARAMS**
  // **PLACEHOLDER FOR STEP 6: CREATE BUCKET**
  // **PLACEHOLDER FOR STEP 7: VERIFY BUCKET**

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

await run();

Warning

It is assumed that private keys are securely stored and managed in accordance with standard security practices.

With the above code in place, you now have the following:

  • walletClient: Used for signing and broadcasting transactions using the derived private key.
  • publicClient: Used for reading general public data from the chain, such as checking transaction receipts or block status.
  • polkadotApi: Used for reading code chain logic and state data from the underlying DataHaven Substrate node.

Connect to the MSP Client

Next, you'll need to connect to the MSP client and check its health status before creating a bucket.

Replace the placeholder // **PLACEHOLDER FOR STEP 1: CONNECT TO MSP CLIENT & CHECK HEALTH** with the following code:

// **PLACEHOLDER FOR STEP 1: CONNECT TO MSP CLIENT & CHECK HEALTH**
const baseUrl = 'https://deo-dh-backend.testnet.datahaven-infra.network/';
const httpConfig: HttpClientConfig = { baseUrl: baseUrl };
// A temporary authentication token obtained after Sign-In with Ethereum (SIWE).
// If not yet authenticated, this will remain undefined and the client will operate in read-only mode.
// Authentication is not required for creating a bucket, but is required for file uploads and bucket management.
let sessionToken: string | undefined = undefined;
const sessionProvider = async () =>
  sessionToken
    ? ({ token: sessionToken, user: { address: address } } as const)
    : undefined;
const mspClient = await MspClient.connect(httpConfig, sessionProvider);

// Check MSP Health Status
const mspHealth: HealthStatus = await mspClient.info.getHealth();
console.log('MSP service health:', mspHealth);

// Trigger the SIWE (Sign-In with Ethereum) flow.
// This prompts the connected wallet to sign an EIP-4361 message,
// which the MSP backend verifies to issue a JWT session token
const siweSession = await mspClient.auth.SIWE(walletClient);
console.log('SIWE Session:', siweSession);
// Store the obtained session token for future authenticated requests
sessionToken = (siweSession as { token: string }).token;

Then, check the health status by running the script:

ts-node index.ts

The response should return a healthy status, like this:

ts-node index.ts
MSP Health Status: {
    status: 'healthy',
    version: '0.1.0',
    service: 'backend-title',
    components: {
        storage: { status: 'healthy' },
        postgres: { status: 'healthy' },
        rpc: { status: 'healthy' }
    }
}

Initialize the StorageHub Client

Add the following code to initialize the StorageHub Client:

// **PLACEHOLDER FOR STEP 2: CREATE STORAGEHUB CLIENT**
// Initialize StorageHub Client
const storageHubClient = new StorageHubClient({
  rpcUrl: 'https://services.datahaven-testnet.network/testnet',
  chain: chain,
  walletClient: walletClient,
  filesystemContractAddress:
    '0x0000000000000000000000000000000000000404' as `0x${string}`,
});

Now that you have the StorageHubClient initialized, you'll use it to derive the bucket ID and create the bucket.

Derive Bucket ID

Before creating a new bucket, you'll need to derive the bucket ID by passing the bucket's name and the address you intend to use to create it. Two buckets with the same name and address cannot co-exist within the same MSP.

// **PLACEHOLDER FOR STEP 3: DERIVE BUCKET ID**
// Derive bucket ID
const bucketName = 'init-bucket';
const bucketId = (await storageHubClient.deriveBucketId(
  address,
  bucketName
)) as string;
console.log('Derived bucket ID: ', bucketId);

Run the script:

ts-node index.ts

The response should include something like this:

Derived bucket ID: 0x5536b20fca3333b6c9ac23579b2757b774512623f926426e3b37150191140392

Check If Derived Bucket ID Isn’t Already On-Chain

Now that you have the bucket ID, you can ensure the bucket doesn't exist on-chain yet.

// **PLACEHOLDER FOR STEP 4: CHECK IF BUCKET EXISTS**
// 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
);

Note

The mspClient can also be used to check if the derived bucket ID already exists using the mspClient.buckets.getBucket() function; however, this only checks if that specific MSP contains a bucket with that ID.

If you rerun the script, the response should include:

Bucket before creation is empty: true

Get Parameters from the MSP

To prepare all the parameters needed for the createBucket function, additional data from the MSP is required, such as mspId and valuePropId.

// **PLACEHOLDER FOR STEP 5: GET MSP PARAMS**
// Get basic MSP information from the MSP including its ID
const mspInfo: InfoResponse = await mspClient.info.getInfo();
const mspId = mspInfo.mspId as `0x${string}`;
console.log('MSP ID:', mspId);

// Choose one of the value props retrieved from the MSP
const valueProps: ValueProp[] = await mspClient.info.getValuePropositions();
if (!Array.isArray(valueProps) || valueProps.length === 0) {
  throw new Error('No value props available from this MSP.');
}

// For simplicity, this selects the first valueProp in the list
const valueProp = valueProps[0];
console.log('Chosen value prop: ', valueProp);

// Get the ID of the chosen value prop
const valuePropId = valueProp.id as `0x${string}`;
console.log('Chosen value prop id: ', valuePropId);

Create a Bucket

Finally, you can call the createBucket() function using the storageHubClient, including the previously gathered parameters and the isPrivate flag that determines the bucket’s privacy.

// **PLACEHOLDER FOR STEP 6: CREATE BUCKET**
// Define if bucket should be private or public
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 receipt = await publicClient.waitForTransactionReceipt({
  hash: txHash,
});
console.log('Bucket creation receipt:', receipt);
if (receipt.status !== 'success') {
  throw new Error(`Bucket creation failed: ${txHash}`);
}

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.

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.

// **PLACEHOLDER FOR STEP 7: VERIFY BUCKET**
const bucketAfterCreation = await polkadotApi.query.providers.buckets(
  bucketId
);
console.log('Bucket after creation exists', !bucketAfterCreation.isEmpty);

// Unwrap bucket in order to read its data
const bucketData = bucketAfterCreation.unwrap();
console.log('Bucket data:', bucketData);

// Check if the retrieved bucket's MSP ID matches the initial MSP ID you retrieved
console.log(
  'Bucket mspId matches initial mspId:',
  bucketData.mspId.toString() === mspId
);
// Check if the retrieved bucket's userId (owner address) matches the initial address you used to create the bucket
console.log(
  'Bucket userId matches initial bucket owner address:',
  bucketData.userId.toString() === address
);

The response should look something like this:

Bucket data: Type(7) [Map] {
  'root' => Type(32) [Uint8Array] [
    3,
    23,
    ...
    19,
    20,
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: 32,
    isStorageFallback: undefined
  ],
  'userId' => Type(20) [Uint8Array] [
    0,
    250,
    ...
    34,
    62,
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: 20,
    isStorageFallback: undefined
  ],
  'mspId' => Type {
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: 33,
    isStorageFallback: undefined
  },
  'private' => [Boolean (bool): false] {
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: 1,
    isStorageFallback: undefined
  },
  'readAccessGroupId' => Type {
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: undefined,
    isStorageFallback: undefined
  },
  'size_' => ,
  'valuePropId' => Type(32) [Uint8Array] [
    98,
    138,
    ...
    218,
    30,
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: 32,
    isStorageFallback: undefined
  ],
  registry: TypeRegistry { createdAtHash: undefined },
  createdAtHash: Type(32) [Uint8Array] [
    231,
    163,
    ...
    40,
    243,
    registry: TypeRegistry { createdAtHash: undefined },
    createdAtHash: undefined,
    initialU8aLength: 32,
    isStorageFallback: undefined
  ],
  initialU8aLength: 127,
  isStorageFallback: undefined
}
Bucket userId matches initial bucket owner address: true Bucket mspId matches initial mspId: true
View complete script
index.ts
import '@storagehub/api-augment';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { types } from '@storagehub/types-bundle';
import {
  HttpClientConfig,
  StorageHubClient,
  initWasm,
} from '@storagehub-sdk/core';
import {
  HealthStatus,
  InfoResponse,
  MspClient,
  ValueProp,
} from '@storagehub-sdk/msp-client';
import {
  Chain,
  PublicClient,
  WalletClient,
  createPublicClient,
  createWalletClient,
  defineChain,
  http,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

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

  // --- viem setup ---
  // Define DataHaven chain, as expected by viem
  const chain: Chain = defineChain({
    id: 55931,
    name: 'DataHaven Testnet',
    nativeCurrency: { name: 'Mock', symbol: 'MOCK', decimals: 18 },
    rpcUrls: {
      default: { http: ['https://services.datahaven-testnet.network/testnet'] },
    },
  });

  // Define account from a private key
  const account = privateKeyToAccount('INSERT_PRIVATE_KEY' as `0x${string}`);
  const address = account.address;

  // Create a wallet client using the defined chain, account, and RPC URL
  const walletClient: WalletClient = createWalletClient({
    chain,
    account,
    transport: http('https://services.datahaven-testnet.network/testnet'),
  });

  // Create a public client using the defined chain and RPC URL
  const publicClient: PublicClient = createPublicClient({
    chain,
    transport: http('https://services.datahaven-testnet.network/testnet'),
  });

  // --- Polkadot.js API setup ---
  const provider = new WsProvider(
    'wss://services.datahaven-testnet.network/testnet'
  );
  const polkadotApi: ApiPromise = await ApiPromise.create({
    provider,
    typesBundle: types,
    noInitWarn: true,
  });

  // --- Bucket creating logic ---
  const baseUrl = 'https://deo-dh-backend.testnet.datahaven-infra.network/';
  const httpConfig: HttpClientConfig = { baseUrl: baseUrl };
  // A temporary authentication token obtained after Sign-In with Ethereum (SIWE).
  // If not yet authenticated, this will remain undefined and the client will operate in read-only mode.
  // Authentication is not required for creating a bucket, but is required for file uploads and bucket management.
  let sessionToken: string | undefined = undefined;
  const sessionProvider = async () =>
    sessionToken
      ? ({ token: sessionToken, user: { address: address } } as const)
      : undefined;
  const mspClient = await MspClient.connect(httpConfig, sessionProvider);

  // Check MSP Health Status
  const mspHealth: HealthStatus = await mspClient.info.getHealth();
  console.log('MSP service health:', mspHealth);

  // Trigger the SIWE (Sign-In with Ethereum) flow.
  // This prompts the connected wallet to sign an EIP-4361 message,
  // which the MSP backend verifies to issue a JWT session token
  const siweSession = await mspClient.auth.SIWE(walletClient);
  console.log('SIWE Session:', siweSession);
  // Store the obtained session token for future authenticated requests
  sessionToken = (siweSession as { token: string }).token;


  // Initialize StorageHub Client
  const storageHubClient = new StorageHubClient({
    rpcUrl: 'https://services.datahaven-testnet.network/testnet',
    chain: chain,
    walletClient: walletClient,
    filesystemContractAddress:
      '0x0000000000000000000000000000000000000404' as `0x${string}`,
  });

  // Derive bucket ID
  const bucketName = 'init-bucket';
  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
  );

  // Get basic MSP information from the MSP including its ID
  const mspInfo: InfoResponse = await mspClient.info.getInfo();
  const mspId = mspInfo.mspId as `0x${string}`;
  console.log('MSP ID:', mspId);

  // Choose one of the value props retrieved from the MSP
  const valueProps: ValueProp[] = await mspClient.info.getValuePropositions();
  if (!Array.isArray(valueProps) || valueProps.length === 0) {
    throw new Error('No value props available from this MSP.');
  }

  // For simplicity, this selects the first valueProp in the list
  const valueProp = valueProps[0];
  console.log('Chosen value prop: ', valueProp);

  // Get the ID of the chosen value prop
  const valuePropId = valueProp.id as `0x${string}`;
  console.log('Chosen value prop id: ', valuePropId);

  // Define if bucket should be private or public
  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 receipt = await publicClient.waitForTransactionReceipt({
    hash: txHash,
  });
  console.log('Bucket creation receipt:', receipt);
  if (receipt.status !== 'success') {
    throw new Error(`Bucket creation failed: ${txHash}`);
  }

  const bucketAfterCreation = await polkadotApi.query.providers.buckets(
    bucketId
  );
  console.log('Bucket after creation exists', !bucketAfterCreation.isEmpty);

  // Unwrap bucket in order to read its data
  const bucketData = bucketAfterCreation.unwrap();
  console.log('Bucket data:', bucketData);

  // Check if the retrieved bucket's MSP ID matches the initial MSP ID you retrieved
  console.log(
    'Bucket mspId matches initial mspId:',
    bucketData.mspId.toString() === mspId
  );
  // Check if the retrieved bucket's userId (owner address) matches the initial address you used to create the bucket
  console.log(
    'Bucket userId matches initial bucket owner address:',
    bucketData.userId.toString() === address
  );

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

await run();

And that’s it. You’ve successfully created a bucket on-chain and verified its data.

Next Steps

Last update: November 11, 2025
| Created: October 17, 2025