Skip to content

Resolve Insolvent Status via API

If your account balance runs out while you have active payment streams, the DataHaven network will flag your account as insolvent. This guide covers the process of checking your insolvent status, paying off any outstanding debt, and clearing the insolvent flag to restore your account's ability to store files in the network. This guide will cover the Polkadot API approach while the Resolve Insolvent Status via UI guide covers the approach through the Polkadot.js Apps UI.

Prerequisites

Before you begin, ensure you have the following:

  • 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 file uploaded to DataHaven

  • Your account has been flagged as insolvent (or you want to proactively check your status)
  • Sufficient funds in your account to pay any outstanding debt

Understanding Insolvency

Insolvency in DataHaven is a two-phase process. When your account balance can no longer cover a provider's charge, that provider marks you as "out of funds." However, you are not yet globally insolvent. You become officially insolvent only when a provider attempts to charge you a second time, and you still cannot pay. This means the effective grace period before insolvency depends on how frequently providers charge and on the minimum they will charge.

Once you are flagged as globally insolvent:

  • Providers that detect your insolvency will automatically stop storing your files and settle debts from your deposits.
  • You cannot create new buckets or upload new files.
  • You cannot create new storage requests until the flag is cleared and the cooldown period passes.

Provider Auto-Cleanup vs Manual Recovery

During normal operation, providers will automatically detect that you've become insolvent, stop storing your files, and charge their owed debt from your deposits. When this happens, all your payment streams are cleaned up automatically, with no action on your part.

However, if a provider is offline or otherwise unable to process your insolvency (for example, if one of your providers went down and never detected your status change), the automatic cleanup won't fully complete. In this scenario, the payOutstandingDebt extrinsic acts as a failsafe, allowing you to manually release the locked funds from those unresolved payment streams and settle the debt yourself, rather than waiting indefinitely for an unreliable provider.

Resolution Steps

To resolve insolvency, you must:

  1. Pay your outstanding debt to providers (only needed if providers haven't already cleaned up your payment streams automatically).
  2. Manually clear the insolvent flag.
  3. Wait for the cooldown period of 100 blocks (~10 minutes) to pass before resuming normal operations.

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 costOperations.ts, which you'll create later in this guide.

Add the following code to your index.ts file:

index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // **PLACEHOLDER FOR STEP 1: CHECK INSOLVENT STATUS**
  // **PLACEHOLDER FOR STEP 2: AUTHENTICATE**
  // **PLACEHOLDER FOR STEP 3: GET PAYMENT STREAMS**
  // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

Check Insolvent Status

Before attempting any recovery actions, first check whether your account is actually flagged as insolvent on the network.

Add Method to Check Insolvent Status

  1. Create a new folder called operations within the src folder (at the same level as the services folder) if you haven't already:

    mkdir operations
    
  2. Create a new file within the operations folder called costOperations.ts and add the following code:

    costOperations.ts
    import { polkadotApi, publicClient } from '../services/clientService.js';
    import { PaymentStreamsResponse } from '@storagehub-sdk/msp-client';
    import { signer } from '../services/clientService.js';
    
    const isInsolvent = async (address: `0x${string}`) => {
      // Query if user is labelled as insolvent by the network
      const userWithoutFundsResponse =
        await polkadotApi.query.paymentStreams.usersWithoutFunds(address);
      console.log(
        `User ${address} without funds response:`,
        userWithoutFundsResponse.toHuman(),
      );
      // If the userWithoutFundsResponse is null, it means the user is not insolvent
      const userIsInsolvent = userWithoutFundsResponse.isSome;
      return userIsInsolvent;
    };
    
    // Add other helper methods
    
    export { isInsolvent };
    

Call the Check Insolvent Status Helper Method

Replace the placeholder // **PLACEHOLDER FOR STEP 1: CHECK INSOLVENT STATUS** with the following code:

index.ts // **PLACEHOLDER FOR STEP 1: CHECK INSOLVENT STATUS**
// Check if insolvent
const insolventStatus = await isInsolvent(address);
console.log(`Insolvent status for ${address}:`, insolventStatus);
// If not insolvent, exit early
if (!insolventStatus) {
  console.log('Not insolvent, no action needed.');
  await polkadotApi.disconnect();
  return;
}
View complete index.ts up until this point
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }

  // **PLACEHOLDER FOR STEP 2: AUTHENTICATE**
  // **PLACEHOLDER FOR STEP 3: GET PAYMENT STREAMS**
  // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

