Skip to content

Commit 2ffa5c7

Browse files
committed
docs: add withSchema, hashData, DataEncoded, DataError, and helper API reference to Data guide
Closes IntersectMBO#154
1 parent 9c16997 commit 2ffa5c7

1 file changed

Lines changed: 229 additions & 30 deletions

File tree

docs/content/docs/encoding/data.mdx

Lines changed: 229 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: PlutusData
33
description: On-chain data structures for Cardano smart contracts
44
---
55

6-
import { Card, Cards } from 'fumadocs-ui/components/card'
6+
import { Card, Cards } from "fumadocs-ui/components/card"
77

88
# PlutusData
99

@@ -15,13 +15,13 @@ The Evolution SDK's Data module gives you type-safe PlutusData creation without
1515

1616
PlutusData consists of five primitive types:
1717

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 |
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 |
2525

2626
## Quick Start
2727

@@ -61,7 +61,7 @@ import { Bytes, Data, Text } from "@evolution-sdk/evolution"
6161
const fee: Data.Data = 170000n
6262
const deposit: Data.Data = 2000000n
6363

64-
// Token quantities
64+
// Token quantities
6565
const nftQuantity: Data.Data = 1n
6666
const ftQuantity: Data.Data = 1000000n
6767

@@ -80,14 +80,10 @@ Raw bytes for hashes, addresses, and binary data:
8080
import { Bytes, Data, Text } from "@evolution-sdk/evolution"
8181

8282
// Transaction hash (32 bytes)
83-
const txHash = Bytes.fromHex(
84-
"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
85-
)
83+
const txHash = Bytes.fromHex("a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2")
8684

8785
// Policy ID (28 bytes)
88-
const policyId = Bytes.fromHex(
89-
"1234567890abcdef1234567890abcdef1234567890abcdef12345678"
90-
)
86+
const policyId = Bytes.fromHex("1234567890abcdef1234567890abcdef1234567890abcdef12345678")
9187

9288
// Asset name (readable string)
9389
const assetName = Text.toBytes("MyToken")
@@ -113,13 +109,9 @@ const claimAction = Data.constr(0n, [])
113109
const cancelAction = Data.constr(1n, [])
114110

