{"record":{"id":"dbab8c20d21215dc","repo":"FuelLabs/fuels-ts","slug":"asset-burn-detected","errorCode":"ASSET_BURN_DETECTED","errorMessage":"Asset burn detected.\nAdd the relevant change outputs to the transaction to avoid burning assets.\nOr enable asset burn, upon sending the transaction.","messagePattern":"Asset burn detected\\.\nAdd the relevant change outputs to the transaction to avoid burning assets\\.\nOr enable asset burn, upon sending the transaction\\.","errorType":"validation","errorClass":"FuelError","httpStatus":null,"severity":"error","filePath":"packages/account/src/providers/transaction-request/helpers.ts","lineNumber":169,"sourceCode":"  transactionRequest: TransactionRequest,\n  enableAssetBurn: boolean = false\n) => {\n  // Asset burn is enabled\n  if (enableAssetBurn === true) {\n    return;\n  }\n\n  // No burnable assets detected\n  if (getBurnableAssetCount(baseAssetId, transactionRequest) <= 0) {\n    return;\n  }\n\n  const message = [\n    'Asset burn detected.',\n    'Add the relevant change outputs to the transaction to avoid burning assets.',\n    'Or enable asset burn, upon sending the transaction.',\n  ].join('\\n');\n  throw new FuelError(ErrorCode.ASSET_BURN_DETECTED, message);\n};\n","sourceCodeStart":151,"sourceCodeEnd":171,"githubUrl":"https://github.com/FuelLabs/fuels-ts/blob/b3f37c91aca4aa9d5e4c0d3967f66237190826ea/packages/account/src/providers/transaction-request/helpers.ts#L151-L171","documentation":"Thrown by validateTransactionForAssetBurn when the transaction would burn a non-base-asset input that has no matching change output, and the caller has not opted into asset burn. The SDK computes getBurnableAssetCount: coin inputs (and message inputs with amount > 0 on the base asset) whose assetId has no Change output are considered burnable. Burning is destructive, so it is opt-in via enableAssetBurn.","triggerScenarios":"Building a transaction that spends an asset but omits the Change output for that assetId; transferring all of a coin's amount forward without a change output for the remainder; sending a tx with inputs of an asset but outputs only for a different asset; calling sendTransaction without enableAssetBurn when the assembled tx would burn.","commonSituations":"Manually constructing a ScriptTransactionRequest and forgetting a change output per asset; spending a message with amount > 0 on the base asset without a base-asset change output; intentionally burning but not setting enableAssetBurn; SDK upgrade that started enforcing this guard by default.","solutions":["Add a Change output for each assetId present in the inputs: tx.addChangeOutput?(...) or ensure the account/assemble flow adds change outputs.","If burning is intentional, pass enableAssetBurn: true on the send/submit path.","Audit inputs vs outputs by assetId before submitting: every input assetId should have a change or matching output.","Use the account's higher-level send flows (transfer/sendTransaction) which add change outputs automatically."],"exampleFix":"// before — input asset has no change output -> would burn\nconst tx = new ScriptTransactionRequest();\ntx.addCoinInput(coinOfAssetB);\ntx.addCoinOutput(recipient, coinOfAssetB.amount, assetB); // no change output for assetB leftover\n\n// after — add a change output for each asset (or let the SDK do it)\ntx.addChangeOutput?.(); // or use account.transfer which adds change outputs\n\n// or, if burning is intended\nawait provider.sendTransaction(tx, { enableAssetBurn: true });","handlingStrategy":"validation","validationCode":"function assertNoUnintendedBurn(baseAssetId, tx) {\n  const inputAssets = new Set(tx.inputs.filter(isRequestInputCoin).map(i => i.assetId));\n  const changeAssets = new Set(tx.outputs.filter(o => o.type === OutputType.Change).map(o => o.assetId));\n  const burnt = [...inputAssets].filter(a => !changeAssets.has(a));\n  if (burnt.length) throw new Error(`Missing change output for assets: ${burnt.join(', ')}`);\n}","typeGuard":"function hasChangeOutputForEveryAsset(tx): boolean {\n  const inAssets = new Set(tx.inputs.filter(isRequestInputCoin).map(i => i.assetId));\n  const outAssets = new Set(tx.outputs.filter(o => o.type === OutputType.Change).map(o => o.assetId));\n  return [...inAssets].every(a => outAssets.has(a));\n}","tryCatchPattern":"import { FuelError, ErrorCode } from '@fuel-ts/errors';\ntry {\n  await provider.sendTransaction(tx);\n} catch (e) {\n  if (e instanceof FuelError && e.code === ErrorCode.ASSET_BURN_DETECTED) {\n    // either add the missing change outputs, or pass enableAssetBurn: true if burning is intended\n  }\n  throw e;\n}","preventionTips":["Use higher-level account.transfer/sendTransaction which add change outputs automatically.","When building txs manually, add a Change output per input assetId.","Only enable asset burn deliberately via enableAssetBurn: true."],"tags":["transaction","asset-burn","outputs","validation","safety"],"backgroundTag":null,"analyzedSha":"b3f37c91aca4aa9d5e4c0d3967f66237190826ea","analyzedAt":"2026-08-12T20:30:56.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}