Skip to Content
Scalus Club is now open! Join us to get an early access to new features 🎉
DocumentationTestingJS/TS Emulator

Cardano Emulator for JavaScript and TypeScript

The Scalus Emulator — an in-memory Cardano node with full transaction validation and Plutus script execution — is also available as an npm package for JavaScript and TypeScript.

What It Validates

Scalus implements a ledger framework that emulates an in-memory node without consensus. Transactions are validated as if submitted to a real node:

ValidationDescription
UTxO RulesInput existence, double-spend prevention, value conservation
Plutus ScriptsV1, V2, V3 script execution with cost model evaluation
StakingStake registration, delegation, reward withdrawals
Native ScriptsMultisig and timelock validation
Fees & CollateralFee calculation, collateral handling on script failure
SignaturesWitness verification for required signers

The emulator runs the same ledger rules as the JVM version — Phase 1 (transaction structure) and Phase 2 (script execution) validation.

Installation

npm install scalus

Quick Start

import { Emulator, SlotConfig } from "scalus"; /* `withAddresses` creates an emulator where every specified address has a UTxO with the specified lovelace amount. */ const emulator = Emulator.withAddresses( ["addr_test1qr...", "addr_test1qp..."], SlotConfig.preview, 10_000_000 ); // Encode your transaction to CBOR with your favorite encoder and submit the result to the Scalus emulator. const result = emulator.submitTx(txCborBytes); if (result.isSuccess) { console.log(`Transaction submitted: ${result.txHash}`); } else { console.log(`Failed: ${result.error}`); if (result.logs) { console.log(`Script logs: ${result.logs.join("\n")}`); } }

Creating an Emulator

With Funded Addresses

const emulator = Emulator.withAddresses( [aliceAddress, bobAddress], SlotConfig.mainnet, BigInt(50_000_000_000) // 50 000 ADA );

With Custom UTxOs

For more control, provide CBOR-encoded UTxOs directly:

// UTxOs as CBOR-encoded Map<TransactionInput, TransactionOutput> const emulator = new Emulator(initialUtxosCbor, SlotConfig.preview);

Slot Configuration

Use the built-in configurations for time conversion:

SlotConfig.mainnet // Mainnet (Shelley era start) SlotConfig.preview // Preview testnet SlotConfig.preprod // Preprod testnet // Or custom configuration const custom = new SlotConfig(zeroTime, zeroSlot, slotLength);

API Reference

submitTx(txCborBytes: Uint8Array): SubmitResult

Submit a CBOR-encoded transaction. Returns:

interface SubmitResult { isSuccess: boolean; txHash?: string; // On success error?: string; // On failure logs?: string[]; // Script trace logs on failure }

getUtxosForAddress(addressBech32: string): Uint8Array[]

Get UTxOs for an address. Each entry is a CBOR-encoded Map<Input, Output>:

const utxos = emulator.getUtxosForAddress(aliceAddress); // Decode with cbor-x or similar

getAllUtxos(): Uint8Array[]

Get all UTxOs in the emulator state.

getUtxosCbor(): Uint8Array

Get the entire UTxO set as a single CBOR-encoded map.

setSlot(slot: number): void

Advance the current slot for time-based validation:

emulator.setSlot(1000);

snapshot(): Emulator

Create a point-in-time copy:

const checkpoint = emulator.snapshot(); // ... submit transactions ... // checkpoint still has original state

Example: Simple Payment

import { Emulator, SlotConfig } from "scalus"; import { Decoder } from "cbor-x"; const decoder = new Decoder({ mapsAsObjects: false }); function hexToBytes(hex: string): Uint8Array { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16); } return bytes; } // Alice has 100 ADA, sends 25 ADA to Bob const initialUtxosCborHex = "a282582000000000000000000000000000000000000000000000000000000000000000000082581d60c8c47610a36034aac6fc58848bdae5c278d994ff502c05455e3b3ee81a05f5e10082582000000000000000000000000000000000000000000000000000000000000000000182581d60c8c47610a36034aac6fc58848bdae5c278d994ff502c05455e3b3ee81a00989680"; const emulator = new Emulator( hexToBytes(initialUtxosCborHex), SlotConfig.preview ); // Build transaction with your preferred library, get CBOR bytes const txCborBytes = buildTransaction(); // Your transaction builder const result = emulator.submitTx(txCborBytes); console.log(result.isSuccess ? `Success: ${result.txHash}` : `Error: ${result.error}`);

Working with UTxOs

The emulator returns UTxOs as CBOR-encoded data. Decode with cbor-x:

import { Decoder } from "cbor-x"; const decoder = new Decoder({ mapsAsObjects: false }); const utxos = emulator.getUtxosForAddress(address); for (const utxoCbor of utxos) { const decoded = decoder.decode(utxoCbor); // decoded is Map<TransactionInput, TransactionOutput> for (const [input, output] of decoded) { console.log("Input:", input); console.log("Output:", output); } }

Integration with Lucid

Use the emulator’s UTxO state with Lucid Evolution for transaction building:

// Get UTxOs from emulator const utxosCbor = emulator.getUtxosCbor(); // Build transaction with Lucid const tx = await lucid.newTx() .pay.ToAddress(bobAddress, { lovelace: 25_000_000n }) .complete(); const signedTx = await tx.sign.withWallet().complete(); const txCbor = signedTx.toCBOR(); // Submit to emulator instead of network const result = emulator.submitTx(hexToBytes(txCbor));

Script Evaluation

For evaluating Plutus scripts without submitting transactions, use evalPlutusScripts:

import { Scalus, SlotConfig } from "scalus"; const redeemers = Scalus.evalPlutusScripts( txCborBytes, utxoCborBytes, SlotConfig.preview, costModels // [v1CostModel, v2CostModel, v3CostModel] ); for (const redeemer of redeemers) { console.log(`${redeemer.tag}[${redeemer.index}]: ${redeemer.budget.memory} mem, ${redeemer.budget.steps} steps`); }

See Also

Last updated on