Skip to main content

Validators

Helios validator scripts have a function called main that returns a boolean (true or false), or void (()).

For a spending validator, main takes two arguments:

  • Datum: data stored on-chain that is linked to the locked UTxO (not avaiable for minting/staking scripts)
  • Redeemer: data specified by the user attempting to spend the locked UTxO

The datum and the redeemer can be of any type, except functions.

The structure of a spending validator script looks as follows:

spending my_validator

import { tx } from ScriptContext

func main(datum: MyDatum, redeemer: MyRedeemer) -> Bool {
...
}

The validator header contains the script purpose.

Script purpose

In Helios all scripts start with a script purpose, followed by the name of the script. There are 6 script purposes:

  • spending
  • minting
  • staking
  • testing
  • mixed
  • module

On this page we are only concerned with the spending script purpose:

spending my_validator

...

module is covered in the next section.

Note: the name of each Helios source is registered in the global scope, so these names can't be used by statements, nor for the lhs of assignments. So eg. the entrypoint script can't be named main as that would conflict with the entrypoint function.

Datum

Each UTxO locked at a script address will also have an associated datum. The script can choose to use the datum as part of the spending validation, or it can choose to ignore the datum using an underscore (_) if it is irrelevant.

The datum can have any type.

Redeemer

Each UTxO used as an input for a transaction also has a redeemer attached. This is data specified by the user attempting to spend that UTxO. The script can again choose to use or ignore the redeemer using an underscore (_) if it is irrelevant to the validation.

The redeemer can have any type.

Script context

Most of the data needed for writing useful validators is contained in the tx field of the ScriptContext, which must be imported.

spending my_validator

import { tx } from ScriptContext

main function

The main function of a validator script accepts one or two arguments and returns a Bool or ().

spending my_validator

...

// redeemer is ignored in this example
func main(datum: MyDatum, _) -> Bool {
...
}

Parameters

Parameterizing validators allows dApp developers to create separate instances of a Helios program.

In Helios, this is done by re-binding one or more top-level const PARAM_NAME = ... declarations.

After re-binding any const parameters to a different value, the resulting program will have a different contract address.

Example

In this example OWNER is a parameter.

spending my_validator

const OWNER = PubKeyHash::new(#)

func main(_, _, ctx: ScriptContext) -> Bool {
ctx.tx.is_signed_by(OWNER)
}

The parameter can be changed before compiling to the final Uplc format:

const program = helios.Program.new(src);

program.parameters.OWNER = new helios.PubKeyHash("...");

const uplcProgram = program.compile();

Many Helios API types can be used when rebinding the parameters. Also the user-defined types are available through program.types. Besides using Helios API types, Javascript primitive objects (i.e. JSON-like) can be used to re-bind a parameter in some cases.

Contrast with Datum

Attaching Datum data structures to specific UTxOs is another way that a validator or other program can have varying behavior.

Using Datum causes those explicit details to be included in UTxOs (and/or in transactions consuming them). Transactions spending the UTxOs held at the same script address can each access and use those various Datum details. Noteably, any interested party can trivially query to discover all the various UTxOs held at a single contract address.

By contrast, two different instances of a parameterized contract, otherwise identical, will have separate addresses where UTxOs can be held. UTxO's don't need to explicitly contain the parameter values.

Querying for UTxO's in separate instances of a parameterized contract is also possible, but requires the interested party to have sufficient knowledge of those separate instance addresses, or other publicly-visible attributes of the target transactions.

Note that any parameterized contract can also use per-UTxO Datum values, as needed.

Example validator: always_succeeds

This basic validator allows locked UTxOs to be spent any way the user wants:

spending always_succeeds

func main(_, _) -> Bool {
true
}

You must use an underscore (_) for unused arguments. In this case all three arguments of main are ignored by using an underscore.