Repository navigation
feat(tx-builder): evaluate with Scalus by default, through IEvaluator - #866
Merged
lisicky merged 5 commits intoNov 1, 2026
Merged
Conversation
Switch OfflineEvaluatorScalus from a per-call evalPlutusScripts to one Scalus TxEvaluator with its own ScriptCache. The evaluator is built on first use and rebuilt only when slotConfig or costModels change; the script cache survives rebuilds. UTxOs are passed as the [input, output] pairs TransactionUnspentOutput already encodes, so the hand-built CBOR map goes. A runtime that cannot import() Scalus (Jest without --experimental-vm-modules) gets an error that says so. MeshTxBuilder evaluates on every coin-selection round, so reuse pays off: on recorded Midgard requests, TxEvaluator with a ScriptCache takes 0.98x the time of Aiken WASM, and the cache alone saves about 17-25%.
`evaluator: "scalus"` makes MeshTxBuilder (and MeshTxBuilderV2) evaluate scripts locally with OfflineEvaluatorScalus, so a script transaction carries the execution units it really needs. It needs a fetcher. Without an evaluator the builder behaves as before: no evaluation and no extra fetches. Before each evaluation the builder gives the Scalus evaluator the network (the `network` option or setNetwork, else mainnet for an addr1 change address; a validity interval without known slot times is an error), the fetched cost models, and the UTxOs it holds for the transaction. Key inputs given only by address and value are fetched once per complete() for their chain data; their declared data is used only when the fetcher reports them not found. clone() passes `network` and keeps track of the Scalus evaluator, since coin selection evaluates on clones. OfflineEvaluatorScalus suggests another evaluator when Scalus cannot be imported.
Drop the `"scalus"` string variant of the `evaluator` option. A builder given a fetcher and no evaluator now evaluates locally with OfflineEvaluatorScalus, Mesh's default; `evaluator: null` turns evaluation off, and a clone never makes its own default. What the builder learns while completing a transaction (the network, the cost models it fetched) reaches any evaluator through the new optional `IEvaluator.configure`, and an evaluator that runs locally says so with `acceptsKnownUtxos` to get every UTxO the builder holds. The builder no longer treats one evaluator specially. OfflineEvaluatorScalus takes an optional network, is configured from a context, keeps a slot config given to its constructor, and checks the validity interval of the serialized transaction itself, before any fetch: a zero start the serializer leaves out no longer needs a network.
… evaluation inputs Evaluators that do not take known UTxOs get the fetched copy of an input given for evaluation when the builder has one, since a given copy may lack script bytes. This fixes the provider-calls test "fetches missing script bytes for a locally known Plutus reference UTxO", which failed on the base branch. Two tests met the new default: the balance fixture holds bare scripts in `scriptRef`, which Scalus cannot decode, so it opts out with `evaluator: null`; an OfflineEvaluatorScalus given to the builder now gets the builder's UTxOs and fetches nothing itself, which its test asserts. New tests: a given local evaluator gets every held UTxO; a given evaluator gets the fetched cost models; the evaluator is kept across reset(); configure() with nothing new keeps the TxEvaluator; getTransactionValidityInterval. Comments trimmed.
lisicky
added a commit
that referenced
this pull request
Nov 1, 2026
Resolves the conflicts with #860 (reusable Scalus TxEvaluator, which this branch carried as a copy) and with the Scalus 1.4.1 test fixes on main: - offline-evaluator-scalus.ts and its test keep this branch's version, which already holds #860's content plus the default-evaluator work (#862, #866). - offline-evaluator-scalus-cases.test.ts keeps its Scalus expectations. Main's edits were to the CSL evaluator test it was copied from, so that test (mesh-core-csl/test/offline-providers/evaluator.test.ts) is restored from main rather than staying deleted. - script-tx-fail / script-tx-fetcher tests import MeshTxBuilder from @meshsdk/transaction, as on main.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #862 (its commits are included unchanged, on the same base) and replaces its
evaluator: "scalus"option with a plainIEvaluatordefault. Opened as an alternative to #862's API; the rest of #862 (networkoption, declared key inputs, known UTxOs for a local evaluator) is kept.Why
evaluator?: IEvaluator | "scalus"makes the builder recognise one evaluator by identity: a privatescalusEvaluatorfield, clone bookkeeping to keep that identity, and a validity-interval check that lives in the builder although it is about the evaluator's slot config. Mesh's default evaluator should be an ordinaryIEvaluatorinstance, and whatever the builder knows that an evaluator could use should reach it through the interface, so any evaluator can take it.What changes
MeshTxBuilderOptions.evaluatorisIEvaluator | null. Left out, with afetcher, the builder evaluates locally withOfflineEvaluatorScalus, the default Mesh chooses (Scalus today).nullturns evaluation off: redeemers keep the units declared on them. Without a fetcher there is no default, as there is nothing to resolve inputs with.MeshTxBuilderV2inherits all of this.IEvaluatorgains two optional members.acceptsKnownUtxos: a local evaluator sets it to receive every UTxO the builder holds (held inputs, declared key inputs), whether or not they are on chain yet.configure(context): takes anEvaluationContextwith the network and the cost models the builder learned while completing. Remote providers implement neither and behave as before, with one improvement: an input given for evaluation is passed as its fetched copy when the builder has one, since a given copy may lack script bytes (utxosForEvaluation).OfflineEvaluatorScalustakes an optional network, implementsconfigure, keeps a slot config given to its constructor whatever network it is told, and checks the validity interval of the serialized transaction itself, before any fetch.getTransactionValidityIntervalis added to core-cst's transaction utils.MeshTxBuilderlosesscalusEvaluator,configureScalusEvaluatorand the"scalus"constructor branch.evaluateRedeemerscallsevaluator.configure?.()and passes known UTxOs whenacceptsKnownUtxosis set (utxosForScalusEvaluatoris nowutxosForLocalEvaluator).clone()passesevaluator ?? null, so a clone never makes a default of its own.Behaviour changes to be aware of
new MeshTxBuilder({ fetcher })that builds a transaction with a Plutus script witness now evaluates it with Scalus. Scalus loads on first evaluation (an ESM chunk, through a dynamicimport()), and inputs the builder does not hold are fetched once percomplete(). Under Jest this needs--experimental-vm-modules, which mesh-transaction, mesh-contract and mesh-scalus-emulator already run with. Five mesh-transaction tests that build Plutus transactions over anOfflineFetchergotevaluator: nullto keep testing with declared units.Tx evaluation failed: The transaction has a validity interval, but the network it is for is unknown ..., like other evaluation errors. It is checked on the serialized transaction:invalidBefore(0), which the serializer leaves out, no longer needs a known network (feat(tx-builder): opt-in Scalus evaluation with evaluator: "scalus" #862's test for it is changed accordingly).networkoption follows the builder to mainnet; a slot config given to the constructor is never replaced. It no longer fetches inputs the builder already holds.mesh-x402'sSpendContextdoc comment is updated; its code already passes an evaluator explicitly.Tests
scalus-evaluator.test.ts(mesh-transaction): the default is Scalus in both builders, none without a fetcher, shared with clones, off withevaluator: null; a given evaluator is left alone, or configured if it takes a context, or given every held UTxO if it is local; the evaluator is kept acrossreset(); network inference and the unknown-network errors; slot 0.offline-evaluator-scalus.test.ts(core-cst):configurewith a network, a pinned slot config and cost models version by version; the unknown-network error before any fetch; the TxEvaluator kept when nothing new is configured.test/utils/transaction.test.ts(core-cst):getTransactionValidityInterval, including a zero start.balance.test.ts"should balance even when outputs don't have lovelaces" (its fixture stores bare scripts inscriptRef, which Scalus cannot decode; it tests balancing, soevaluator: null) andprovider-calls.test.ts"offline evaluator fetches each transaction once across evaluations" (a givenOfflineEvaluatorScalusnow gets the builder's UTxOs and fetches nothing itself, which the test now asserts, with the builder's fetcher asked once per transaction).provider-calls.test.ts"fetches missing script bytes for a locally known Plutus reference UTxO" fails on the base branch and on feat(tx-builder): opt-in Scalus evaluation with evaluator: "scalus" #862 (Script not found);utxosForEvaluationabove fixes it.Verification
Run locally after
npm run build:mesh:git merge-treewith #861: no conflicts. CI does not run for PRs into this branch.