{"record":{"id":"fa7b32f4222594f5","repo":"FuelLabs/fuel-core","slug":"missing-output-contract-on-mint-transaction","errorCode":null,"errorMessage":"Missing output_contract on mint transaction","messagePattern":"Missing output_contract on mint transaction","errorType":"exception","errorClass":"Error::Serialization","httpStatus":null,"severity":"error","filePath":"crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs","lineNumber":684,"sourceCode":"            );\n\n            Ok(FuelTransaction::Create(create_tx))\n        }\n        ProtoTransactionVariant::Mint(proto_mint) => {\n            let tx_pointer_proto = proto_mint.tx_pointer.as_ref().ok_or_else(|| {\n                Error::Serialization(anyhow!(\"Missing tx_pointer on mint transaction\"))\n            })?;\n            let tx_pointer = tx_pointer_from_proto(tx_pointer_proto)?;\n            let input_contract_proto =\n                proto_mint.input_contract.as_ref().ok_or_else(|| {\n                    Error::Serialization(anyhow!(\n                        \"Missing input_contract on mint transaction\"\n                    ))\n                })?;\n            let input_contract = contract_input_from_proto(input_contract_proto)?;\n            let output_contract_proto =\n                proto_mint.output_contract.as_ref().ok_or_else(|| {\n                    Error::Serialization(anyhow!(\n                        \"Missing output_contract on mint transaction\"\n                    ))\n                })?;\n            let output_contract = contract_output_from_proto(output_contract_proto)?;\n            let mint_asset_id = fuel_core_types::fuel_types::AssetId::try_from(\n                proto_mint.mint_asset_id.as_slice(),\n            )\n            .map_err(|e| Error::Serialization(anyhow!(e)))?;\n\n            let mint_tx = FuelTransaction::mint(\n                tx_pointer,\n                input_contract,\n                output_contract,\n                proto_mint.mint_amount,\n                mint_asset_id,\n                proto_mint.gas_price,\n            );\n","sourceCodeStart":666,"sourceCodeEnd":702,"githubUrl":"https://github.com/FuelLabs/fuel-core/blob/b9d4d170da3a31c9ace5f963d633b326348e0d42/crates/services/block_aggregator_api/src/blocks/old_block_source/convertor_adapter/proto_to_fuel_conversions.rs#L666-L702","documentation":"The Mint transaction conversion needs both a contract input and a contract output; this error fires when the proto mint message has output_contract unset. It is a hard failure (Error::Serialization) raised before FuelTransaction::mint is constructed, because the fuel-core Mint type requires the output contract.","triggerScenarios":"A ProtoTransactionVariant::Mint arrives with input_contract present but output_contract None (or both unset — this error surfaces once input checks pass). Typical with partial encoders or schema-skew between block source and aggregator.","commonSituations":"Producer node built with a proto version where mint output_contract was optional/newly added; truncated or hand-crafted proto payloads; replaying legacy archived blocks.","solutions":["Fix the producer to always set output_contract on mint transactions and re-encode the affected blocks","Align proto definitions (protobuf_types.rs) between producer and consumer versions","Validate mint messages up front and quarantine blocks containing incomplete mint transactions instead of failing the whole stream","Re-encode legacy data through a migration that derives the output contract from the input contract's contract_id"],"exampleFix":"// before\nif let Some(mint) = variant_as_mint(&proto_tx) { convert_mint(mint)?; } // fails mid-way\n\n// after\nfn mint_is_complete(m: &ProtoMint) -> bool {\n    m.tx_pointer.is_some() && m.input_contract.is_some() && m.output_contract.is_some()\n}\nif mint_is_complete(&mint) { convert_mint(&mint)?; } else { skip_and_report(&proto_tx); }","handlingStrategy":"validation","validationCode":"fn mint_tx_is_complete(m: &ProtoMint) -> bool {\n    m.tx_pointer.is_some() && m.input_contract.is_some() && m.output_contract.is_some()\n}","typeGuard":"fn has_output_contract(m: &ProtoMint) -> bool { m.output_contract.is_some() }","tryCatchPattern":"if let Err(e) = convert_transaction(&proto_tx) {\n    if e.to_string().contains(\"Missing output_contract on mint\") {\n        metrics::incomplete_mint_tx.inc(); quarantine(proto_tx.id());\n    } else { return Err(e); }\n}","preventionTips":["Treat mint's input_contract and output_contract as required in producer code reviews","Add schema-level comments marking the fields as mandatory for round-tripping","Re-encode legacy blocks through a validated migration before ingestion"],"tags":["rust","fuel","protobuf","serialization","mint-transaction","conversion"],"backgroundTag":null,"analyzedSha":"b9d4d170da3a31c9ace5f963d633b326348e0d42","analyzedAt":"2026-08-16T08:56:42.692Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}