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

  1. Add the missing contract inputs yourself before assembly: for each called contract, push Input::contract(...) and a matching Output::Contract
  2. Update the node/VM to a version that populates contract_id on ContractNotInInputs panic receipts
  3. 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

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


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/bea51e857ea09d80. Report an issue: GitHub.