Summary
Any transaction containing a Plutus V3 mint submitted to preprod or preview fails with PPViewHashesDontMatch. Mesh's serializer always feeds the hardcoded DEFAULT_V[123]_COST_MODEL_LIST from @meshsdk/common to the script integrity hash, with no way for the actual on-chain cost models to flow through. The hardcoded values match Cardano mainnet today (297 V3 entries), but preprod and preview have shipped an expanded model (350 V3 entries) ahead of the upcoming PV11 hard fork. Mainnet still hashes correctly today, but the same break will hit mainnet once PV11 is enacted on chain.
Steps to reproduce the bug
Any code path that builds a Plutus V3 mint with MeshTxBuilder and submits it via a BlockfrostProvider (preprod or preview) reproduces this. The mint UTxO selection, datum and redeemer construction all succeed; the failure happens at submit_tx after the Cardano node validates the script integrity hash.
const txBuilder = new MeshTxBuilder({ fetcher, submitter, verbose: true });
txBuilder
.mintPlutusScriptV3()
.mint(quantity, policyId, userTokenName)
// ... datum, redeemer, collateral, change address ...
const unsignedTx = await txBuilder.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx); // ← fails here
Actual Result
Submission returns HTTP 400 from Blockfrost with:
ConwayUtxowFailure (PPViewHashesDontMatch Mismatch (RelEQ) {
supplied: SJust (SafeHash "11bb34b7d88c308fd3ef46f2d8d149921b47aa74ea9f9d2e6f9452323c94d4b6"),
expected: SJust (SafeHash "be800d742c17bb9145008762618d0248e5d47aa90c702539721b63219a88d6d2")
})
The supplied/expected hashes are stable across multiple submission attempts and across different mint assetNames — confirming the integrity hash computation is deterministically wrong, not a transient state issue.
Expected Result
Mesh should compute the script integrity hash from the cost models actually in effect on the target network — typically by deriving them from the protocol parameters supplied to the serializer — instead of from hardcoded defaults. Submission should succeed (or fail for an actual transaction-level reason, not script integrity).
SDK version
1.9.0-beta.102
Environment type
Environment details
- @meshsdk/core: 1.9.0-beta.102 (also confirmed on 1.9.0-beta.98)
- @meshsdk/core-csl: same versions
- Node: 22.x
- Provider: BlockfrostProvider (preprod project key)
- Cardano preprod: epoch 289, protocol version 10.0
Additional context
Root cause
Mesh's serializer builds the script integrity hash using hardcoded cost-model arrays from @meshsdk/common, ignoring whatever cost models the protocol parameters actually contain:
packages/mesh-common/src/constants/cost-models.ts — declares DEFAULT_V[123]_COST_MODEL_LIST (currently 166 / 175 / 297 entries, matching mainnet).
packages/mesh-core-cst/src/serializer/index.ts (around L1531-1539 on main) — CardanoSDKSerializerCore always calls Serialization.CostModel.newPlutusV[123](DEFAULT_VX_COST_MODEL_LIST), with no path for this.protocolParams to override them.
As a result, a Plutus V3 tx built against preprod/preview (which serve 350 V3 entries) gets hashed with the 297-entry mainnet model. The Cardano node recomputes the hash from current params and rejects the submission as PPViewHashesDontMatch. Mainnet still hashes correctly today purely because its on-chain cost model still matches the hardcoded defaults.
Evidence — cost model sizes by network
Verified independently via Koios and Blockfrost; both sources agree:
| Network |
PlutusV1 |
PlutusV2 |
PlutusV3 |
Protocol |
Affected? |
| Mainnet |
166 |
175 |
297 |
10.0 |
No (works today) |
| Preprod |
332 |
332 |
350 |
10.0 |
Yes — this issue |
| Preview |
332 |
332 |
350 |
11.0 |
Yes — this issue |
Reproduction of cost model counts:
curl -s "https://api.koios.rest/api/v1/cli_protocol_params" \
| jq '.[0].costModels | map_values(length)'
# { "PlutusV1": 166, "PlutusV2": 175, "PlutusV3": 297 }
curl -s "https://preprod.koios.rest/api/v1/cli_protocol_params" \
| jq '.[0].costModels | map_values(length)'
# { "PlutusV1": 332, "PlutusV2": 332, "PlutusV3": 350 }
curl -s "https://preview.koios.rest/api/v1/cli_protocol_params" \
| jq '.[0].costModels | map_values(length)'
# { "PlutusV1": 332, "PlutusV2": 332, "PlutusV3": 350 }
The 53-entry delta on V3 corresponds to new builtins introduced for the PV11 fork (Value type ops, array helpers, expModInteger, etc.).
This will affect mainnet imminently
The new cost model isn't preprod-only — it's the staging for the PV11 intra-era hard fork, which is already supported by cardano-node 11.0.1 and pending governance enactment on mainnet.
From the cardano-node 11.0.1 release notes:
"Node 11.0.1 is the first release to support the PV11 intra-era hard fork, enabling the upgrade to protocol version 11 once the hard fork governance action has been voted on by the SPOs, DReps, and the Constitutional Committee and enacted on chain."
Source: https://github.com/IntersectMBO/cardano-node/releases/tag/11.0.1
Once the governance action passes on mainnet, mainnet's PlutusV3 cost model will also expand to 350 entries and every Mesh-using project minting with Plutus V3 scripts on mainnet will be blocked by this same PPViewHashesDontMatch error.
Workarounds attempted
- Downgrade
@meshsdk/core to 1.9.0-beta.98 → fails with identical supplied/expected hashes, confirming the issue is not a recent regression but pre-existing in all current 1.9.x betas.
- Downgrade to
1.8.14 → fails to compile (serializeData is not exported in 1.8.x).
- Mainnet → works correctly with
1.9.0-beta.102 today, since mainnet still serves the 297-entry V3 cost model — but this workaround expires when PV11 is enacted.
Verified the proposed fix works on chain
To confirm the diagnosis, I ran a local patch that fetches live cost models from a provider and feeds them to Serialization.CostModel.newPlutusV[123] instead of the hardcoded defaults. With that patch applied, the same CIP-68 mint that previously failed now submits successfully:
That confirms three things:
- Mesh's
language_views CBOR serialization is correct — it just needs to be fed the right cost models.
- The canonical Plutus order returned by Koios (flat array form) matches what Cardano expects in the integrity hash.
- The fix doesn't require any WASM rebuild or change to
whisky-js; it's a pure TypeScript change in the serializer wrapper.
Summary
Any transaction containing a Plutus V3 mint submitted to preprod or preview fails with
PPViewHashesDontMatch. Mesh's serializer always feeds the hardcodedDEFAULT_V[123]_COST_MODEL_LISTfrom@meshsdk/commonto the script integrity hash, with no way for the actual on-chain cost models to flow through. The hardcoded values match Cardano mainnet today (297 V3 entries), but preprod and preview have shipped an expanded model (350 V3 entries) ahead of the upcoming PV11 hard fork. Mainnet still hashes correctly today, but the same break will hit mainnet once PV11 is enacted on chain.Steps to reproduce the bug
Any code path that builds a Plutus V3 mint with
MeshTxBuilderand submits it via aBlockfrostProvider(preprod or preview) reproduces this. The mint UTxO selection, datum and redeemer construction all succeed; the failure happens atsubmit_txafter the Cardano node validates the script integrity hash.Actual Result
Submission returns HTTP 400 from Blockfrost with:
The supplied/expected hashes are stable across multiple submission attempts and across different mint assetNames — confirming the integrity hash computation is deterministically wrong, not a transient state issue.
Expected Result
Mesh should compute the script integrity hash from the cost models actually in effect on the target network — typically by deriving them from the protocol parameters supplied to the serializer — instead of from hardcoded defaults. Submission should succeed (or fail for an actual transaction-level reason, not script integrity).
SDK version
1.9.0-beta.102
Environment type
Environment details
Additional context
Root cause
Mesh's serializer builds the script integrity hash using hardcoded cost-model arrays from
@meshsdk/common, ignoring whatever cost models the protocol parameters actually contain:packages/mesh-common/src/constants/cost-models.ts— declaresDEFAULT_V[123]_COST_MODEL_LIST(currently 166 / 175 / 297 entries, matching mainnet).packages/mesh-core-cst/src/serializer/index.ts(around L1531-1539 onmain) —CardanoSDKSerializerCorealways callsSerialization.CostModel.newPlutusV[123](DEFAULT_VX_COST_MODEL_LIST), with no path forthis.protocolParamsto override them.As a result, a Plutus V3 tx built against preprod/preview (which serve 350 V3 entries) gets hashed with the 297-entry mainnet model. The Cardano node recomputes the hash from current params and rejects the submission as
PPViewHashesDontMatch. Mainnet still hashes correctly today purely because its on-chain cost model still matches the hardcoded defaults.Evidence — cost model sizes by network
Verified independently via Koios and Blockfrost; both sources agree:
Reproduction of cost model counts:
The 53-entry delta on V3 corresponds to new builtins introduced for the PV11 fork (Value type ops, array helpers, expModInteger, etc.).
This will affect mainnet imminently
The new cost model isn't preprod-only — it's the staging for the PV11 intra-era hard fork, which is already supported by
cardano-node 11.0.1and pending governance enactment on mainnet.From the
cardano-node 11.0.1release notes:Source: https://github.com/IntersectMBO/cardano-node/releases/tag/11.0.1
Once the governance action passes on mainnet, mainnet's PlutusV3 cost model will also expand to 350 entries and every Mesh-using project minting with Plutus V3 scripts on mainnet will be blocked by this same
PPViewHashesDontMatcherror.Workarounds attempted
@meshsdk/coreto1.9.0-beta.98→ fails with identical supplied/expected hashes, confirming the issue is not a recent regression but pre-existing in all current 1.9.x betas.1.8.14→ fails to compile (serializeDatais not exported in 1.8.x).1.9.0-beta.102today, since mainnet still serves the 297-entry V3 cost model — but this workaround expires when PV11 is enacted.Verified the proposed fix works on chain
To confirm the diagnosis, I ran a local patch that fetches live cost models from a provider and feeds them to
Serialization.CostModel.newPlutusV[123]instead of the hardcoded defaults. With that patch applied, the same CIP-68 mint that previously failed now submits successfully:That confirms three things:
language_viewsCBOR serialization is correct — it just needs to be fed the right cost models.whisky-js; it's a pure TypeScript change in the serializer wrapper.