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:
-
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 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:
- Pay your outstanding debt to providers (only needed if providers haven't already cleaned up your payment streams automatically).
- Manually clear the insolvent flag.
- 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:
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¶
-
Create a new folder called
operationswithin thesrcfolder (at the same level as theservicesfolder) if you haven't already: -
Create a new file within the
operationsfolder calledcostOperations.tsand add the following code:costOperations.tsimport { 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:
// 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
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:
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:
// Authenticate
const authProfile = await authenticateUser();
console.log('Authenticated user profile:', authProfile);
View complete index.ts up until this point
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.
-
If you haven't already implemented the
getPaymentStreamsmethod from the Track Costs guide, add the following code to yourmspService.tsfile:mspService.tsconst 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 -
Replace the placeholder
// **PLACEHOLDER FOR STEP 3: GET PAYMENT STREAMS**with the following code:
View complete index.ts up until this point
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:
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:
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:
Call the Calculate Outstanding Debt Helper Method¶
Replace the placeholder // **PLACEHOLDER FOR STEP 4: CALCULATE OUTSTANDING DEBT** with the following code:
// 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
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:
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:
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:
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.
// 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
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:
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:
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:
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:
// Clear insolvent flag
await clearInsolventFlag();
View complete index.ts up until this point
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:
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:
// 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:
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
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
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¶
| Created: February 18, 2026