Evolution SDK
Smart Contracts

Blueprint Codegen

Generate type-safe TypeScript schemas from CIP-57 Plutus Blueprints

Blueprint Codegen

When you compile a smart contract with Aiken (or another toolchain that outputs CIP-57 Plutus Blueprints), you get a plutus.json file describing your validators, their parameters, datums, and redeemers.

Evolution SDK can generate type-safe TSchema definitions from this blueprint, giving you compile-time type safety for all on-chain data interactions.

Quick Start

import { Blueprint } from "@evolution-sdk/evolution"
import * as fs from "fs"

// 1. Load your blueprint
const blueprint = JSON.parse(fs.readFileSync("plutus.json", "utf-8"))

// 2. Generate TypeScript code
const code = Blueprint.Codegen.generateTypeScript(blueprint)

// 3. Write to file
fs.writeFileSync("src/contract-types.ts", code)

The generated file includes:

  • TSchema definitions for every type in your blueprint
  • Validator metadata (script hashes, parameter schemas)
  • Full type safety for datums, redeemers, and parameters

Configuration

Pass a config object to customize code generation:

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

declare const blueprint: any

const code = Blueprint.Codegen.generateTypeScript(blueprint, {
  // How to generate Option<T>
  optionStyle: "NullOr",     // "NullOr" | "UndefinedOr" | "Union"

  // How to generate union types
  unionStyle: "Variant",     // "Variant" | "Struct" | "TaggedStruct"

  // How to generate empty constructors
  emptyConstructorStyle: "Literal", // "Literal" | "Struct"

  // Module organization
  moduleStrategy: "flat",    // "flat" | "namespaced"

  // Include constructor index in TSchema
  includeIndex: false,

  // Field naming for unnamed constructor fields
  fieldNaming: {
    singleFieldName: "value",
    multiFieldPattern: "field{index}",
  },

  // Import paths for generated code
  imports: {
    data: 'import { Data } from "@evolution-sdk/evolution"',
    tschema: 'import { TSchema } from "@evolution-sdk/evolution"',
    schema: 'import { Schema } from "effect"',
  },

  indent: "  ",
})

Option Styles

Controls how Option<T> types from your blueprint are represented:

StyleGenerated CodeUse When
"NullOr"TSchema.NullOr(T)Default, idiomatic TypeScript
"UndefinedOr"TSchema.UndefinedOr(T)Prefer undefined over null
"Union"TSchema.Union(TaggedStruct("Some", ...), TaggedStruct("None", ...))Need explicit pattern matching

Union Styles

Controls how union types with named constructors are generated:

StyleGenerated CodeUse When
"Variant"TSchema.Variant({ Tag1: {...}, Tag2: {...} })Default, compact
"Struct"TSchema.Union(TSchema.Struct(...), ...)Verbose, same encoding
"TaggedStruct"TSchema.Union(TaggedStruct("Tag1", ...), ...)Effect _tag style

Module Strategy

Controls how types are organized in the generated file:

  • "flat" (default) — All types at top level: CardanoAddressCredential, CardanoAssetsPolicyId
  • "namespaced" — Nested TypeScript namespaces: Cardano.Address.Credential, Cardano.Assets.PolicyId

Custom Field Names

When your blueprint has constructors with unnamed fields, you can provide explicit names:

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

declare const blueprint: any

const code = Blueprint.Codegen.generateTypeScript(blueprint, {
  optionStyle: "NullOr",
  unionStyle: "Variant",
  emptyConstructorStyle: "Literal",
  moduleStrategy: "flat",
  includeIndex: false,
  useRelativeRefs: true,
  fieldNaming: {
    singleFieldName: "value",
    multiFieldPattern: "field{index}",
  },
  imports: {
    data: 'import { Data } from "@evolution-sdk/evolution"',
    tschema: 'import { TSchema } from "@evolution-sdk/evolution"',
    schema: 'import { Schema } from "effect"',
  },
  indent: "  ",
  variantFieldNames: {
    "Credential.VerificationKey": ["hash"],
    "Credential.Script": ["hash"],
  },
})

What Gets Generated

For a blueprint with validators and type definitions, the codegen produces:

Schema definitions — TSchema types for every definition in the blueprint:

// Generated from Blueprint: my-contract
import { Data } from "@evolution-sdk/evolution"
import { TSchema } from "@evolution-sdk/evolution"

export const PlutusData = TSchema.PlutusData

export const ByteArray = TSchema.ByteArray
export const Int = TSchema.Integer

export const Credential = TSchema.Variant({
  VerificationKey: { hash: TSchema.ByteArray },
  Script: { hash: TSchema.ByteArray },
})

Validator metadata — Script info for each validator:

export const myValidator = {
  title: "my_validator",
  compiledCode: "5907...",
  hash: "abc123...",
}

Using Generated Types

Once generated, use the schemas with Data.withSchema for type-safe encoding/decoding:

import { Data } from "@evolution-sdk/evolution"
// Import from your generated file
// import { Credential, myValidator } from "./contract-types"

declare const Credential: any
declare const myValidator: { compiledCode: string }

const CredentialCodec = Data.withSchema(Credential)

// Type-safe datum creation
const datum = CredentialCodec.toData({
  VerificationKey: {
    hash: new Uint8Array(28), // Type error if wrong shape
  },
})

End-to-End Workflow

  1. Compile your smart contract (e.g., aiken build)
  2. Generate types: run the codegen against plutus.json
  3. Import generated schemas in your app
  4. Use with Data.withSchema for type-safe transaction building
import { Blueprint } from "@evolution-sdk/evolution"
import * as fs from "fs"

// Build step: generate types from blueprint
const blueprint = JSON.parse(fs.readFileSync("plutus.json", "utf-8"))
const code = Blueprint.Codegen.generateTypeScript(blueprint)
fs.writeFileSync("src/generated/contract-types.ts", code)

Then in your application code, import and use the generated types with full type safety.

Next Steps