If you run the script with the code above, the response should look something like this:

ts-node index.ts
User 0x90b5a...c7912 without funds response: { sinceBlock: '1,234,567' }
Insolvent status for 0x90b5a...c7912: true

Authenticate

Before accessing payment stream information, authenticate with the MSP. The authenticateUser helper signs a SIWE message and returns a session token that authorizes your requests. Add the following code to use the authenticateUser helper method you've already implemented in mspService.ts:

index.ts // **PLACEHOLDER FOR STEP 2: AUTHENTICATE**
// Authenticate
const authProfile = await authenticateUser();
console.log('Authenticated user profile:', authProfile);
View complete index.ts up until this point
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }
  // Authenticate
  const authProfile = await authenticateUser();
  console.log('Authenticated user profile:', authProfile);

  // **PLACEHOLDER FOR STEP 3: GET PAYMENT STREAMS**
  // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

Get Payment Streams

To pay off your outstanding debt, you need to know which providers you owe money to. This information comes from your payment streams.

  1. If you haven't already implemented the getPaymentStreams method from the Track Costs guide, add the following code to your mspService.ts file:

    mspService.ts
    const getPaymentStreams = async (): Promise<PaymentStreamsResponse> => {
      // Fetch payment streams associated with the authenticated user
      const paymentStreams = await mspClient.info.getPaymentStreams();
      return paymentStreams;
    };
    // Make sure to add this method to the export statement at the bottom of the file too
    
  2. Replace the placeholder // **PLACEHOLDER FOR STEP 3: GET PAYMENT STREAMS** with the following code:

    index.ts // **PLACEHOLDER FOR STEP 3: GET PAYMENT STREAMS**
    // Get payment streams from MSP
    const paymentStreams = await getPaymentStreams();
    console.log('Unique Payment Streams:', paymentStreams);
    
View complete index.ts up until this point
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }
  // Authenticate
  const authProfile = await authenticateUser();
  console.log('Authenticated user profile:', authProfile);
  // Get payment streams from MSP
  const paymentStreams = await getPaymentStreams();
  console.log('Unique Payment Streams:', paymentStreams);

  // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

If you run the script with the code above, the response should look something like this:

ts-node index.ts
Unique Payment Streams: {
  streams: [
    {
      provider: '0x0000000000000000000000000000000000000000000000000000000000000010',
      providerType: 'bsp',
      totalAmountPaid: '4043906586769',
      costPerTick: '10125385'
    },
    {
      provider: '0x0000000000000000000000000000000000000000000000000000000000000001',
      providerType: 'msp',
      totalAmountPaid: '2060982766347038397',
      costPerTick: '3450008424335'
    }
  ]
}

Calculate Outstanding Debt