115111
// Variant with single field
116-
const verificationKeyCred = Data.constr(0n, [
117-
Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")
118-
])
112+
const verificationKeyCred = Data.constr(0n, [Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de")])
119113

120-
const scriptCred = Data.constr(1n, [
121-
Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab")
122-
])
114+
const scriptCred = Data.constr(1n, [Bytes.fromHex("def456abc123def456abc123def456abc123def456abc123def456ab")])
123115

124116
// Multiple fields
125117
const outputRef = Data.constr(0n, [
@@ -149,10 +141,13 @@ const tokenMetadata = Data.map([
149141
[Text.toBytes("name"), Text.toBytes("MyToken")],
150142
[Text.toBytes("ticker"), Text.toBytes("MTK")],
151143
[Text.toBytes("decimals"), 6n],
152-
[Text.toBytes("properties"), Data.map([
153-
[Text.toBytes("mintable"), 1n], // boolean as 0/1
154-
[Text.toBytes("burnable"), 1n]
155-
])]
144+
[
145+
Text.toBytes("properties"),
146+
Data.map([
147+
[Text.toBytes("mintable"), 1n], // boolean as 0/1
148+
[Text.toBytes("burnable"), 1n]
149+
])
150+
]
156151
])
157152
```
158153

@@ -201,7 +196,7 @@ const datum = Data.constr(0n, [
201196
const cborHex = Data.toCBORHex(datum)
202197
// "d8799fa2646265..."
203198

204-
// Encode to bytes
199+
// Encode to bytes
205200
const cborBytes = Data.toCBORBytes(datum)
206201
// Uint8Array [216, 121, 159, ...]
207202

@@ -260,10 +255,13 @@ const metadata = Data.map([
260255
[Text.toBytes("name"), Text.toBytes("SpaceAce #4242")],
261256
[Text.toBytes("image"), Text.toBytes("ipfs://QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco")],
262257
[Text.toBytes("rarity"), Text.toBytes("legendary")],
263-
[Text.toBytes("attributes"), Data.map([
264-
[Text.toBytes("class"), Text.toBytes("explorer")],
265-
[Text.toBytes("power"), 9000n]
266-
])]
258+
[
259+
Text.toBytes("attributes"),
260+
Data.map([
261+
[Text.toBytes("class"), Text.toBytes("explorer")],
262+
[Text.toBytes("power"), 9000n]
263+
])
264+
]
267265
])
268266

269267
const cip68Datum = Data.constr(0n, [
@@ -315,6 +313,207 @@ Use the Data module directly when:
315313

316314
For production smart contract integration, use [TSchema](/docs/encoding/tschema) for type-safe schema definitions with automatic validation.
317315

316+
## Data.withSchema()
317+
318+
`Data.withSchema()` is the bridge between raw PlutusData and TSchema. It takes a TSchema definition and returns a codec with six methods for converting between your TypeScript types and CBOR:
319+
320+
```typescript twoslash
321+
import { Bytes, Data, TSchema } from "@evolution-sdk/evolution"
322+
323+
const EscrowSchema = TSchema.Struct({
324+
beneficiary: TSchema.ByteArray,
325+
deadline: TSchema.Integer,
326+
amount: TSchema.Integer
327+
})
328+
329+
// Create codec from schema
330+
const EscrowCodec = Data.withSchema(EscrowSchema)
331+
332+
// Available methods:
333+
// EscrowCodec.toData(value) → PlutusData
334+
// EscrowCodec.fromData(data) → value
335+
// EscrowCodec.toCBORHex(value) → string
336+
// EscrowCodec.toCBORBytes(value) → Uint8Array
337+
// EscrowCodec.fromCBORHex(hex) → value
338+
// EscrowCodec.fromCBORBytes(bytes) → value
339+
340+
const datum = {
341+
beneficiary: Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"),
342+
deadline: 1735689600000n,
343+
amount: 10000000n
344+
}
345+
346+
// Encode to CBOR hex (for use in transactions)
347+
const cborHex = EscrowCodec.toCBORHex(datum)
348+
349+
// Decode back from CBOR hex
350+
const decoded = EscrowCodec.fromCBORHex(cborHex)
351+
352+
// Convert to raw PlutusData
353+
const plutusData = EscrowCodec.toData(datum)
354+
```
355+
356+
Use `Data.withSchema()` any time you have a TSchema definition and need to encode/decode values for on-chain use. For raw PlutusData without a schema, use `Data.toCBORHex()` directly.
357+
358+
## Hashing PlutusData
359+
360+
Use `Data.hashData()` to compute the blake2b-256 hash of any PlutusData value over its CBOR encoding. This is used when you need to reference a datum by hash in a transaction output:
361+
362+
```typescript twoslash
363+
import { Bytes, Data, TSchema } from "@evolution-sdk/evolution"
364+
365+
const EscrowSchema = TSchema.Struct({
366+
beneficiary: TSchema.ByteArray,
367+
deadline: TSchema.Integer,
368+
amount: TSchema.Integer
369+
})
370+
371+
const EscrowCodec = Data.withSchema(EscrowSchema)
372+
373+
const datum = {
374+
beneficiary: Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"),
375+
deadline: 1735689600000n,
376+
amount: 10000000n
377+
}
378+
379+
// Convert to raw PlutusData first, then hash
380+
const plutusData = EscrowCodec.toData(datum)
381+
const datumHash = Data.hashData(plutusData)
382+
383+
console.log(datumHash) // DatumHash instance (blake2b-256)
384+
```
385+
386+
## DataEncoded
387+
388+
`DataEncoded` is a TypeScript type representing PlutusData in its JSON-serializable form, based on the Conway CDDL specification. It is used internally for JSON encoding and is useful when you need to inspect or transmit PlutusData as plain JSON:
389+
390+
```typescript twoslash
391+
import { Data } from "@evolution-sdk/evolution"
392+
393+
// DataEncoded represents PlutusData in JSON-safe form:
394+
//
395+
// Constructor: { index: string, fields: DataEncoded[] }
396+
// Map: ReadonlyArray<readonly [DataEncoded, DataEncoded]>
397+
// List: ReadonlyArray<DataEncoded>
398+
// Integer: string (e.g. "42")
399+
// ByteArray: string (hex, e.g. "a1b2c3")
400+
401+
type MyDatumEncoded = Data.DataEncoded
402+
403+
// Example: a Constructor with one integer field
404+
const encoded: Data.DataEncoded = {
405+
index: "0",
406+
fields: ["5000000"]
407+
}
408+
```
409+
410+
You will rarely need to use `DataEncoded` directly — prefer `Data.withSchema()` with TSchema for type-safe encoding. `DataEncoded` is most useful when debugging raw PlutusData or building low-level serialization tools.
411+
412+
## DataError
413+
414+
`DataError` is the tagged error class thrown by Data operations when encoding or decoding fails. It carries an optional `message` and `cause` for debugging:
415+
416+
```typescript twoslash
417+
import { Data, TSchema } from "@evolution-sdk/evolution"
418+
419+
const Schema = TSchema.Struct({
420+
amount: TSchema.Integer
421+
})
422+
423+
const Codec = Data.withSchema(Schema)
424+
425+
try {
426+
// This will throw DataError if the CBOR is invalid or doesn't match the schema
427+
const result = Codec.fromCBORHex("invalid_cbor_hex")
428+
} catch (error) {
429+
if (error instanceof Data.DataError) {
430+
console.error("Data decoding failed:", error.message)
431+
console.error("Caused by:", error.cause)
432+
}
433+
}
434+
```
435+
436+
`DataError` extends Effect's `TaggedError`, so it integrates cleanly with Effect-based error handling pipelines if your project uses Effect.
437+
438+
## Constr Class vs Data.constr() Helper
439+
440+
The `Data` module provides two ways to create Constructor PlutusData — the `Constr` class and the `Data.constr()` convenience function. They produce the same result but serve different purposes:
441+
442+
**`Constr` class** — the underlying schema class with typed `index` and `fields` properties. Use it when you need to inspect, pattern match, or type-check a constructor value:
443+
444+
```typescript twoslash
445+
import { Data } from "@evolution-sdk/evolution"
446+
447+
// Constr is a class with typed properties
448+
const constr = new Data.Constr({ index: 0n, fields: [] })
449+
450+
console.log(constr.index) // 0n
451+
console.log(constr.fields) // []
452+
453+
// Useful for type checking
454+
if (constr instanceof Data.Constr) {
455+
console.log("It's a constructor with index:", constr.index)
456+
}
457+
```
458+
459+
**`Data.constr()` helper** — a convenience function that calls `Constr.make()` internally. Use it when constructing PlutusData inline for brevity:
460+
461+
```typescript twoslash
462+
import { Bytes, Data } from "@evolution-sdk/evolution"
463+
464+
// Claim action (index 0, no fields)
465+
const claim = Data.constr(0n, [])
466+
467+
// Script credential (index 1, one field)
468+
const scriptCred = Data.constr(1n, [
469+
Data.bytearray(Bytes.fromHex("abc123def456abc123def456abc123def456abc123def456abc123de"))
470+
])
471+
472+
// Equivalent to using the class directly:
473+
// new Data.Constr({ index: 0n, fields: [] })
474+
```
475+
476+
**When to use which:**
477+
478+
| | `Constr` class | `Data.constr()` |
479+
| ---------------------------- | -------------- | ---------------------- |
480+
| Constructing values || ✅ Preferred (shorter) |
481+
| Type checking (`instanceof`) |||
482+
| Pattern matching on fields |||
483+
| Use in TSchema definitions |||
484+
| Inline data building | ⚠️ Verbose ||
485+
486+
**Rule of thumb**: Use `Data.constr()` when building data, use the `Constr` class when reading or inspecting it.
487+
488+
## Helper API Reference
489+
490+
A quick reference of all `Data` module helpers:
491+
492+
| Function | Signature | Description |
493+
| ---------------------------------- | --------------------------- | --------------------------------------------------- |
494+
| `Data.withSchema(schema)` | `(schema) → Codec` | Create a type-safe codec from a TSchema definition |
495+
| `Data.hashData(data)` | `(Data) → DatumHash` | Hash PlutusData with blake2b-256 over CBOR encoding |
496+
| `Data.hash(data)` | `(Data) → number` | Hash value for equality comparison |
497+
| `Data.toCBORHex(data)` | `(Data) → string` | Encode raw PlutusData to CBOR hex |
498+
| `Data.fromCBORHex(hex)` | `(string) → Data` | Decode CBOR hex to raw PlutusData |
499+
| `Data.toCBORBytes(data)` | `(Data) → Uint8Array` | Encode raw PlutusData to CBOR bytes |
500+
| `Data.fromCBORBytes(bytes)` | `(Uint8Array) → Data` | Decode CBOR bytes to raw PlutusData |
501+
| `Data.equals(a, b)` | `(Data, Data) → boolean` | Deep equality check for PlutusData |
502+
| `Data.constr(index, fields)` | `(bigint, Data[]) → Constr` | Create a Constructor PlutusData |
503+
| `Data.map(entries)` | `([Data, Data][]) → Map` | Create a Map PlutusData |
504+
| `Data.list(items)` | `(Data[]) → List` | Create a List PlutusData |
505+
| `Data.int(value)` | `(bigint) → Int` | Create an Integer PlutusData |
506+
| `Data.bytearray(bytes)` | `(string) → ByteArray` | Create a ByteArray PlutusData from hex string |
507+
| `Data.isConstr(data)` | `(Data) → boolean` | Type guard for Constr |
508+
| `Data.isMap(data)` | `(Data) → boolean` | Type guard for Map |
509+
| `Data.isList(data)` | `(Data) → boolean` | Type guard for List |
510+
| `Data.isInt(data)` | `(Data) → boolean` | Type guard for Integer |
511+
| `Data.isBytes(data)` | `(Data) → boolean` | Type guard for ByteArray |
512+
| `Data.matchConstr(data, handlers)` | `(Data, handlers) → T` | Pattern match on a Constr |
513+
| `Data.matchData(data, handlers)` | `(Data, handlers) → T` | Pattern match on any PlutusData |
514+
| `DataEncoded` | `type` | JSON-serializable representation of PlutusData |
515+
| `DataError` | `class` | Tagged error for Data encoding/decoding failures |
516+
318517
## Next Steps
319518

320519
<Cards>

0 commit comments

Comments
 (0)