datahaven-xyz/datahaven · critical

InsufficientSovereignBalance

InsufficientSovereignBalance

Error message

InsufficientSovereignBalance

What it means

unlock_tokens (the Ethereum→DataHaven direction) verifies that the Ethereum sovereign account holds enough free balance to cover the unlock. Available funds are computed as balance minus minimum_balance (existential deposit buffer); if the requested amount exceeds that, the pallet fails with InsufficientSovereignBalance so the sovereign account is never reaped below its existential deposit.

Solutions

  1. Fund the Ethereum sovereign account on DataHaven so its balance minus existential deposit covers pending unlocks.
  2. Check T::Currency::balance(sovereign) before submitting and queue the unlock until liquidity exists.
  3. Investigate inflow/outflow accounting if the sovereign is persistently short — it may indicate minted withdrawals exceeded locked deposits.
  4. Retry after the account is topped up; the user's funds were not moved.

Example fix

// before: submit blindly
await api.tx.datahavenNativeTransfer.unlockTokens(who, amount).signAndSend(caller);
// after
const sovereign = await api.query.datahavenNativeTransfer.ethereumSovereignAccount();
const { data: bal } = await api.query.system.account(sovereign);
const available = bal.free - api.consts.balances.existentialDeposit;
if (available < amount) throw new Error('sovereign balance insufficient for unlock');
await api.tx.datahavenNativeTransfer.unlockTokens(who, amount).signAndSend(caller);
Defensive patterns

Strategy: validation

Validate before calling

const sovereign = await api.query.datahavenNativeTransfer.ethereumSovereignAccount();
const { data: bal } = await api.query.system.account(sovereign);
const available = bal.free.toBigInt() - api.consts.balances.existentialDeposit.toBigInt();
if (available < amount) throw new Error(`Sovereign account short by ${amount - available}`);

Try / catch

try {
  await api.tx.datahavenNativeTransfer.unlockTokens(who, amount).signAndSend(caller);
} catch (e) {
  if (String(e).includes('InsufficientSovereignBalance')) alertOps('Sovereign underfunded; top up before retry');
  else throw e;
}

Prevention

When it happens

Trigger: Calling unlock_tokens (lib.rs:302 region) where amount > Currency::balance(sovereign) - Currency::minimum_balance(), i.e. the sovereign account's spendable (post-buffer) balance is below the requested amount.

Common situations: Sovereign account underfunded because more tokens were bridged out of Ethereum than deposited in (bridge insolvency risk); several users unlocking after large withdrawals drained the account; misconfigured sovereign funding during testnet setup.

Related errors


AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13). Data as JSON: /api/errors/349e6759e59da23c. Report an issue: GitHub.

Appendix: source

Thrown at operator/pallets/datahaven-native-transfer/src/lib.rs:302

            Self::deposit_event(Event::TokensLocked {
                account: who.clone(),
                amount,
            });

            Ok(())
        }

        /// Unlock tokens returning from Ethereum
        ///
        /// Transfers tokens from the Ethereum sovereign account back to user
        pub fn unlock_tokens(who: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
            let sovereign = T::EthereumSovereignAccount::get();
            let balance = T::Currency::balance(&sovereign);
            let minimum_balance = T::Currency::minimum_balance();
            let available_balance = balance.saturating_sub(minimum_balance);

            // Allow unlocking only from funds that exceed the existential buffer.
            ensure!(
                available_balance >= amount,
                Error::<T>::InsufficientSovereignBalance
            );

            // Transfer from the Ethereum sovereign account
            T::Currency::transfer(&sovereign, who, amount, Preservation::Preserve)?;

            Self::deposit_event(Event::TokensUnlocked {
                account: who.clone(),
                amount,
            });

            Ok(())
        }

        /// Get the balance of locked tokens in the Ethereum sovereign account
        /// This represents the total amount of tokens locked for transfers to Ethereum
        pub fn total_locked_balance() -> BalanceOf<T> {

View on GitHub (pinned to edcb13dbbc)