Before paying your debt, it's important to calculate the total amount owed and verify you have sufficient funds. Technically, you don't have to calculate your debt, since the network will automatically withdraw the exact amount owed from your account when you call the payOutstandingDebt extrinsic. Still, then you wouldn't know how much you were about to pay. The calculateTotalOutstandingDebt helper method returns two types of debt:

  • Total raw debt: The total amount accumulated based on storage rates
  • Total effective debt: The actual amount you owe, capped by your deposit for each payment stream (essentially the sum of each payment stream's min(userDeposit, userDebt))

Add Methods to Get Balance and Calculate Debt

Add the following methods to your costOperations.ts file:

costOperations.ts
const getBalance = async (address: `0x${string}`): Promise<bigint> => {
  // Query balance
  const balanceWei = await publicClient.getBalance({ address });
  return balanceWei;
};

const calculateTotalOutstandingDebt = async (
  address: `0x${string}`,
  paymentStreams: PaymentStreamsResponse,
) => {
  const seen = new Set<string>();

  // Current tick from proofs dealer — used for MSPs (privileged providers)
  const currentTick = await polkadotApi.call.proofsDealerApi.getCurrentTick();

  let totalRawDebt = 0n;
  let totalEffectiveDebt = 0n;

  for (const { provider, providerType } of paymentStreams.streams) {
    if (seen.has(provider)) continue;
    seen.add(provider);

    if (providerType === 'bsp') {
      // BSPs use per-provider LastChargeableInfo (updated on proof submission)
      const lastChargeableInfo =
        await polkadotApi.query.paymentStreams.lastChargeableInfo(provider);

      if (!lastChargeableInfo) continue;

      const stream =
        await polkadotApi.query.paymentStreams.dynamicRatePaymentStreams(
          provider,
          address,
        );
      if (stream.isSome) {
        const dynamicRatePaymentStream = stream.unwrap();

        const rawDebt =
          ((lastChargeableInfo.priceIndex.toBigInt() -
            dynamicRatePaymentStream.priceIndexWhenLastCharged.toBigInt()) *
            dynamicRatePaymentStream.amountProvided.toBigInt()) /
          2n ** 30n;

        const effectiveDebt =
          rawDebt < dynamicRatePaymentStream.userDeposit.toBigInt()
            ? rawDebt
            : dynamicRatePaymentStream.userDeposit.toBigInt();
        totalRawDebt += rawDebt;
        totalEffectiveDebt += effectiveDebt;
      }
    } else if (providerType === 'msp') {
      // MSPs are privileged providers — they can charge up to the current tick
      const stream =
        await polkadotApi.query.paymentStreams.fixedRatePaymentStreams(
          provider,
          address,
        );
      if (stream.isSome) {
        const fixedRatePaymentStream = stream.unwrap();

        const rawDebt =
          (currentTick.toBigInt() -
            fixedRatePaymentStream.lastChargedTick.toBigInt()) *
          fixedRatePaymentStream.rate.toBigInt();

        const effectiveDebt =
          rawDebt < fixedRatePaymentStream.userDeposit.toBigInt()
            ? rawDebt
            : fixedRatePaymentStream.userDeposit.toBigInt();
        totalRawDebt += rawDebt;
        totalEffectiveDebt += effectiveDebt;
      }
    }
  }
  return { totalRawDebt, totalEffectiveDebt };
};

Make sure to update your exports:

costOperations.ts
export {
  getBalance,
  isInsolvent,
  calculateTotalOutstandingDebt,
};

Call the Calculate Outstanding Debt Helper Method

Replace the placeholder // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT** with the following code:

index.ts // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT**
// Log current balance
const balance = await getBalance(address); // MOCK token in wei
console.log(
  `Current balance: ${balance} (wei), ${Number(formatEther(balance))} (MOCK)`,
);
// Calculate total and effective outstanding debt
const { totalRawDebt, totalEffectiveDebt } =
  await calculateTotalOutstandingDebt(address, paymentStreams);
// Log debts
console.log(
  `Total Raw Debt: ${totalRawDebt} (wei), ${Number(formatEther(totalRawDebt))} (MOCK)`,
);
console.log(
  `Total Effective Debt: ${totalEffectiveDebt} (wei), ${Number(formatEther(totalEffectiveDebt))} (MOCK)`,
);
// Check if balance is sufficient to cover effective debt
if (balance < totalEffectiveDebt) {
  console.log('Insufficient balance to pay outstanding debt.');
  await polkadotApi.disconnect();
  return;
}
View complete index.ts up until this point
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }
  // Authenticate
  const authProfile = await authenticateUser();
  console.log('Authenticated user profile:', authProfile);
  // Get payment streams from MSP
  const paymentStreams = await getPaymentStreams();
  console.log('Unique Payment Streams:', paymentStreams);
  // Log current balance
  const balance = await getBalance(address); // MOCK token in wei
  console.log(
    `Current balance: ${balance} (wei), ${Number(formatEther(balance))} (MOCK)`,
  );
  // Calculate total and effective outstanding debt
  const { totalRawDebt, totalEffectiveDebt } =
    await calculateTotalOutstandingDebt(address, paymentStreams);
  // Log debts
  console.log(
    `Total Raw Debt: ${totalRawDebt} (wei), ${Number(formatEther(totalRawDebt))} (MOCK)`,
  );
  console.log(
    `Total Effective Debt: ${totalEffectiveDebt} (wei), ${Number(formatEther(totalEffectiveDebt))} (MOCK)`,
  );
  // Check if balance is sufficient to cover effective debt
  if (balance < totalEffectiveDebt) {
    console.log('Insufficient balance to pay outstanding debt.');
    await polkadotApi.disconnect();
    return;
  }

  // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT**
  // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

If you run the script with the code above, the response should look something like this:

