Datums
Attach data to script-locked UTxOs
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:
| Type | Description | When to Use |
|---|---|---|
| Inline Datum | Data stored directly in the UTxO | Recommended for most use cases (Plutus V2+) |
| Datum Hash | Only a hash stored on-chain; full datum provided when spending | Legacy 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 { , , , , , } from "@evolution-sdk/evolution"
const = .()
.({
: "https://cardano-preprod.blockfrost.io/api/v0",
: ..!
})
.({ : ..!, : 0 })
// Simple inline datum
const = await
.()
.({
: .("addr_test1wrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qnmqsyu"),
: .(10_000_000n),
: new .({ : .(0n, [5000000n, 1735689600000n]) })
})
.()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 })
// Compute datum hash from PlutusData
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 // DatumHash returned by hashData
})
.build()To spend the output, give its datum to collectFrom. The builder matches each datum to its input by hash and writes it in the witness set:
// lockedUtxo: the output locked above; script: the validator at its address
const spendTx = await client
.newTx()
.collectFrom({ inputs: [lockedUtxo], redeemer: Data.constr(0n, []), datums: [datum] })
.attachScript({ script })
.build()A datum built by hand must be in the layout its hash was computed with, as datum is here. For an output someone else locked, fetch the datum with client.getDatum(lockedUtxo.datumOption), which keeps the bytes its hash covers, or let the build fetch each missing datum with build({ onMissingDatum: "provider" }). A provider can serve a datum only after an earlier transaction has published it. Outputs this SDK locks never publish theirs, so keep the datum of each output you lock. With neither, build() fails and names each input that needs its datum.
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 { , , } from "@evolution-sdk/evolution"
// Define datum schema matching your validator
const = .({
: .,
: .,
: .
})
type = typeof .
const = .()
// Create type-safe datum
const : = {
: .("abc123def456abc123def456abc123def456abc123def456abc123de"),
: 1735689600000n,
: 10_000_000n
}
// Encode to PlutusData for use in transactions
const = .()Constructing Complex Datums
Variant Datums
For datums with multiple possible shapes:
import { , , } from "@evolution-sdk/evolution"
const = .({
: {
: .,
: .
},
: {
: .,
: .,
: .
},
: {}
})
type = typeof .
const : = {
: { : 1500000n, : 100n }
}
const : = {
: { : 2000000n, : 50n, : 10n }
}Using Raw PlutusData
For quick prototyping without schemas:
import { , , } from "@evolution-sdk/evolution"
// Constructor with fields
const = .(0n, [
.("abc123def456abc123def456abc123def456abc123def456abc123de"),
1735689600000n,
.([
[.("name"), .("My NFT")],
[.("image"), .("ipfs://Qm...")]
])
])Reading Datums from UTxOs
When querying UTxOs, inline datums are available directly on the UTxO object:
import { , , , , , } from "@evolution-sdk/evolution"
const = .()
.({
: "https://cardano-preprod.blockfrost.io/api/v0",
: ..!
})
.({ : ..!, : 0 })
// Query UTxOs at a script address
const = .("addr_test1wrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qnmqsyu")
const = await .()
for (const of ) {
if (.) {
// datumOption is InlineDatum or DatumHash
.("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
- Locking to Script — Send funds to a script address with datums
- Spending from Script — Unlock script UTxOs with redeemers
- TSchema — Type-safe schema definitions
- PlutusData — Raw PlutusData construction