{"record":{"id":"bea51e857ea09d80","repo":"FuelLabs/fuel-core","slug":"missing-contract-id","errorCode":null,"errorMessage":"missing contract id","messagePattern":"missing contract id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fuel-core/src/schema/tx/assemble_tx.rs","lineNumber":847,"sourceCode":"            if !has_spendable_input {\n                script.inputs_mut().pop();\n            }\n\n            let mut contracts_not_in_inputs = Vec::new();\n\n            match &status.result {\n                TransactionExecutionResult::Success { .. } => break,\n                TransactionExecutionResult::Failed { receipts, .. } => {\n                    for receipt in receipts.iter() {\n                        if let Receipt::Panic {\n                            reason,\n                            contract_id,\n                            ..\n                        } = receipt\n                            && reason.reason() == &PanicReason::ContractNotInInputs\n                        {\n                            let contract_id = contract_id\n                                .ok_or_else(|| anyhow::anyhow!(\"missing contract id\"))?;\n                            contracts_not_in_inputs.push(contract_id);\n                        }\n                    }\n                }\n            }\n\n            if contracts_not_in_inputs.is_empty() {\n                break\n            }\n\n            for contract_id in contracts_not_in_inputs {\n                if !self.set_contracts.insert(contract_id) {\n                    continue\n                }\n\n                let inptus = script.inputs_mut();\n\n                let contract_idx = u16::try_from(inptus.len()).unwrap_or(u16::MAX);","sourceCodeStart":829,"sourceCodeEnd":865,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/fuel-core/src/schema/tx/assemble_tx.rs#L829-L865","documentation":"While estimating the script gas limit, the assembler inspects dry-run receipts for Panic receipts with PanicReason::ContractNotInInputs — those tell it which contract to add as an input so the call succeeds on retry. This error fires when such a Panic receipt carries no contract_id, so the assembler cannot tell which contract to add.","triggerScenarios":"A script calls a contract that is not among the tx inputs (no Input::Contract for that ContractId), and the VM-produced panic receipt lacks the contract id field. The assembler then cannot auto-repair the tx.","commonSituations":"SDKs or hand-built scripts referencing a contract without adding its Input::Contract + Output::Contract pair; unusual instruction paths (e.g. CROO-based or indirect calls) where the panic receipt is emitted without the id; VM version changes in how panic receipts are populated.","solutions":["Add the missing contract inputs yourself before assembly: for each called contract, push Input::contract(...) and a matching Output::Contract","Update the node/VM to a version that populates contract_id on ContractNotInInputs panic receipts","If it persists on a stock node with a well-formed script, report it as a VM bug with the script bytes"],"exampleFix":"// Rust (fuel-tx)\n// before\nlet mut script = Script::default(); /* bytecode calls contract C */\n\n// after\nscript.inputs_mut().push(Input::contract(\n  UtxoId::new(*OUTPOINT_TXID, 0),\n  contract_root_of_c, state_root_of_c, tx_id_of_c,\n));\nscript.outputs_mut().push(Output::Contract { input_index: (script.inputs().len() - 1) as u8 });","handlingStrategy":"validation","validationCode":"// before assembly, ensure every called contract has an input+output pair\nfunction assertContractsInInputs(script: Script, calledContractIds: ContractId[]) {\n  const present = new Set(script.inputs.filter(i => i.type === 'Contract').map(i => i.contractId));\n  for (const id of calledContractIds) {\n    if (!present.has(id)) throw new Error(`contract ${id} called but not in inputs`);\n  }\n}","typeGuard":null,"tryCatchPattern":"catch (e) { if (/missing contract id/.test(e.message)) { /* add Input::Contract + Output::Contract for the called contract and resubmit; report VM bug if inputs were complete */ } else throw e; }","preventionTips":["Wallets/SDKs: always pair Input::Contract with Output::Contract for called contracts","Track the contract id set your script calls (from its configuration/ABI) at build time","Pin VM/node versions known to populate contract_id on ContractNotInInputs panics"],"tags":["script","contract","dry-run","receipts","assemble-tx"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}