ts-node index.ts
Current balance: 1500000000000000000 (wei), 1.5 (MOCK)
Total Raw Debt: 850000000000000000 (wei), 0.85 (MOCK)
Total Effective Debt: 740000000000000000 (wei), 0.74 (MOCK)

Pay Outstanding Debt

Now that you've calculated the outstanding debt and verified you have sufficient funds, you can pay the debt to all providers.

If all of your providers are online and have already automatically cleaned up your files and settled debts from your deposits, this step may not be necessary. However, if any provider was offline or failed to process your insolvency, you'll need to manually pay the outstanding debt to release the locked funds from those unresolved payment streams. Calling payOutstandingDebt also prevents further debt from accumulating while you wait for providers to react.

Add Method to Pay Outstanding Debt

Add the following method to your costOperations.ts file:

costOperations.ts
const payOutstandingDebt = async (providerIds: string[]) => {
  // Create and send transaction to pay outstanding debt
  const confirmTx =
    polkadotApi.tx.paymentStreams.payOutstandingDebt(providerIds);

  await new Promise<void>((resolve, reject) => {
    confirmTx
      .signAndSend(signer, ({ status, dispatchError }) => {
        console.log('Paying outstanding debt...');
        if (status.isFinalized) {
          if (dispatchError) {
            if (dispatchError.isModule) {
              // Get details of the error
              const decoded = polkadotApi.registry.findMetaError(
                dispatchError.asModule,
              );
              reject(
                new Error(
                  `${decoded.section}.${decoded.method}: ${decoded.docs.join(' ')}`,
                ),
              );
            } else {
              // Other, CannotLookup, BadOrigin, no extra info
              reject(new Error(dispatchError.toString()));
            }
          } else {
            console.log('Outstanding debt paid successfully!');
            resolve();
          }
        }
      })
      .catch(reject);
  });
};

Make sure to update your exports:

costOperations.ts
export {
  getBalance,
  isInsolvent,
  calculateTotalOutstandingDebt,
  payOutstandingDebt,
};

Call the Pay Outstanding Debt Helper Method

Replace the placeholder // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT** with the following code:

Note

When you fetch your active payment streams, some may be from the same provider, resulting in those payment streams having the same provider ID. You'll need to extract the unique provider IDs from your payment streams before paying, because the payOutstandingDebt extrinsic expects an array of unique provider IDs as a parameter.

index.ts // **PLACEHOLDER FOR STEP 5: PAY OUTSTANDING DEBT**
// Extract unique provider IDs
const providerIds = [
  ...new Set(
    paymentStreams.streams.map(
      (stream: PaymentStreamInfo) => stream.provider,
    ),
  ),
];
console.log('Provider IDs:', providerIds);
// Pay outstanding debt
await payOutstandingDebt(providerIds);
View complete index.ts up until this point
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }
  // Authenticate
  const authProfile = await authenticateUser();
  console.log('Authenticated user profile:', authProfile);
  // Get payment streams from MSP
  const paymentStreams = await getPaymentStreams();
  console.log('Unique Payment Streams:', paymentStreams);
  // Log current balance
  const balance = await getBalance(address); // MOCK token in wei
  console.log(
    `Current balance: ${balance} (wei), ${Number(formatEther(balance))} (MOCK)`,
  );
  // Calculate total and effective outstanding debt
  const { totalRawDebt, totalEffectiveDebt } =
    await calculateTotalOutstandingDebt(address, paymentStreams);
  // Log debts
  console.log(
    `Total Raw Debt: ${totalRawDebt} (wei), ${Number(formatEther(totalRawDebt))} (MOCK)`,
  );
  console.log(
    `Total Effective Debt: ${totalEffectiveDebt} (wei), ${Number(formatEther(totalEffectiveDebt))} (MOCK)`,
  );
  // Check if balance is sufficient to cover effective debt
  if (balance < totalEffectiveDebt) {
    console.log('Insufficient balance to pay outstanding debt.');
    await polkadotApi.disconnect();
    return;
  }
  // Extract unique provider IDs
  const providerIds = [
    ...new Set(
      paymentStreams.streams.map(
        (stream: PaymentStreamInfo) => stream.provider,
      ),
    ),
  ];
  console.log('Provider IDs:', providerIds);
  // Pay outstanding debt
  await payOutstandingDebt(providerIds);

  // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

