|
| 1 | +--- |
| 2 | +title: PlutusData |
| 3 | +description: On-chain data structures for Cardano smart contracts |
| 4 | +--- |
| 5 | + |
| 6 | +import { Card, Cards } from 'fumadocs-ui/components/card' |
| 7 | + |
| 8 | +# PlutusData |
| 9 | + |
| 10 | +PlutusData is the serialization format for all on-chain data in Cardano smart contracts. Every datum attached to a UTxO, every redeemer that unlocks a script, and every parameter passed to a validator must be encoded as PlutusData. |
| 11 | + |
| 12 | +The Evolution SDK's Data module gives you type-safe PlutusData creation without touching raw CBOR bytes. |
| 13 | + |
| 14 | +## The Five Types |
| 15 | + |
| 16 | +PlutusData consists of five primitive types: |
| 17 | + |
| 18 | +| Type | TypeScript | Use For | |
| 19 | +|------|-----------|---------| |
| 20 | +| **Integer** | `bigint` | Amounts, indices, timestamps, quantities | |
| 21 | +| **ByteArray** | `Uint8Array` | Hashes, addresses, policy IDs, asset names | |
| 22 | +| **Constructor** | `{ index: bigint, fields: Data[] }` | Variants, tagged unions, structured data | |
| 23 | +| **Map** | `Map<Data, Data>` | Metadata, key-value stores | |
| 24 | +| **List** | `ReadonlyArray<Data>` | Arrays of values | |
| 25 | + |
| 26 | +## Quick Start |
| 27 | + |
| 28 | +Create PlutusData using TypeScript primitives: |
| 29 | + |
| 30 | +```typescript twoslash |
| 31 | +import { Core } from "@evolution-sdk/evolution" |
| 32 | + |
| 33 | +// Integer (bigint) |
| 34 | +const lovelaceAmount: Core.Data.Data = 5000000n |
| 35 | + |
| 36 | +// ByteArray (Uint8Array) |
| 37 | +const tokenName = Core.Text.toBytes("HOSKY") |
| 38 | +const policyId = Core.Bytes.fromHex("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8") |
| 39 | + |
| 40 | +// Constructor (variant with fields) |
| 41 | +const unlockAction = Core.Data.constr(0n, []) // variant 0, no fields |
| 42 | + |
| 43 | +// Map (key-value pairs) |
| 44 | +const metadata = Core.Data.map([ |
| 45 | + [Core.Text.toBytes("name"), Core.Text.toBytes("My NFT")], |
| 46 | + [Core.Text.toBytes("image"), Core.Text.toBytes("ipfs://Qm...")] |
| 47 | +]) |
| 48 | + |
| 49 | +// List (array) |
| 50 | +const quantities: Core.Data.Data = [100n, 200n, 300n] |
| 51 | +``` |
| 52 | + |
| 53 | +## Integers |
| 54 | + |
| 55 | +Use `bigint` directly—no wrapper needed: |
| 56 | + |
| 57 | +```typescript twoslash |
| 58 | +import { Core } from "@evolution-sdk/evolution" |
| 59 | + |
| 60 | +// Lovelace amounts |
| 61 | +const fee: Core.Data.Data = 170000n |
| 62 | +const deposit: Core.Data.Data = 2000000n |
| 63 | + |
| 64 | +// Token quantities |
| 65 | +const nftQuantity: Core.Data.Data = 1n |
| 66 | +const ftQuantity: Core.Data.Data = 1000000n |
| 67 | + |
| 68 | +// Negative values supported |
| 69 | +const delta: Core.Data.Data = -500n |
| 70 | + |
| 71 | +// Large numbers |
| 72 | +const totalSupply: Core.Data.Data = 45000000000000000n |
| 73 | +``` |
| 74 | + |
| 75 | +## Byte Arrays |
| 76 | + |
| 77 | +Raw bytes for hashes, addresses, and binary data: |
| 78 | + |
| 79 | +```typescript twoslash |
| 80 | +import { Core } from "@evolution-sdk/evolution" |
| 81 | + |
| 82 | +// Transaction hash (32 bytes) |
| 83 | +const txHash = Core.Bytes.fromHex( |
| 84 | + "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" |
| 85 | +) |
| 86 | + |
| 87 | +// Policy ID (28 bytes) |
| 88 | +const policyId = Core.Bytes.fromHex( |
| 89 | + "1234567890abcdef1234567890abcdef1234567890abcdef12345678" |
| 90 | +) |
| 91 | + |
| 92 | +// Asset name (readable string) |
| 93 | +const assetName = Core.Text.toBytes("MyToken") |
| 94 | + |
| 95 | +// Empty byte array (ada-only policyId) |
| 96 | +const adaPolicyId = new Uint8Array() |
| 97 | +``` |
| 98 | + |
| 99 | +**When to use `Bytes.fromHex` vs `Text.toBytes`:** |
| 100 | + |
| 101 | +- **`Bytes.fromHex`**: For hashes, policy IDs, credential hashes (hexadecimal data) |
| 102 | +- **`Text.toBytes`**: For asset names, metadata values (human-readable strings) |
| 103 | + |
| 104 | +## Constructors |
| 105 | + |
| 106 | +Tagged unions representing variants or structured data: |
| 107 | + |
| 108 | +```typescript twoslash |
| 109 | +import { Core } from "@evolution-sdk/evolution" |
| 110 | + |
| 111 | +// Simple variant (no data) |
| 112 | +const claimAction = Core.Data.constr(0n, []) |
| 113 | +const cancelAction = Core.Data.constr(1n, []) |
| 114 | + |
| 115 | +// Variant with single field |
| 116 | +const verificationKeyCred = Core.Data.constr(0n, [ |
| 117 | + Core.Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de") |
| 118 | +]) |
| 119 | + |
| 120 | +const scriptCred = Core.Data.constr(1n, [ |
| 121 | + Core.Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab") |
| 122 | +]) |
| 123 | + |
| 124 | +// Multiple fields |
| 125 | +const outputRef = Core.Data.constr(0n, [ |
| 126 | + Core.Bytes.fromHex("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"), // tx hash |
| 127 | + 2n // output index |
| 128 | +]) |
| 129 | +``` |
| 130 | + |
| 131 | +**Constructor index** determines which variant you're creating. Your Plutus validator defines what each index means. |
| 132 | + |
| 133 | +## Maps |
| 134 | + |
| 135 | +Key-value pairs where both keys and values are PlutusData: |
| 136 | + |
| 137 | +```typescript twoslash |
| 138 | +import { Core } from "@evolution-sdk/evolution" |
| 139 | + |
| 140 | +// NFT metadata |
| 141 | +const nftMetadata = Core.Data.map([ |
| 142 | + [Core.Text.toBytes("name"), Core.Text.toBytes("CryptoKitty #1234")], |
| 143 | + [Core.Text.toBytes("image"), Core.Text.toBytes("ipfs://QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco")], |
| 144 | + [Core.Text.toBytes("description"), Core.Text.toBytes("A rare cryptokitty with rainbow fur")] |
| 145 | +]) |
| 146 | + |
| 147 | +// Nested maps |
| 148 | +const tokenMetadata = Core.Data.map([ |
| 149 | + [Core.Text.toBytes("name"), Core.Text.toBytes("MyToken")], |
| 150 | + [Core.Text.toBytes("ticker"), Core.Text.toBytes("MTK")], |
| 151 | + [Core.Text.toBytes("decimals"), 6n], |
| 152 | + [Core.Text.toBytes("properties"), Core.Data.map([ |
| 153 | + [Core.Text.toBytes("mintable"), 1n], // boolean as 0/1 |
| 154 | + [Core.Text.toBytes("burnable"), 1n] |
| 155 | + ])] |
| 156 | +]) |
| 157 | +``` |
| 158 | + |
| 159 | +## Lists |
| 160 | + |
| 161 | +Ordered arrays of PlutusData: |
| 162 | + |
| 163 | +```typescript twoslash |
| 164 | +import { Core } from "@evolution-sdk/evolution" |
| 165 | + |
| 166 | +// List of integers |
| 167 | +const prices: Core.Data.Data = [100n, 250n, 500n, 1000n] |
| 168 | + |
| 169 | +// List of byte arrays (hashes) |
| 170 | +const approvedSigners: Core.Data.Data = [ |
| 171 | + Core.Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"), |
| 172 | + Core.Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab"), |
| 173 | + Core.Bytes.fromHex("123456789abc123456789abc123456789abc123456789abc12345678") |
| 174 | +] |
| 175 | + |
| 176 | +// List of constructors |
| 177 | +const actions: Core.Data.Data = [ |
| 178 | + Core.Data.constr(0n, []), // Claim |
| 179 | + Core.Data.constr(1n, []), // Cancel |
| 180 | + Core.Data.constr(2n, [5000000n]) // PartialClaim(amount) |
| 181 | +] |
| 182 | +``` |
| 183 | + |
| 184 | +## CBOR Encoding |
| 185 | + |
| 186 | +Convert PlutusData to CBOR for blockchain submission: |
| 187 | + |
| 188 | +```typescript twoslash |
| 189 | +import { Core } from "@evolution-sdk/evolution" |
| 190 | + |
| 191 | +const datum = Core.Data.constr(0n, [ |
| 192 | + Core.Data.map([ |
| 193 | + [Core.Text.toBytes("beneficiary"), Core.Text.toBytes("addr1...")], |
| 194 | + [Core.Text.toBytes("deadline"), 1735689600000n] |
| 195 | + ]), |
| 196 | + 5000000n, // amount |
| 197 | + 1n // version |
| 198 | +]) |
| 199 | + |
| 200 | +// Encode to hex string |
| 201 | +const cborHex = Core.Data.toCBORHex(datum) |
| 202 | +// "d8799fa2646265..." |
| 203 | + |
| 204 | +// Encode to bytes |
| 205 | +const cborBytes = Core.Data.toCBORBytes(datum) |
| 206 | +// Uint8Array [216, 121, 159, ...] |
| 207 | + |
| 208 | +// Decode from CBOR |
| 209 | +const decoded = Core.Data.fromCBORHex(cborHex) |
| 210 | +// Returns original PlutusData structure |
| 211 | +``` |
| 212 | + |
| 213 | +## Equality Comparison |
| 214 | + |
| 215 | +Check if two PlutusData structures are equal: |
| 216 | + |
| 217 | +```typescript twoslash |
| 218 | +import { Core } from "@evolution-sdk/evolution" |
| 219 | + |
| 220 | +const map1 = Core.Data.map([ |
| 221 | + [Core.Text.toBytes("name"), Core.Text.toBytes("Alice")], |
| 222 | + [Core.Text.toBytes("age"), 30n] |
| 223 | +]) |
| 224 | + |
| 225 | +const map2 = Core.Data.map([ |
| 226 | + [Core.Text.toBytes("name"), Core.Text.toBytes("Alice")], |
| 227 | + [Core.Text.toBytes("age"), 30n] |
| 228 | +]) |
| 229 | + |
| 230 | +// Deep equality check |
| 231 | +const isEqual = Core.Data.equals(map1, map2) |
| 232 | +// true |
| 233 | +``` |
| 234 | + |
| 235 | +## Real-World Examples |
| 236 | + |
| 237 | +### Escrow Datum |
| 238 | + |
| 239 | +```typescript twoslash |
| 240 | +import { Core } from "@evolution-sdk/evolution" |
| 241 | + |
| 242 | +// Escrow locked until deadline |
| 243 | +const escrowDatum = Core.Data.constr(0n, [ |
| 244 | + Core.Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"), // beneficiary |
| 245 | + 1735689600000n, // deadline (Unix timestamp) |
| 246 | + 10000000n // locked lovelace amount |
| 247 | +]) |
| 248 | + |
| 249 | +const cborHex = Core.Data.toCBORHex(escrowDatum) |
| 250 | +// Attach to UTxO as inline datum |
| 251 | +``` |
| 252 | + |
| 253 | +### CIP-68 NFT Metadata |
| 254 | + |
| 255 | +```typescript twoslash |
| 256 | +import { Core } from "@evolution-sdk/evolution" |
| 257 | + |
| 258 | +// Reference NFT metadata (label 100) |
| 259 | +const metadata = Core.Data.map([ |
| 260 | + [Core.Text.toBytes("name"), Core.Text.toBytes("SpaceAce #4242")], |
| 261 | + [Core.Text.toBytes("image"), Core.Text.toBytes("ipfs://QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco")], |
| 262 | + [Core.Text.toBytes("rarity"), Core.Text.toBytes("legendary")], |
| 263 | + [Core.Text.toBytes("attributes"), Core.Data.map([ |
| 264 | + [Core.Text.toBytes("class"), Core.Text.toBytes("explorer")], |
| 265 | + [Core.Text.toBytes("power"), 9000n] |
| 266 | + ])] |
| 267 | +]) |
| 268 | + |
| 269 | +const cip68Datum = Core.Data.constr(0n, [ |
| 270 | + metadata, |
| 271 | + 1n, // version |
| 272 | + [] // extra fields |
| 273 | +]) |
| 274 | +``` |
| 275 | + |
| 276 | +### Redeemer with Multiple Actions |
| 277 | + |
| 278 | +```typescript twoslash |
| 279 | +import { Core } from "@evolution-sdk/evolution" |
| 280 | + |
| 281 | +// Action variants |
| 282 | +const claim = Core.Data.constr(0n, []) |
| 283 | +const cancel = Core.Data.constr(1n, []) |
| 284 | +const update = Core.Data.constr(2n, [ |
| 285 | + 1735776000000n // new deadline |
| 286 | +]) |
| 287 | + |
| 288 | +// Use in transaction |
| 289 | +const redeemer = claim |
| 290 | +``` |
| 291 | + |
| 292 | +### Multi-Sig Validator Redeemer |
| 293 | + |
| 294 | +```typescript twoslash |
| 295 | +import { Core } from "@evolution-sdk/evolution" |
| 296 | + |
| 297 | +const multiSigRedeemer = Core.Data.constr(0n, [ |
| 298 | + // Required signers (list of key hashes) |
| 299 | + [ |
| 300 | + Core.Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"), |
| 301 | + Core.Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab") |
| 302 | + ] as Core.Data.Data, |
| 303 | + 2n // threshold (2 of N) |
| 304 | +]) |
| 305 | +``` |
| 306 | + |
| 307 | +## When to Use PlutusData Directly |
| 308 | + |
| 309 | +Use the Data module directly when: |
| 310 | + |
| 311 | +- Quick prototyping or testing |
| 312 | +- Working with dynamic data structures |
| 313 | +- Debugging CBOR encoding issues |
| 314 | +- Building tooling or explorers |
| 315 | + |
| 316 | +For production smart contract integration, use [TSchema](/docs/encoding/tschema) for type-safe schema definitions with automatic validation. |
| 317 | + |
| 318 | +## Next Steps |
| 319 | + |
| 320 | +<Cards> |
| 321 | + <Card title="TSchema" href="/docs/encoding/tschema"> |
| 322 | + Type-safe schema definitions with automatic validation |
| 323 | + </Card> |
| 324 | + <Card title="Plutus Types" href="/docs/encoding/plutus"> |
| 325 | + Pre-built types for addresses, credentials, values, and CIP-68 |
| 326 | + </Card> |
| 327 | + <Card title="CBOR" href="/docs/encoding/cbor"> |
| 328 | + Low-level CBOR encoding and decoding |
| 329 | + </Card> |
| 330 | +</Cards> |
0 commit comments