Evolution SDK
Smart Contracts

Native Scripts

Time-locks, multi-sig, and simple minting policies without Plutus

Native Scripts

Native scripts are lightweight validators that don't require Plutus. They check simple conditions — key signatures, time constraints, or combinations of both. Use them for time-locked vesting, multi-sig wallets, and simple minting policies.

No script evaluation costs, no collateral required, smaller transactions.

Script Types

TypeWhat It ChecksUse Case
ScriptPubKeyA specific key signed the transactionSingle-signer authorization
InvalidBeforeTransaction is after a slotTime-locked release
InvalidHereafterTransaction is before a slotDeadline enforcement
ScriptAllALL sub-scripts passMulti-sig (all must sign)
ScriptAnyANY sub-script passesMulti-sig (one must sign)
ScriptNOfKN of K sub-scripts passM-of-N multi-sig

Building Native Scripts

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

// Single key requirement
const singleSigner = NativeScripts.makeScriptPubKey(
  Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de") // 28-byte key hash
)

// Time-lock: can't spend before slot 100000
const timeLock = NativeScripts.makeInvalidBefore(100000n)

// Deadline: can't spend after slot 200000
const deadline = NativeScripts.makeInvalidHereafter(200000n)

Multi-Sig: All Must Sign

Require ALL listed keys to sign the transaction:

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

const keyHash1 = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")
const keyHash2 = Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab")
const keyHash3 = Bytes.fromHex("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8")

// All three must sign
const multiSig = NativeScripts.makeScriptAll([
  NativeScripts.makeScriptPubKey(keyHash1),
  NativeScripts.makeScriptPubKey(keyHash2),
  NativeScripts.makeScriptPubKey(keyHash3),
])

Multi-Sig: Any Can Sign

Require ANY ONE of the listed keys:

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

const keyHash1 = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")
const keyHash2 = Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab")

// Either key can authorize
const anyOf = NativeScripts.makeScriptAny([
  NativeScripts.makeScriptPubKey(keyHash1),
  NativeScripts.makeScriptPubKey(keyHash2),
])

Multi-Sig: M-of-N

Require at least N of K keys to sign:

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

const keyHash1 = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")
const keyHash2 = Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab")
const keyHash3 = Bytes.fromHex("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8")

// 2-of-3 multi-sig
const twoOfThree = NativeScripts.makeScriptNOfK(2n, [
  NativeScripts.makeScriptPubKey(keyHash1),
  NativeScripts.makeScriptPubKey(keyHash2),
  NativeScripts.makeScriptPubKey(keyHash3),
])

Time-Locked Vesting

Combine a key requirement with a time constraint — funds can only be spent after a deadline:

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

const beneficiary = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")

// Can only spend after slot 50000000 AND with beneficiary's signature
const vestingScript = NativeScripts.makeScriptAll([
  NativeScripts.makeScriptPubKey(beneficiary),
  NativeScripts.makeInvalidBefore(50000000n),
])

Minting with Native Scripts

Native scripts work as minting policies — no Plutus, no redeemer, no collateral:

import { Assets, NativeScripts, Bytes, preprod, Client } from "@evolution-sdk/evolution"

const client = Client.make(preprod)
  .withBlockfrost({
    baseUrl: "https://cardano-preprod.blockfrost.io/api/v0",
    projectId: process.env.BLOCKFROST_API_KEY!
  })
  .withSeed({ mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 })

const myKeyHash = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")

// Simple minting policy: only my key can mint
const mintingPolicy = NativeScripts.makeScriptPubKey(myKeyHash)

// Wrap in NativeScript for the builder
const script = new NativeScripts.NativeScript({ script: mintingPolicy })

// The policy ID is the script hash — you'd compute this from the script CBOR
// For this example, use a placeholder (28 bytes = 56 hex chars)
declare const policyId: string // hash of the native script

let assets = Assets.fromLovelace(0n)
assets = Assets.addByHex(assets, policyId, "4d79546f6b656e", 1000n) // "MyToken"

// No redeemer needed for native scripts
const tx = await client
  .newTx()
  .mintAssets({ assets })
  .attachScript({ script })
  .build()

const signed = await tx.sign()
await signed.submit()

Utility Functions

import { NativeScripts, Bytes } from "@evolution-sdk/evolution"

const keyHash1 = Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")
const keyHash2 = Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab")

const script = NativeScripts.makeScriptAll([
  NativeScripts.makeScriptPubKey(keyHash1),
  NativeScripts.makeScriptPubKey(keyHash2),
  NativeScripts.makeInvalidBefore(100000n),
])

// Count minimum required signers
const count = NativeScripts.countRequiredSigners(script)
// count = 2 (two key requirements in ScriptAll)

// Extract all key hashes from the script tree
const keyHashes = NativeScripts.extractKeyHashes(script)
// keyHashes = [keyHash1, keyHash2]

// Serialize to CBOR
const cbor = NativeScripts.toCBORHex(
  new NativeScripts.NativeScript({ script })
)

// Convert to JSON (for debugging or interop)
const json = NativeScripts.toJSON(script)

Native Scripts vs Plutus Scripts

Native ScriptsPlutus Scripts
ComplexitySimple conditions onlyArbitrary logic
Execution costNone (free)Memory + CPU units
CollateralNot requiredRequired
Transaction sizeSmallerLarger (script included)
RedeemerNot neededRequired
Use casesMulti-sig, time-locks, simple mintingDeFi, auctions, complex validation

Next Steps