If you run the script with the code above, the response should look something like this:

ts-node index.ts
Provider IDs: [
  '0x0000000000000000000000000000000000000000000000000000000000000010',
  '0x0000000000000000000000000000000000000000000000000000000000000001'
]
Paying outstanding debt...
Outstanding debt paid successfully!

Clear Insolvent Flag

After paying all outstanding debt, you must explicitly clear the insolvent flag from your account. This signals to the network that you've resolved your debt and wish to restore normal account functionality. Once the flag is cleared and the cooldown period has passed, you can resume processing new storage requests.

Add Method to Clear Insolvent Flag

Add the following method to your costOperations.ts file:

costOperations.ts
const clearInsolventFlag = async () => {
  // Create and send transaction to clear insolvent flag
  const confirmTx = polkadotApi.tx.paymentStreams.clearInsolventFlag();

  await new Promise<void>((resolve, reject) => {
    confirmTx
      .signAndSend(signer, ({ status, dispatchError }) => {
        console.log('Clearing insolvent flag...');
        if (status.isFinalized) {
          if (dispatchError) {
            if (dispatchError.isModule) {
              // Get details of the error
              const decoded = polkadotApi.registry.findMetaError(
                dispatchError.asModule,
              );
              reject(
                new Error(
                  `${decoded.section}.${decoded.method}: ${decoded.docs.join(' ')}`,
                ),
              );
            } else {
              // Other, CannotLookup, BadOrigin, no extra info
              reject(new Error(dispatchError.toString()));
            }
          } else {
            console.log('Insolvent flag cleared successfully!');
            resolve();
          }
        }
      })
      .catch(reject);
  });
};

Update your exports to include the new method:

costOperations.ts
export {
  getBalance,
  isInsolvent,
  calculateTotalOutstandingDebt,
  payOutstandingDebt,
  clearInsolventFlag,
};

Call the Clear Insolvent Flag Helper Method

Replace the placeholder // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG** with the following code:

index.ts // **PLACEHOLDER FOR STEP 6: CLEAR INSOLVENT FLAG**
// Clear insolvent flag
await clearInsolventFlag();
View complete index.ts up until this point
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // --- Insolvency resolution logic ---
  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }
  // Authenticate
  const authProfile = await authenticateUser();
  console.log('Authenticated user profile:', authProfile);
  // Get payment streams from MSP
  const paymentStreams = await getPaymentStreams();
  console.log('Unique Payment Streams:', paymentStreams);
  // Log current balance
  const balance = await getBalance(address); // MOCK token in wei
  console.log(
    `Current balance: ${balance} (wei), ${Number(formatEther(balance))} (MOCK)`,
  );
  // Calculate total and effective outstanding debt
  const { totalRawDebt, totalEffectiveDebt } =
    await calculateTotalOutstandingDebt(address, paymentStreams);
  // Log debts
  console.log(
    `Total Raw Debt: ${totalRawDebt} (wei), ${Number(formatEther(totalRawDebt))} (MOCK)`,
  );
  console.log(
    `Total Effective Debt: ${totalEffectiveDebt} (wei), ${Number(formatEther(totalEffectiveDebt))} (MOCK)`,
  );
  // Check if balance is sufficient to cover effective debt
  if (balance < totalEffectiveDebt) {
    console.log('Insufficient balance to pay outstanding debt.');
    await polkadotApi.disconnect();
    return;
  }
  // Extract unique provider IDs
  const providerIds = [
    ...new Set(
      paymentStreams.streams.map(
        (stream: PaymentStreamInfo) => stream.provider,
      ),
    ),
  ];
  console.log('Provider IDs:', providerIds);
  // Pay outstanding debt
  await payOutstandingDebt(providerIds);
  // Clear insolvent flag
  await clearInsolventFlag();

  // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**

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

run();

If you run the script with the code above, the response should look something like this:

ts-node index.ts
Clearing insolvent flag...
Insolvent flag cleared successfully!

Verify Resolution

After clearing the insolvent flag, verify that your account status has been updated.

Replace the placeholder // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION** with the following code:

index.ts // **PLACEHOLDER FOR STEP 7: VERIFY RESOLUTION**
// Re-check insolvent status
const newInsolventStatus = await isInsolvent(address);
console.log(`New insolvent status for ${address}:`, newInsolventStatus);

If you run the script with the code above, the response should look something like this:

