Skip to content

PPViewHashesDontMatch on Plutus V3 mints in preprod/preview after recent Cardano cost model expansion (will affect mainnet at PV11 hard fork) #824

@JoelDellamaggiore

Description

@JoelDellamaggiore

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

  • Node.js
  • Browser
  • Browser Extension
  • Other

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:

  1. Mesh's language_views CBOR serialization is correct — it just needs to be fed the right cost models.
  2. The canonical Plutus order returned by Koios (flat array form) matches what Cardano expects in the integrity hash.
  3. The fix doesn't require any WASM rebuild or change to whisky-js; it's a pure TypeScript change in the serializer wrapper.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions