---
name: local-development
description: Use when developing or testing Scalus smart contracts with the local Emulator and TxBuilder. Covers how to create an Emulator provider, submit transactions, and test negative validator cases.
---

## Overview

When developing on-chain code, the Emulator is a local, single-node no-consensus implementation of
the Cardano ledger. It includes phase 1 & 2 validation.

## File structure for testing a validator

Four files are typically involved:

- **Validator** — the on-chain logic (e.g. `MyValidator.scala`)
- **Contract** — compiles the validator into a `PlutusV3` value (e.g. `MyContract.scala`)
- **Transactions** — uses `TxBuilder` to create validator-specific transactions (e.g. `MyTransactions.scala`)
- **Test** — wires everything together using the Emulator (e.g. `MyValidatorTest.scala`)

### Contract file

```scala
private given Options = Options.release
lazy val MyContract = PlutusV3.compile(MyValidator.validate)
```

For parameterized validators, apply the parameter after compilation:

```scala
val applied = MyContract.withErrorTraces(myParam)   // applies param + enables traces
val script  = Script.PlutusV3(applied.program.cborByteString)
val scriptAddress = applied.address(network)
```

### Transactions file

`CardanoInfo` (aliased as `env`) carries network/protocol config and is provided by `ScalusTest`
via `TestUtil.testEnvironment`. Spending transactions need an evaluator.
`evaluator = PlutusScriptEvaluator.constMaxBudget(env)` skips script evaluation while the builder
balances the transaction. The Emulator still runs the scripts when you submit.

```scala
case class MyTransactions(
    env: CardanoInfo,
    evaluator: PlutusScriptEvaluator,
    contract: PlutusV3[Data => Unit]  
) {
    def script: Script.PlutusV3 = contract.script
    val scriptAddress: Address  = contract.address(env.network)

    // Necessary to have funds locked behind the contract under test
    def sendToScript(utxos: Utxos, datum: MyDatum, sponsor: Address, signer: TransactionSigner): Transaction =
        TxBuilder(env)
            .spend(Utxo(utxos.head))
            .payTo(scriptAddress, Value.ada(10), datum) // or pass the ada amount in a parameter
            .complete(availableUtxos = utxos, sponsor = sponsor)
            .sign(signer)
            .transaction

    def spendFromScript(
        utxos: Utxos,
        scriptUtxo: Utxo,
        redeemer: MyRedeemer,
        signerPkh: AddrKeyHash,
        sponsor: Address,
        signer: TransactionSigner
    ): Transaction =
        TxBuilder(env, evaluator)
            .spend(scriptUtxo, redeemer, script)
            .spend(Utxo(utxos.head))
            .payTo(sponsor, scriptUtxo.output.value)
            .complete(availableUtxos = utxos, sponsor = sponsor)
            .sign(signer)
            .transaction
}
```

### Test file

```scala
// ScalusTest provides: PlutusVM, Alice/Bob/Eve/Charles/Dave parties (via Party.*),
//                       cardanoEnv, genesisHash, assertScriptFail, and ArbitraryInstances
class MyValidatorTest extends AnyFunSuite, ScalusTest {
    import MyValidatorTest.{*, given}

    test("valid redeemer succeeds") {
        val provider = createProvider()
        // ... build and submit transactions
    }
}

object MyValidatorTest extends ScalusTest {
    private given env: CardanoInfo = TestUtil.testEnvironment
    private val compiledContract   = MyContract.withErrorTraces
    private val scriptAddress      = compiledContract.address(env.network)

    private val txCreator = MyTransactions(
        env       = env,
        evaluator = PlutusScriptEvaluator.constMaxBudget(env),
        contract  = compiledContract
    )

    private def createProvider(): Emulator = {
        // genesisHash is a zero-hash sentinel used as the "genesis" input
        val initialUtxos = Map(
            Input(genesisHash, 0) -> Output(Alice.address, Value.ada(5000)),
            Input(genesisHash, 1) -> Output(Alice.address, Value.ada(5000)),
            Input(genesisHash, 2) -> Output(Bob.address,   Value.ada(5000)),
        )
        Emulator(initialUtxos = initialUtxos, initialContext = Context.testMainnet())
    }
}
```

## Submitting a transaction

```scala
provider.submit(tx).await() match {
    case Left(value) => fail(s"Transaction failed: $value")
    case Right(_)    => ()
}
```

## Testing negative cases (validator expected to fail)

Override `PlutusScriptEvaluator` so `TxBuilder` won't reject the transaction during construction:

```scala
val evaluator = PlutusScriptEvaluator.constMaxBudget(env)

TxBuilder(env, evaluator)
    .spend(scriptUtxo, badRedeemer, script, Set(signerPkh))
    ...
    .complete(...) // or `build`
    .sign(signer)
    .transaction
// Then submit to the Emulator and assert Left(SubmitError.ScriptFailure(...))
```