ts-node index.ts
User 0x90b5a...c7912 without funds response: null
New insolvent status for 0x90b5a...c7912: false

Cooldown Period

After successfully clearing the insolvent flag, a cooldown period of 100 blocks (~10 minutes) applies before you can resume normal operations like creating buckets or uploading files.

View complete index.ts
index.ts
import '@storagehub/api-augment';
import { initWasm } from '@storagehub-sdk/core';
import { address, polkadotApi } from './services/clientService.js';
import { authenticateUser, getPaymentStreams } from './services/mspService.js';
import {
  calculateTotalOutstandingDebt,
  clearInsolventFlag,
  getBalance,
  isInsolvent,
  payOutstandingDebt,
} from './operations/costOperations.js';
import { PaymentStreamInfo } from '@storagehub-sdk/msp-client';
import { formatEther } from 'viem';

async function run() {
  // Initialize WASM
  await initWasm();

  // Check if insolvent
  const insolventStatus = await isInsolvent(address);
  console.log(`Insolvent status for ${address}:`, insolventStatus);
  // If not insolvent, exit early
  if (!insolventStatus) {
    console.log('Not insolvent, no action needed.');
    await polkadotApi.disconnect();
    return;
  }

  // Authenticate
  const authProfile = await authenticateUser();
  console.log('Authenticated user profile:', authProfile);

  // Get payment streams from MSP
  const paymentStreams = await getPaymentStreams();
  console.log('Unique Payment Streams:', paymentStreams);

  // Log current balance
  const balance = await getBalance(address); // MOCK token in wei
  console.log(
    `Current balance: ${balance} (wei), ${Number(formatEther(balance))} (MOCK)`,
  );
  // Calculate total and effective outstanding debt
  const { totalRawDebt, totalEffectiveDebt } =
    await calculateTotalOutstandingDebt(address, paymentStreams);
  // Log debts
  console.log(
    `Total Raw Debt: ${totalRawDebt} (wei), ${Number(formatEther(totalRawDebt))} (MOCK)`,
  );
  console.log(
    `Total Effective Debt: ${totalEffectiveDebt} (wei), ${Number(formatEther(totalEffectiveDebt))} (MOCK)`,
  );
  // Check if balance is sufficient to cover effective debt
  if (balance < totalEffectiveDebt) {
    console.log('Insufficient balance to pay outstanding debt.');
    await polkadotApi.disconnect();
    return;
  }

  // Extract unique provider IDs
  const providerIds = [
    ...new Set(
      paymentStreams.streams.map(
        (stream: PaymentStreamInfo) => stream.provider,
      ),
    ),
  ];
  console.log('Provider IDs:', providerIds);
  // Pay outstanding debt
  await payOutstandingDebt(providerIds);

  // Clear insolvent flag
  await clearInsolventFlag();

  // Re-check insolvent status
  const newInsolventStatus = await isInsolvent(address);
  console.log(`New insolvent status for ${address}:`, newInsolventStatus);

  // Disconnect from Polkadot API
  await polkadotApi.disconnect();
}

run();
View complete costOperations.ts
costOperations.ts
import { polkadotApi, publicClient } from '../services/clientService.js';
import { PaymentStreamsResponse } from '@storagehub-sdk/msp-client';
import { signer } from '../services/clientService.js';

const getBalance = async (address: `0x${string}`): Promise<bigint> => {
  // Query balance
  const balanceWei = await publicClient.getBalance({ address });
  return balanceWei;
};

const isInsolvent = async (address: `0x${string}`) => {
  // Query if user is labelled as insolvent by the network
  const userWithoutFundsResponse =
    await polkadotApi.query.paymentStreams.usersWithoutFunds(address);
  console.log(
    `User ${address} without funds response:`,
    userWithoutFundsResponse.toHuman(),
  );
  // If the userWithoutFundsResponse is null, it means the user is not insolvent
  const userIsInsolvent = userWithoutFundsResponse.isSome;
  return userIsInsolvent;
};

