{"record":{"id":"349e6759e59da23c","repo":"datahaven-xyz/datahaven","slug":"insufficientsovereignbalance","errorCode":"InsufficientSovereignBalance","errorMessage":"InsufficientSovereignBalance","messagePattern":"InsufficientSovereignBalance","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"operator/pallets/datahaven-native-transfer/src/lib.rs","lineNumber":302,"sourceCode":"            Self::deposit_event(Event::TokensLocked {\n                account: who.clone(),\n                amount,\n            });\n\n            Ok(())\n        }\n\n        /// Unlock tokens returning from Ethereum\n        ///\n        /// Transfers tokens from the Ethereum sovereign account back to user\n        pub fn unlock_tokens(who: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {\n            let sovereign = T::EthereumSovereignAccount::get();\n            let balance = T::Currency::balance(&sovereign);\n            let minimum_balance = T::Currency::minimum_balance();\n            let available_balance = balance.saturating_sub(minimum_balance);\n\n            // Allow unlocking only from funds that exceed the existential buffer.\n            ensure!(\n                available_balance >= amount,\n                Error::<T>::InsufficientSovereignBalance\n            );\n\n            // Transfer from the Ethereum sovereign account\n            T::Currency::transfer(&sovereign, who, amount, Preservation::Preserve)?;\n\n            Self::deposit_event(Event::TokensUnlocked {\n                account: who.clone(),\n                amount,\n            });\n\n            Ok(())\n        }\n\n        /// Get the balance of locked tokens in the Ethereum sovereign account\n        /// This represents the total amount of tokens locked for transfers to Ethereum\n        pub fn total_locked_balance() -> BalanceOf<T> {","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/datahaven-xyz/datahaven/blob/edcb13dbbcd3c29489eaa2480a6f60ee4cb1f3ec/operator/pallets/datahaven-native-transfer/src/lib.rs#L284-L320","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Fund the Ethereum sovereign account on DataHaven so its balance minus existential deposit covers pending unlocks.","Check T::Currency::balance(sovereign) before submitting and queue the unlock until liquidity exists.","Investigate inflow/outflow accounting if the sovereign is persistently short — it may indicate minted withdrawals exceeded locked deposits.","Retry after the account is topped up; the user's funds were not moved."],"exampleFix":"// before: submit blindly\nawait api.tx.datahavenNativeTransfer.unlockTokens(who, amount).signAndSend(caller);\n// after\nconst sovereign = await api.query.datahavenNativeTransfer.ethereumSovereignAccount();\nconst { data: bal } = await api.query.system.account(sovereign);\nconst available = bal.free - api.consts.balances.existentialDeposit;\nif (available < amount) throw new Error('sovereign balance insufficient for unlock');\nawait api.tx.datahavenNativeTransfer.unlockTokens(who, amount).signAndSend(caller);","handlingStrategy":"validation","validationCode":"const sovereign = await api.query.datahavenNativeTransfer.ethereumSovereignAccount();\nconst { data: bal } = await api.query.system.account(sovereign);\nconst available = bal.free.toBigInt() - api.consts.balances.existentialDeposit.toBigInt();\nif (available < amount) throw new Error(`Sovereign account short by ${amount - available}`);","typeGuard":null,"tryCatchPattern":"try {\n  await api.tx.datahavenNativeTransfer.unlockTokens(who, amount).signAndSend(caller);\n} catch (e) {\n  if (String(e).includes('InsufficientSovereignBalance')) alertOps('Sovereign underfunded; top up before retry');\n  else throw e;\n}","preventionTips":["Monitor sovereign account balance with alerts against aggregate pending unlocks.","Keep a funding runbook to top up the sovereign account quickly.","Reconcile deposits (locks on Ethereum) with unlocks to detect insolvency early."],"tags":["bridge","insolvency","balance","substrate"],"backgroundTag":"insufficient-funds","analyzedSha":"edcb13dbbcd3c29489eaa2480a6f60ee4cb1f3ec","analyzedAt":"2026-09-13T19:19:32.206Z","contentChangedAt":"2026-09-13T19:19:32.206Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}