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

  1. Remove pre-existing Change outputs for assets you also declare in requiredBalances — the assembler creates change outputs itself
  2. Align the changeAccount owner of the required balance with the recipient of the existing Change output for that asset
  3. 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

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


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