const calculateTotalOutstandingDebt = async (
  address: `0x${string}`,
  paymentStreams: PaymentStreamsResponse,
) => {
  const seen = new Set<string>();

  // Current tick from proofs dealer — used for MSPs (privileged providers)
  const currentTick = await polkadotApi.call.proofsDealerApi.getCurrentTick();

  let totalRawDebt = 0n;
  let totalEffectiveDebt = 0n;

  for (const { provider, providerType } of paymentStreams.streams) {
    if (seen.has(provider)) continue;
    seen.add(provider);

    if (providerType === 'bsp') {
      // BSPs use per-provider LastChargeableInfo (updated on proof submission)
      const lastChargeableInfo =
        await polkadotApi.query.paymentStreams.lastChargeableInfo(provider);

      if (!lastChargeableInfo) continue;

      const stream =
        await polkadotApi.query.paymentStreams.dynamicRatePaymentStreams(
          provider,
          address,
        );
      if (stream.isSome) {
        const dynamicRatePaymentStream = stream.unwrap();

        const rawDebt =
          ((lastChargeableInfo.priceIndex.toBigInt() -
            dynamicRatePaymentStream.priceIndexWhenLastCharged.toBigInt()) *
            dynamicRatePaymentStream.amountProvided.toBigInt()) /
          2n ** 30n;

        const effectiveDebt =
          rawDebt < dynamicRatePaymentStream.userDeposit.toBigInt()
            ? rawDebt
            : dynamicRatePaymentStream.userDeposit.toBigInt();
        totalRawDebt += rawDebt;
        totalEffectiveDebt += effectiveDebt;
      }
    } else if (providerType === 'msp') {
      // MSPs are privileged providers — they can charge up to the current tick
      const stream =
        await polkadotApi.query.paymentStreams.fixedRatePaymentStreams(
          provider,
          address,
        );
      if (stream.isSome) {
        const fixedRatePaymentStream = stream.unwrap();

        const rawDebt =
          (currentTick.toBigInt() -
            fixedRatePaymentStream.lastChargedTick.toBigInt()) *
          fixedRatePaymentStream.rate.toBigInt();

        const effectiveDebt =
          rawDebt < fixedRatePaymentStream.userDeposit.toBigInt()
            ? rawDebt
            : fixedRatePaymentStream.userDeposit.toBigInt();
        totalRawDebt += rawDebt;
        totalEffectiveDebt += effectiveDebt;
      }
    }
  }
  return { totalRawDebt, totalEffectiveDebt };
};

const payOutstandingDebt = async (providerIds: string[]) => {
  // Create and send transaction to pay outstanding debt
  const confirmTx =
    polkadotApi.tx.paymentStreams.payOutstandingDebt(providerIds);

  await new Promise<void>((resolve, reject) => {
    confirmTx
      .signAndSend(signer, ({ status, dispatchError }) => {
        console.log('Paying outstanding debt...');
        if (status.isFinalized) {
          if (dispatchError) {
            if (dispatchError.isModule) {
              // Get details of the error
              const decoded = polkadotApi.registry.findMetaError(
                dispatchError.asModule,
              );
              reject(
                new Error(
                  `${decoded.section}.${decoded.method}: ${decoded.docs.join(' ')}`,
                ),
              );
            } else {
              // Other, CannotLookup, BadOrigin, no extra info
              reject(new Error(dispatchError.toString()));
            }
          } else {
            console.log('Outstanding debt paid successfully!');
            resolve();
          }
        }
      })
      .catch(reject);
  });
};

const clearInsolventFlag = async () => {
  // Create and send transaction to clear insolvent flag
  const confirmTx = polkadotApi.tx.paymentStreams.clearInsolventFlag();

  await new Promise<void>((resolve, reject) => {
    confirmTx
      .signAndSend(signer, ({ status, dispatchError }) => {
        console.log('Clearing insolvent flag...');
        if (status.isFinalized) {
          if (dispatchError) {
            if (dispatchError.isModule) {
              // Get details of the error
              const decoded = polkadotApi.registry.findMetaError(
                dispatchError.asModule,
              );
              reject(
                new Error(
                  `${decoded.section}.${decoded.method}: ${decoded.docs.join(' ')}`,
                ),
              );
            } else {
              // Other, CannotLookup, BadOrigin, no extra info
              reject(new Error(dispatchError.toString()));
            }
          } else {
            console.log('Insolvent flag cleared successfully!');
            resolve();
          }
        }
      })
      .catch(reject);
  });
};

export {
  getBalance,
  isInsolvent,
  calculateTotalOutstandingDebt,
  payOutstandingDebt,
  clearInsolventFlag,
};

Next Steps

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