Skip to main content

Datums

Datums are the data payloads attached to UTxOs at script addresses. When you lock funds to a smart contract, the datum carries the state your validator needs — beneficiary addresses, deadlines, token quantities, or any structured data your contract logic requires.

Datum Options

Evolution SDK supports two ways to attach datums to outputs:

TypeDescriptionWhen to Use
Inline DatumData stored directly in the UTxORecommended for most use cases (Plutus V2+)
Datum HashOnly a hash stored on-chain; full datum provided when spendingLegacy Plutus V1 contracts

Inline Datums

Inline datums embed the full data in the output. The spending transaction can read it directly without needing to provide the datum separately:

import { Address, Assets, Data, InlineDatum, 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 tx = await client
.newTx()
.payToAddress({
address: Address.fromBech32("addr_test1wrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qnmqsyu"),
assets: Assets.fromLovelace(10_000_000n),
datum: new InlineDatum.InlineDatum({ data: Data.constr(0n, [5000000n, 1735689600000n]) })
})
.build()

Datum Hashes

Datum hashes store only a 32-byte hash in the output. The full datum must be provided when spending:

import { Address, Assets, Data, 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 datum = Data.constr(0n, [5000000n])
const datumHash = Data.toDatumHash(datum)

const tx = await client
.newTx()
.payToAddress({
address: Address.fromBech32("addr_test1wrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qnmqsyu"),
assets: Assets.fromLovelace(10_000_000n),
datum: datumHash
})
.build()

Type-Safe Datums with TSchema

For production contracts, define your datum structure with TSchema to get compile-time type checking and automatic CBOR encoding:

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

const EscrowDatumSchema = TSchema.Struct({
beneficiary: TSchema.ByteArray,
deadline: TSchema.Integer,
amount: TSchema.Integer
})

type EscrowDatum = typeof EscrowDatumSchema.Type
const EscrowDatumCodec = Data.withSchema(EscrowDatumSchema)

const datum: EscrowDatum = {
beneficiary: Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"),
deadline: 1735689600000n,
amount: 10_000_000n
}

const plutusData = EscrowDatumCodec.toData(datum)

Constructing Complex Datums

Variant Datums

For datums with multiple possible shapes:

import { Data, TSchema } from "@evolution-sdk/evolution"

const OrderDatumSchema = TSchema.Variant({
Buy: {
price: TSchema.Integer,
quantity: TSchema.Integer
},
Sell: {
price: TSchema.Integer,
quantity: TSchema.Integer,
min_fill: TSchema.Integer
},
Cancel: {}
})

type OrderDatum = typeof OrderDatumSchema.Type

const buyOrder: OrderDatum = {
Buy: { price: 1500000n, quantity: 100n }
}

const sellOrder: OrderDatum = {
Sell: { price: 2000000n, quantity: 50n, min_fill: 10n }
}

Using Raw PlutusData

For quick prototyping without schemas:

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

const datum = Data.constr(0n, [
Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"),
1735689600000n,
Data.map([
[Text.toBytes("name"), Text.toBytes("My NFT")],
[Text.toBytes("image"), Text.toBytes("ipfs://Qm...")]
])
])

Reading Datums from UTxOs

When querying UTxOs, inline datums are available directly on the UTxO object:

import { Address, 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 scriptAddress = Address.fromBech32("addr_test1wrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qnmqsyu")
const utxos = await client.getUtxos(scriptAddress)

for (const utxo of utxos) {
if (utxo.datumOption) {
console.log("UTxO has datum attached")
}
}

Best Practices

  • Use inline datums for new contracts — they're simpler and don't require datum lookup when spending
  • Use TSchema for production — compile-time type checking prevents encoding mistakes
  • Match your validator exactly — field names and order in TSchema must match your Plutus type definitions
  • Test round-trips — verify that encoding and decoding your datum produces identical results

Next Steps