Evolution SDK
Encoding

TSchema

Type-safe schema definitions for Plutus data structures

TSchema

TSchema wraps Effect Schema with Plutus-specific encoding rules, giving you compile-time type safety and automatic CBOR serialization for smart contract data structures.

Define your schema once, get TypeScript types and CBOR encoding automatically.

Why TSchema?

Without TSchema (manual PlutusData):

  • Easy to mismatch types between TypeScript and on-chain
  • No compile-time validation
  • Manual CBOR encoding prone to errors
  • Field order mistakes cause validator failures

With TSchema:

  • Types inferred from schema automatically
  • Compiler catches type mismatches
  • Correct CBOR encoding guaranteed
  • Field order enforced by structure

Quick Start

Define a schema, extract types, create codec:

import {  } from "@evolution-sdk/evolution"

// Define schema
const  = ..({
  : ..,
  : ..
})

// Extract TypeScript type
type  = typeof .
// {
//   transaction_id: Uint8Array
//   output_index: bigint  
// }

// Create codec
const  = ..()

// Use it
const :  = {
  : ..("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"),
  : 0n
}

const  = .()
const  = .()

The Core Schemas

ByteArray

For hashes, policy IDs, addresses, and binary data:

import {  } from "@evolution-sdk/evolution"

const  = ..

type  = typeof .
// Uint8Array

const :  = ..(
  "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
)

Integer

For amounts, quantities, timestamps, indices:

import {  } from "@evolution-sdk/evolution"

const  = ..

type  = typeof .
// bigint

const :  = 5000000n

Struct

For records with named fields (encoded as Plutus Constructor):

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : ..,
  : ..
})

type  = typeof .
// {
//   payment_credential: Uint8Array
//   stake_credential: Uint8Array
// }

const  = ..()

const :  = {
  : ..("abc123def456abc123def456abc123def456abc123def456abc123de"),
  : ..("def456abc123def456abc123def456abc123def456abc123def456ab")
}

const  = .()

Variant

For sum types (multiple possible constructors):

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : {
    : ..
  },
  : {
    : ..
  }
})

type  = typeof .
// | { VerificationKey: { hash: Uint8Array } }
// | { Script: { hash: Uint8Array } }

const  = ..()

// Verification key credential
const :  = {
  : {
    : ..("abc123def456abc123def456abc123def456abc123def456abc123de")
  }
}

// Script credential
const :  = {
  : {
    : ..("def456abc123def456abc123def456abc123def456abc123def456ab")
  }
}

const  = .()
const  = .()

Array

For lists of values:

import {  } from "@evolution-sdk/evolution"

const  = ..(..)

type  = typeof .
// ReadonlyArray<bigint>

const  = ..()

const :  = [100n, 200n, 300n]
const  = .()

Map

For key-value mappings:

import {  } from "@evolution-sdk/evolution"

const  = ..(
  .., // AssetName
  ..    // Quantity
)

type  = typeof .
// Map<Uint8Array, bigint>

const  = ..()

const :  = new ([
  [..("546f6b656e"), 100n], // "Token" in hex
  [..("4e4654"), 1n]        // "NFT" in hex
])

const  = .()

UndefinedOr

For optional fields (Maybe pattern):

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : ..,
  : ..(..)
})

type  = typeof .
// {
//   name: Uint8Array
//   nickname: Uint8Array | undefined
// }

const  = ..()

const :  = {
  : ..("416c696365"), // "Alice"
  : 
}

const :  = {
  : ..("426f62"), // "Bob"
  : ..("426f62627920") // "Bobby"
}

Creating Codecs

Use Data.withSchema() to create a codec from any schema:

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : ..,
  : ..
})

const  = ..()

// Codec provides:
// - toData(value) → PlutusData
// - fromData(data) → value
// - toCBORHex(value) → string
// - toCBORBytes(value) → Uint8Array
// - fromCBORHex(hex) → value
// - fromCBORBytes(bytes) → value

Real-World Examples

Payment Credential

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : {
    : ..
  },
  : {
    : ..
  }
})

export type  = typeof .

export const  = ..()

// Use in your app
const :  = {
  : {
    : ..("abc123def456abc123def456abc123def456abc123def456abc123de")
  }
}

const  = .()

Escrow Datum

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : ..,
  : ..,
  : ..
})

export type  = typeof .

export const  = ..()

// Create datum
const :  = {
  : ..("abc123def456abc123def456abc123def456abc123def456abc123de"),
  : 1735689600000n,
  : 10000000n
}

// Encode for transaction
const  = .()

Multi-Action Redeemer

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : {},
  : {},
  : {
    : ..,
    : ..
  }
})

export type  = typeof .

export const  = ..()

// Different actions
const :  = { : {} }
const :  = { : {} }
const :  = {
  : {
    : ..("def456abc123def456abc123def456abc123def456abc123def456ab"),
    : 1735776000000n
  }
}

const  = .()

CIP-68 Metadata Datum

For CIP-68 metadata with arbitrary PlutusData fields, you can build the schema manually or use the pre-built types from the plutus directory. See the Plutus Types documentation for ready-to-use CIP-68 schemas.

Nested Schemas

Schemas compose naturally:

import {  } from "@evolution-sdk/evolution"

// Base schemas
const  = ..({
  : { : .. },
  : { : .. }
})

const  = ..({
  : { :  },
  : {
    : ..,
    : ..,
    : ..
  }
})

// Composed schema
const  = ..({
  : ,
  : ..()
})

export type  = typeof .

export const  = ..()

Type Extraction

Get TypeScript types from any schema:

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : ..,
  : ..,
  : ..(..)
})

// Extract type
type  = typeof .
// {
//   id: Uint8Array
//   amount: bigint
//   metadata: Uint8Array | undefined
// }

// Use in functions
function (: ) {
  .("Processing amount:", .)
}

Schema vs Direct PlutusData

ApproachType SafetyValidationCBOR EncodingUse When
TSchema✅ Compile-time✅ Automatic✅ Guaranteed correctProduction smart contracts
Direct Data❌ Runtime only❌ Manual⚠️ ManualPrototyping, debugging, tools

Rule of thumb: Use TSchema for all production smart contract integration.

Best Practices

Define schemas once: Create a schema for each datum/redeemer type and reuse it.

Match validator exactly: Your TSchema definitions must match your Plutus validator types exactly, including field names and order.

Use type extraction: Let TypeScript infer types from schemas—don't duplicate type definitions.

Test round-trips: Always verify encode → decode returns the original value.

Export types and codecs: Make both the type and codec available:

import {  } from "@evolution-sdk/evolution"

const  = ..({
  : ..
})

export type  = typeof .
export const  = ..()

Next Steps