Important Defaults
Non-obvious behaviors you should know about before building
Important Defaults
Evolution SDK ships with sensible defaults that handle complexity for you. But if you don't know they exist, they can be surprising. This page documents the most important implicit behaviors.
Transaction Builder Defaults
Automatic Coin Selection
When you call .build(), the SDK automatically selects UTxOs from your wallet to cover the transaction's requirements. You don't need to manually pick inputs.
// You write this:
const tx = await client.newTx()
.payToAddress({ address, assets: Assets.fromLovelace(5_000_000n) })
.build()
// The builder automatically:
// 1. Queries your wallet's UTxOs
// 2. Selects the largest UTxOs first (largest-first algorithm)
// 3. Covers the payment amount + fees + min UTxO for changeOverride: Pass availableUtxos to .build() to provide your own UTxO set.
Automatic Fee Calculation
Fees are calculated automatically based on transaction size, script execution costs, and current protocol parameters. You never set fees manually.
Override: Use FeeValidation.assertValidFee() after building to verify fees are within expected bounds.
Automatic Collateral Selection
For transactions involving Plutus scripts, the builder automatically selects collateral UTxOs. Collateral is required by the protocol in case script execution fails.
Override: Not typically needed — the builder handles this correctly.
Automatic Script Evaluation
When you attach a script and provide a redeemer, the builder evaluates the script to compute execution units (memory + CPU). This happens during .build().
// You provide:
.collectFrom({ inputs: scriptUtxos, redeemer: Data.constr(0n, []) })
.attachScript({ script: validatorScript })
// The builder automatically:
// 1. Evaluates the script with the redeemer
// 2. Computes exact execution units (memory + CPU)
// 3. Sets execution budgets on the redeemer
// 4. Includes execution costs in fee calculationAutomatic Redeemer Indexing
After coin selection changes the input order, redeemer indices must be recalculated. The builder does this automatically — you never manually set redeemer indices.
Fresh State Per Build
Builders are safe to reuse. Each .build() call creates independent state — no contamination from previous builds.
const template = client.newTx()
.payToAddress({ address, assets })
// Safe: each build() creates independent state
const tx1 = await template.build()
const tx2 = await template.build() // No contamination from tx1Encoding Defaults
CBOR: Aiken-Compatible by Default
UPLC.applyParamsToScript() uses Aiken-compatible CBOR encoding (indefinite-length arrays/maps) by default. If you're using a different Plutus toolchain, you may need different options.
// Default: Aiken encoding
UPLC.applyParamsToScript(script, params)
// For CML-compatible encoding:
UPLC.applyParamsToScript(script, params, CBOR.CML_DATA_DEFAULT_OPTIONS)Scripts Are Double-CBOR Encoded
Compiled Plutus scripts from Aiken/Plutarch are typically double-CBOR encoded (flat bytes → CBOR bytes → CBOR bytes). The SDK handles this automatically, but if you're working with raw script bytes, be aware of the encoding level.
// Check encoding level
const level = UPLC.getCborEncodingLevel(scriptHex)
// "double" | "single" | "none"Provider Defaults
Protocol Parameters Fetched Automatically
The builder fetches protocol parameters from your provider during .build(). Parameters are used for fee calculation, min UTxO requirements, and cost model selection.
Network Is Set at Client Creation
The network (mainnet, preprod, preview) is fixed when you create the client. All operations use this network — there's no per-transaction network override.
import { preprod, mainnet, Client } from "@evolution-sdk/evolution"
// Network is locked to preprod for all operations
const client = Client.make(preprod).withBlockfrost({...})Error Defaults
Errors Are Tagged
All SDK errors have a _tag field for pattern matching. This is an Effect convention — use it instead of instanceof:
catch (e: any) {
if (e._tag === "EvaluationError") { /* script failed */ }
if (e._tag === "CoinSelectionError") { /* insufficient funds */ }
}Build Returns Promise by Default
.build() returns a Promise. For Effect-based error handling, use .buildEffect(). For Either-based, use .buildEither().
Next Steps
- Getting Started — Build your first transaction
- Common Patterns — Copy-paste recipes
- Error Handling — All error types and debugging
- Architecture — How the build pipeline works