FuelLabs/fuel-core · error
fee index out of bounds
Error message
fee index out of bounds
What it means
A second, defensive lookup of the fee payer: when assembly begins it fetches required_balances[fee_index] to get the account that covers gas, erroring if the index is out of bounds. Through the GraphQL API this is normally unreachable because AssembleTx::new already validated fee_index < required_balances.len() at construction.
Source
Thrown at crates/fuel-core/src/schema/tx/assemble_tx.rs:268
}
}
let mut change_output_policies = HashMap::<AssetId, ChangePolicy>::new();
let mut set_change_outputs = HashSet::<AssetId>::new();
for output in tx.outputs() {
if let Output::Change { to, asset_id, .. } = output {
change_output_policies.insert(*asset_id, ChangePolicy::Change(*to));
set_change_outputs.insert(*asset_id);
}
}
let mut base_asset_reserved: Option<u64> = None;
let fee_payer_account = arguments
.required_balances
.get(arguments.fee_index as usize)
.ok_or_else(|| anyhow::anyhow!("fee index out of bounds"))?
.account
.clone();
let mut requested_asset = HashSet::new();
for required_balance in &arguments.required_balances {
let asset_id = required_balance.asset_id;
requested_asset.insert(asset_id);
if asset_id == base_asset_id
&& fee_payer_account.owner() == required_balance.account.owner()
{
base_asset_reserved = Some(required_balance.amount);
}
}
for input_asset_id in &unique_used_asset {
// If the user didn't request the asset, we add it to the required balances
// with minimal amount `0` and `ChangePolicy::Change` policy.View on GitHub (pinned to b9d4d170da)
Solutions
- Do not modify AssembleArguments.required_balances after AssembleTx::new — treat the arguments as consumed
- Rebuild the assembler with a fresh AssembleArguments whenever the balances list changes
- Keep the same fee_index < required_balances.len() invariant you already validated at construction
Example fix
// Rust embedder // before let mut assembler = AssembleTx::new(tx, args)?; assembler.arguments_mut().required_balances.retain(|b| b.amount > 0); // may drop the fee payer let tx = assembler.assemble().await?; // after let mut args = args; args.required_balances.retain(|b| b.amount > 0); args.fee_index = args.fee_index.min((args.required_balances.len() as u16).saturating_sub(1)); let tx = AssembleTx::new(tx, args)?.assemble().await?;
Defensive patterns
Strategy: validation
Validate before calling
// Rust embedder: validate immediately before assembly, not just at construction
fn args_valid(args: &AssembleArguments<'_>) -> bool {
(args.fee_index as usize) < args.required_balances.len()
} Try / catch
catch (e) { if (/fee index out of bounds/.test(e.message)) { /* rebuild AssembleArguments with a consistent fee_index */ } else throw e; } Prevention
- Treat AssembleArguments as consumed by AssembleTx::new — never mutate it afterwards
- When filtering required_balances, clamp or recompute fee_index in the same code block
- Prefer rebuilding the whole assembler over patching fields
When it happens
Trigger: Practically limited to code embedding the assembler directly (AssembleTx::new + assemble) that mutates arguments.required_balances between construction and the assembly step, shrinking it below fee_index.
Common situations: Library embedders reusing an AssembleArguments struct after filtering its required_balances; test harnesses that drain the vec before calling assemble.
Related errors
- The fee address index is out of bounds
- required balances contain duplicate (asset, account) pair
- The asset {} has multiple change policies
- Unable to add more inputs because reached the maximum allowe
- The transaction has more outputs than allowed by the consens
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/d9c10344d0e2d3aa.
Report an issue: GitHub.