{"record":{"id":"5a813b33c0b8846a","repo":"FuelLabs/fuel-core","slug":"the-fee-address-index-is-out-of-bounds","errorCode":null,"errorMessage":"The fee address index is out of bounds","messagePattern":"The fee address index is out of bounds","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fuel-core/src/schema/tx/assemble_tx.rs","lineNumber":191,"sourceCode":"{\n    pub fn new(tx: Tx, mut arguments: AssembleArguments<'a>) -> anyhow::Result<Self> {\n        let max_inputs = arguments.consensus_parameters.tx_params().max_inputs();\n        let max_outputs = arguments.consensus_parameters.tx_params().max_outputs();\n\n        if tx.inputs().len() > max_inputs as usize {\n            return Err(anyhow::anyhow!(\n                \"The transaction has more inputs than allowed by the consensus\"\n            ));\n        }\n\n        if tx.outputs().len() > max_outputs as usize {\n            return Err(anyhow::anyhow!(\n                \"The transaction has more outputs than allowed by the consensus\"\n            ));\n        }\n\n        if arguments.fee_index as usize >= arguments.required_balances.len() {\n            return Err(anyhow::anyhow!(\"The fee address index is out of bounds\"));\n        }\n\n        if has_duplicates(&arguments.required_balances, |balance| {\n            (balance.asset_id, balance.account.owner())\n        }) {\n            return Err(anyhow::anyhow!(\n                \"required balances contain duplicate (asset, account) pair\"\n            ));\n        }\n\n        let base_asset_id = *arguments.consensus_parameters.base_asset_id();\n        let mut signature_witness_indexes = HashMap::<Address, u16>::new();\n\n        // Exclude inputs that already are used by the transaction\n        let mut has_predicates = false;\n        let inputs = tx.inputs();\n        let mut unique_used_asset = HashSet::new();\n        let mut set_contracts = HashSet::new();","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/fuel-core/src/schema/tx/assemble_tx.rs#L173-L209","documentation":"The fee_address_index argument selects which entry of required_balances pays gas for the assembled transaction. AssembleTx::new rejects the request when fee_index (a u16) is >= required_balances.len(), i.e. the index points outside the list you provided.","triggerScenarios":"Calling assembleTransaction with feeAddressIndex >= requiredBalances.length — e.g. passing requiredBalances: [] while leaving a nonzero feeAddressIndex, or reordering/shrinking the balances array after choosing the index.","commonSituations":"Empty requiredBalances array (fee-only assembly should still include one entry for the fee account); off-by-one when building balances dynamically; copying an index constant between code paths with different balance lists.","solutions":["Set feeAddressIndex to the position of the account that should pay gas — usually 0 if the first required balance is the fee payer","Ensure requiredBalances is non-empty and includes the fee-paying account","Add a client-side assertion feeAddressIndex < requiredBalances.length before the mutation"],"exampleFix":"// before\nawait client.assembleTx(txBytes, { requiredBalances: balances, feeAddressIndex: 2 }); // balances.length === 1\n\n// after\nconst feeAddressIndex = balances.findIndex((b) => b.account === FEE_PAYER);\nif (feeAddressIndex < 0 || feeAddressIndex >= balances.length) throw new Error('bad feeAddressIndex');\nawait client.assembleTx(txBytes, { requiredBalances: balances, feeAddressIndex });","handlingStrategy":"validation","validationCode":"function assertFeeIndex(requiredBalances: unknown[], feeAddressIndex: number) {\n  if (requiredBalances.length === 0) throw new Error('requiredBalances must not be empty');\n  if (!Number.isInteger(feeAddressIndex) || feeAddressIndex < 0 || feeAddressIndex >= requiredBalances.length) {\n    throw new Error(`feeAddressIndex ${feeAddressIndex} out of range for ${requiredBalances.length} balances`);\n  }\n}","typeGuard":"function isValidFeeIndex(balances: unknown[], i: unknown): i is number {\n  return typeof i === 'number' && i >= 0 && i < balances.length;\n}","tryCatchPattern":"catch (e) { if (/fee address index is out of bounds/.test(e.message)) { /* fix index to 0 or the fee payer's position and retry */ } else throw e; }","preventionTips":["Derive feeAddressIndex with findIndex on the balances array instead of hardcoding","Default to feeAddressIndex = 0 when the first balance is the fee payer","Add a unit test asserting the index invariant for every balances-building code path"],"tags":["transaction","fee","validation","graphql","assemble-tx"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}