Skip to content

Commit 039c707

Browse files
committed
fix(withdraw-cli): use canonical cost-model order for script-integrity hash
The Conway-safe protocol-params patch sorted Blockfrost's named `cost_models` parameters alphabetically — not the canonical Plutus order — so the script-integrity hash mismatched the ledger's and every real withdraw was rejected with PPViewHashesDontMatch. Build and evaluation pass, so the fault only surfaced on submit. Use `cost_models_raw` (Blockfrost's positional, already-canonical array); the named-object fallback now preserves insertion order. Verified on Preprod: a Withdraw now submits and confirms on-chain.
1 parent 31e338e commit 039c707

1 file changed

Lines changed: 8 additions & 3 deletions

File tree

withdraw-cli/src/vault.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,10 @@ function patchProtocolParamsConwaySafe(bf: any, url: string, key: string): void
278278
throw new Error(`Blockfrost protocol-params returned non-JSON: ${text.slice(0, 100)}`)
279279
}
280280
const r = JSON.parse(text) as any
281-
// Normalise cost_models from named-key to positional (canonical Plutus order).
281+
// Fallback: convert a named-key cost_models object to positional form.
282+
// Blockfrost lists the parameters in canonical Plutus order, so positions
283+
// follow iteration (insertion) order — sorting the names would scramble
284+
// them and corrupt the script-integrity hash.
282285
const normalizeCostModels = (raw: any): any => {
283286
if (!raw || typeof raw !== 'object') return raw
284287
const out: any = {}
@@ -287,7 +290,6 @@ function patchProtocolParamsConwaySafe(bf: any, url: string, key: string): void
287290
const entries = Object.entries(paramObj as Record<string, number>)
288291
const allNumeric = entries.every(([k]) => /^\d+$/.test(k))
289292
if (allNumeric) { out[version] = paramObj; continue }
290-
entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
291293
const positional: Record<string, number> = {}
292294
entries.forEach(([_n, val], idx) => { positional[String(idx)] = val })
293295
out[version] = positional
@@ -311,7 +313,10 @@ function patchProtocolParamsConwaySafe(bf: any, url: string, key: string): void
311313
collateralPercentage: parseInt(r.collateral_percent ?? '150'),
312314
maxCollateralInputs: parseInt(r.max_collateral_inputs ?? '3'),
313315
minFeeRefScriptCostPerByte: parseInt(r.min_fee_ref_script_cost_per_byte ?? '15'),
314-
costModels: normalizeCostModels(r.cost_models),
316+
// `cost_models_raw` is Blockfrost's positional array form, already in
317+
// canonical Plutus order — required for a correct script-integrity hash
318+
// (and thus tx submission). Fall back to the named object if absent.
319+
costModels: r.cost_models_raw ?? normalizeCostModels(r.cost_models),
315320
}
316321
return cached
317322
}

0 commit comments

Comments
 (0)