linera-io/linera-protocol · warning · async_graphql::Error
Not enough unlocked balance; try again later.
Error message
Not enough unlocked balance; try again later.
What it means
The faucet rate-limits payouts so its balance decays linearly over the configured lifetime: a batch is refused when `remaining_balance / remaining_duration < start_balance / full_duration` (metric `RATE_LIMIT_REJECTIONS`). This message means the faucet still holds tokens but they are effectively locked by the schedule — claims are arriving faster than the configured unlock rate between `start_timestamp` and `end_timestamp`. It is transient by design; the whole batch is rejected and retried later.
Source
Thrown at linera-faucet/server/src/lib.rs:911
.iter()
.fold(Amount::ZERO, |acc, r| acc.saturating_add(r.amount));
let Ok(remaining_balance) = balance.try_sub(total_amount) else {
// Not enough balance - reject all requests
#[cfg(with_metrics)]
metrics::INSUFFICIENT_BALANCE_REJECTIONS
.with_label_values(&[])
.inc();
return Err(Error::new("The faucet is empty."));
};
// Rate limit: Locked token balance decreases lineraly with time, i.e.:
// remaining_balance / remaining_duration >= start_balance / full_duration
if multiply(u128::from(self.config.start_balance), remaining_duration)
> multiply(u128::from(remaining_balance), full_duration)
{
#[cfg(with_metrics)]
metrics::RATE_LIMIT_REJECTIONS.with_label_values(&[]).inc();
return Err(Error::new("Not enough unlocked balance; try again later."));
}
Ok(())
}
/// Sends an error response to all requestors.
fn send_err(requests: Vec<PendingRequest>, err: impl Into<async_graphql::Error>) {
let err = err.into();
for request in requests {
request.send_err(err.clone());
}
}
/// Executes a batch of chain creation and/or token transfer requests.
async fn execute_batch(&mut self, requests: Vec<PendingRequest>) -> anyhow::Result<()> {
if let Err(err) = self.check_rate_limiting(&requests).await {
tracing::debug!("Rejecting requests due to rate limiting: {err:?}");
Self::send_err(requests, err);
return Ok(());View on GitHub (pinned to 6c226ddcb3)
Solutions
- Retry with backoff — the allowed payout grows continuously as time passes.
- If you operate the faucet, raise `start_balance` or extend `end_timestamp` so the unlock rate covers the load.
- Verify `start_timestamp`/`end_timestamp` configuration; a mis-set window makes the schedule unreachable.
- Pace requests client-side instead of submitting bursts.
Example fix
// before
await faucet.claim(owner); // throws when the current burst exceeds the unlock rate
// after
async function claimWithBackoff(owner) {
for (let delay = 30_000; ; delay = Math.min(delay * 2, 1_800_000)) {
try { return await faucet.claim(owner); }
catch (e) { if (!/not enough unlocked balance/i.test(String(e.message))) throw e; await sleep(delay); }
}
} Defensive patterns
Strategy: retry
Try / catch
for (const delay of backoff(30_000, 1_800_000)) { try { return await faucet.claim(owner); } catch (e) { if (!/not enough unlocked balance/i.test(e.message)) throw e; await sleep(delay); } } Prevention
- Pace claims client-side instead of bursting; the unlock schedule is linear in time.
- Operators: size start_balance and end_timestamp to the expected claim rate.
- Check faucet config start/end timestamps after any redeploy.
When it happens
Trigger: Claim rate exceeding the configured linear schedule (burst of claims early in the faucet's lifetime); a batch whose total amount would push the balance below the schedule line; approaching `end_timestamp` with substantial balance still locked.
Common situations: Load tests hammering a faucet configured for a long duration; misconfigured `start_timestamp` (e.g. set in the past, shrinking `full_duration` and inflating the required rate); many users claiming right after a devnet launch.
Related errors
- You have already claimed tokens for this period
- please specify one of `--faucet` or `--genesis`.
- Failed to start faucet
- no admin chain (Root(0)) in genesis config
- The chain with the ID returned by the faucet is not owned by
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/720a6a887578e6fd.
Report an issue: GitHub.