FuelLabs/fuel-core · error
The asset {} has multiple change policies
Error message
The asset {} has multiple change policies What it means
Each asset gets exactly one change policy: either the Change output the tx already carries for that asset, or the change_policy attached to a required balance. The assembler errors when a required balance's change policy conflicts with the policy already registered for the same asset (from a pre-existing Change output in your tx or from an earlier required balance).
Source
Thrown at crates/fuel-core/src/schema/tx/assemble_tx.rs:311
amount: 0,
change_policy: ChangePolicy::Change(recipient),
});
}
}
if base_asset_reserved.is_none() {
base_asset_reserved = Some(0);
}
for required_balance in &arguments.required_balances {
let asset_id = required_balance.asset_id;
let entry = change_output_policies.entry(asset_id);
match entry {
Entry::Occupied(old) => {
if old.get() != &required_balance.change_policy {
return Err(anyhow::anyhow!(
"The asset {} has multiple change policies",
asset_id
));
}
}
Entry::Vacant(vacant) => {
vacant.insert(required_balance.change_policy);
}
}
}
// Removed required balances with zero amount and if they are not used in inputs
let required_balances = core::mem::take(&mut arguments.required_balances);
arguments.required_balances = required_balances
.into_iter()
.filter_map(|r| {
if r.amount != 0 || unique_used_asset.contains(&r.asset_id) {
Some(r)View on GitHub (pinned to b9d4d170da)
Solutions
- Remove pre-existing Change outputs for assets you also declare in requiredBalances — the assembler creates change outputs itself
- Align the changeAccount owner of the required balance with the recipient of the existing Change output for that asset
- Pick one policy per asset: Change(to one owner) or Destroy, never both
Example fix
// before
const tx = new ScriptTransaction();
tx.addOutput(new OutputChange(assetA, ownerOld));
await assemble(tx, { requiredBalances: [{ account: ownerNew, assetId: assetA, ... }] });
// after
const tx = new ScriptTransaction(); // no manual Change output for assetA
await assemble(tx, { requiredBalances: [{ account: ownerNew, assetId: assetA, changeAccount: ownerNew, ... }] }); Defensive patterns
Strategy: validation
Validate before calling
function assertNoPolicyConflict(tx: { outputs: Array<{ type: string; assetId?: string; to?: string }> }, balances: Array<{ assetId: string; changeAccount: string | null }>) {
const changeOwners = new Map(tx.outputs.filter(o => o.type === 'Change').map(o => [o.assetId!, o.to!]));
for (const b of balances) {
const existing = changeOwners.get(b.assetId);
const policyOwner = b.changeAccount ?? 'Destroy';
if (existing && existing !== policyOwner) {
throw new Error(`asset ${b.assetId} has conflicting change policies`);
}
}
} Try / catch
catch (e) { if (/has multiple change policies/.test(e.message)) { /* drop manual Change outputs for that asset or align changeAccount, then retry */ } else throw e; } Prevention
- Do not pre-attach Change outputs for assets declared in requiredBalances
- Keep one change policy decision per asset in one place in your tx-building code
- Beware: policy equality is by owner address — a different owner means a conflict
When it happens
Trigger: Submitting a tx that already contains Output::Change { asset_id: A, to: owner1 } together with a requiredBalances entry for asset A whose changeAccount.owner() differs; or mixing ChangePolicy::Change for asset A with ChangePolicy::Destroy for the same asset.
Common situations: Wallets that pre-attach change outputs and then also set changeAccount in requiredBalances; switching an asset to destroy-change while the template tx still emits a Change output; two required balances for the same asset (different accounts) with different change policies.
Related errors
- The fee address index is out of bounds
- required balances contain duplicate (asset, account) pair
- The transaction has more outputs than allowed by the consens
- fee index out of bounds
- Unable to add more `Change` outputs because reached the maxi
AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16).
Data as JSON: /api/errors/402a4a0a4c773c48.
Report an issue: GitHub.