FuelLabs/fuel-core · error
missing contract id
Error message
missing contract id
What it means
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.
Source
Thrown at crates/fuel-core/src/schema/tx/assemble_tx.rs:847
if !has_spendable_input {
script.inputs_mut().pop();
}
let mut contracts_not_in_inputs = Vec::new();
match &status.result {
TransactionExecutionResult::Success { .. } => break,
TransactionExecutionResult::Failed { receipts, .. } => {
for receipt in receipts.iter() {
if let Receipt::Panic {
reason,
contract_id,
..
} = receipt
&& reason.reason() == &PanicReason::ContractNotInInputs
{
let contract_id = contract_id
.ok_or_else(|| anyhow::anyhow!("missing contract id"))?;
contracts_not_in_inputs.push(contract_id);
}
}
}
}
if contracts_not_in_inputs.is_empty() {
break
}
for contract_id in contracts_not_in_inputs {
if !self.set_contracts.insert(contract_id) {
continue
}
let inptus = script.inputs_mut();
let contract_idx = u16::try_from(inptus.len()).unwrap_or(u16::MAX);View on GitHub (pinned to b9d4d170da)
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
Example fix
// Rust (fuel-tx)
// before
let mut script = Script::default(); /* bytecode calls contract C */
// after
script.inputs_mut().push(Input::contract(
UtxoId::new(*OUTPOINT_TXID, 0),
contract_root_of_c, state_root_of_c, tx_id_of_c,
));
script.outputs_mut().push(Output::Contract { input_index: (script.inputs().len() - 1) as u8 }); Defensive patterns
Strategy: validation
Validate before calling
// before assembly, ensure every called contract has an input+output pair
function assertContractsInInputs(script: Script, calledContractIds: ContractId[]) {
const present = new Set(script.inputs.filter(i => i.type === 'Contract').map(i => i.contractId));
for (const id of calledContractIds) {
if (!present.has(id)) throw new Error(`contract ${id} called but not in inputs`);
}
} Try / catch
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; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- During script gas limit estimation, dry-run returned incorre
- Run out of slots for the contract outputs
- dry run count overflow
- Missing code for contract: {id}
- 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/bea51e857ea09d80.
Report an issue: GitHub.