Skip to content

Commit 00c7ee4

Browse files
committed
Add Aiken-compatible CBOR encoding
1 parent 0dcf415 commit 00c7ee4

File tree

7 files changed

+812
-124
lines changed

7 files changed

+812
-124
lines changed

packages/evolution/src/core/CBOR.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export type CodecOptions =
6767
| {
6868
readonly mode: "canonical"
6969
readonly mapsAsObjects?: boolean
70+
readonly encodeMapAsPairs?: boolean
7071
}
7172
| {
7273
readonly mode: "custom"
@@ -76,6 +77,7 @@ export type CodecOptions =
7677
readonly sortMapKeys: boolean
7778
readonly useMinimalEncoding: boolean
7879
readonly mapsAsObjects?: boolean
80+
readonly encodeMapAsPairs?: boolean
7981
}
8082

8183
/**
@@ -120,6 +122,29 @@ export const CML_DATA_DEFAULT_OPTIONS: CodecOptions = {
120122
mapsAsObjects: false
121123
} as const
122124

125+
/**
126+
* Aiken-compatible CBOR encoding options
127+
*
128+
* Matches the encoding used by Aiken's cbor.serialise():
129+
* - Indefinite-length arrays (9f...ff)
130+
* - Maps encoded as arrays of pairs (not CBOR maps)
131+
* - Strings as bytearrays (major type 2, not 3)
132+
* - Constructor tags: 121-127 for indices 0-6, then 1280+ for 7+
133+
*
134+
* @since 2.0.0
135+
* @category constants
136+
*/
137+
export const AIKEN_DEFAULT_OPTIONS: CodecOptions = {
138+
mode: "custom",
139+
useIndefiniteArrays: true,
140+
useIndefiniteMaps: true,
141+
useDefiniteForEmpty: false,
142+
sortMapKeys: false,
143+
useMinimalEncoding: true,
144+
mapsAsObjects: false,
145+
encodeMapAsPairs: true
146+
} as const
147+
123148
/**
124149
* CBOR encoding options that return objects instead of Maps for Schema.Struct compatibility
125150
*
@@ -877,6 +902,13 @@ const encodeMapEntriesSync = (pairs: Array<[CBOR, CBOR]>, options: CodecOptions)
877902
const useMinimal = options.mode === "canonical" || (options.mode === "custom" && options.useMinimalEncoding)
878903
const sortKeys = options.mode === "canonical" || (options.mode === "custom" && options.sortMapKeys)
879904
const useIndefinite = options.mode === "custom" && options.useIndefiniteMaps && length > 0
905+
const encodeAsPairs = options.encodeMapAsPairs === true
906+
907+
// If encoding as array of pairs (Aiken/Plutus style), delegate to array encoding
908+
if (encodeAsPairs) {
909+
const pairArrays = pairs.map(([k, v]) => [k, v] as CBOR)
910+
return encodeArraySync(pairArrays, options)
911+
}
880912

881913
// Fast path for empty maps
882914
if (length === 0) {

packages/evolution/src/core/Data.ts

Lines changed: 17 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Data as EffectData, Effect, Equal, FastCheck, Hash, ParseResult, Schema
22

33
import * as Bytes from "./Bytes.js"
44
import * as CBOR from "./CBOR.js"
5-
import * as Function from "./Function.js"
65
import * as Numeric from "./Numeric.js"
76

87
/**
@@ -195,14 +194,16 @@ export const ByteArray = Schema.Uint8ArrayFromHex.annotations({
195194
})
196195
export type ByteArray = typeof ByteArray.Type
197196

197+
export interface DataSchema extends Schema.SchemaClass<Data, DataEncoded> {}
198+
198199
/**
199200
* Combined schema for PlutusData type with proper recursion
200201
*
201202
* @category schemas
202203
*
203204
* @since 2.0.0
204205
*/
205-
export const DataSchema: Schema.Schema<Data, DataEncoded> = Schema.Union(
206+
export const DataSchema: DataSchema = Schema.Union(
206207
// Map: ReadonlyArray<[DataEncoded, DataEncoded]> <-> Map<Data, Data>
207208
MapSchema,
208209

@@ -861,128 +862,42 @@ export const FromCBORHex = (options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_O
861862
description: "Transforms CBOR hex string to Data using CDDL encoding"
862863
})
863864

864-
// ============================================================================
865-
// Either Namespace
866-
// ============================================================================
867-
868-
/**
869-
* Either-based variants for functions that can fail.
870-
*
871-
* @since 2.0.0
872-
* @category either
873-
*/
874-
export namespace Either {
875-
/**
876-
* Encode PlutusData to CBOR bytes with Either error handling
877-
*
878-
* @since 2.0.0
879-
* @category transformation
880-
*/
881-
export const toCBORBytes = Function.makeCBOREncodeEither(FromCDDL, DataError, CBOR.CML_DATA_DEFAULT_OPTIONS)
882-
883-
/**
884-
* Encode PlutusData to CBOR hex string with Either error handling
885-
*
886-
* @since 2.0.0
887-
* @category transformation
888-
*/
889-
export const toCBORHex = Function.makeCBOREncodeHexEither(FromCDDL, DataError, CBOR.CML_DATA_DEFAULT_OPTIONS)
890-
891-
/**
892-
* Decode PlutusData from CBOR bytes with Either error handling
893-
*
894-
* @since 2.0.0
895-
* @category transformation
896-
*/
897-
export const fromCBORBytes = Function.makeCBORDecodeEither(FromCDDL, DataError, CBOR.CML_DATA_DEFAULT_OPTIONS)
898-
899-
/**
900-
* Decode PlutusData from CBOR hex string with Effect error handling
901-
*
902-
* @since 2.0.0
903-
* @category transformation
904-
*/
905-
export const fromCBORHex = Function.makeCBORDecodeHexEither(FromCDDL, DataError, CBOR.CML_DATA_DEFAULT_OPTIONS)
906-
907-
/**
908-
* Create a schema that transforms from a custom type to Data and provides CBOR encoding
909-
*
910-
* @since 2.0.0
911-
* @category combinators
912-
*/
913-
export const withSchema = <A, I extends Data>(
914-
schema: Schema.Schema<A, I>,
915-
options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS
916-
) => {
917-
return {
918-
toData: Function.makeEncodeEither(schema, DataError),
919-
fromData: Function.makeDecodeEither(schema, DataError),
920-
toCBORHex: Function.makeCBOREncodeHexEither(FromCDDL, DataError, options),
921-
toCBORBytes: Function.makeCBOREncodeEither(FromCDDL, DataError, options),
922-
fromCBORHex: Function.makeCBORDecodeHexEither(FromCDDL, DataError, options),
923-
fromCBORBytes: Function.makeCBORDecodeEither(FromCDDL, DataError, options)
924-
}
925-
}
926-
}
927-
928865
/**
929866
* Encode PlutusData to CBOR bytes
930867
*
931868
* @since 2.0.0
932869
* @category transformation
933870
*/
934-
export const toCBORBytes = Function.makeCBOREncodeSync(
935-
FromCDDL,
936-
DataError,
937-
"Data.toCBORBytes",
938-
CBOR.CML_DATA_DEFAULT_OPTIONS
939-
)
871+
export const toCBORBytes = (data: Data, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS) =>
872+
Schema.encodeSync(FromCBORBytes(options))(data)
940873

941874
/**
942875
* Encode PlutusData to CBOR hex string
943876
*
944877
* @since 2.0.0
945878
* @category transformation
946879
*/
947-
export const toCBORHex = Function.makeCBOREncodeHexSync(
948-
FromCDDL,
949-
DataError,
950-
"Data.toCBORHex",
951-
CBOR.CML_DATA_DEFAULT_OPTIONS
952-
)
880+
export const toCBORHex = (data: Data, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS) =>
881+
Schema.encodeSync(FromCBORHex(options))(data)
953882

954883
/**
955884
* Decode PlutusData from CBOR bytes
956885
*
957886
* @since 2.0.0
958887
* @category transformation
959888
*/
960-
export const fromCBORBytes = Function.makeCBORDecodeSync(
961-
FromCDDL,
962-
DataError,
963-
"Data.fromCBORBytes",
964-
CBOR.CML_DATA_DEFAULT_OPTIONS
965-
)
889+
export const fromCBORBytes = (bytes: Uint8Array, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS): Data =>
890+
Schema.decodeSync(FromCBORBytes(options))(bytes)
966891

967892
/**
968893
* Decode PlutusData from CBOR hex string
969894
*
970895
* @since 2.0.0
971896
* @category transformation
972897
*/
973-
export const fromCBORHex = Function.makeCBORDecodeHexSync(
974-
FromCDDL,
975-
DataError,
976-
"Data.fromCBORHex",
977-
CBOR.CML_DATA_DEFAULT_OPTIONS
978-
)
898+
export const fromCBORHex = (hex: string, options: CBOR.CodecOptions = CBOR.CML_DATA_DEFAULT_OPTIONS): Data =>
899+
Schema.decodeSync(FromCBORHex(options))(hex)
979900

980-
/**
981-
* Transform data to Data using a schema
982-
*
983-
* @since 2.0.0
984-
* @category transformation
985-
*/
986901
/**
987902
* Create a schema that transforms from a custom type to Data and provides CBOR encoding
988903
*
@@ -994,31 +909,11 @@ export const withSchema = <A, I extends Data>(
994909
options: CBOR.CodecOptions = DEFAULT_CBOR_OPTIONS
995910
) => {
996911
return {
997-
toData: Function.makeEncodeSync(schema, DataError, "withSchema.toData"),
998-
fromData: Function.makeDecodeSync(schema, DataError, "withSchema.fromData"),
999-
toCBORHex: Function.makeCBOREncodeHexSync(
1000-
Schema.compose(FromCDDL, schema),
1001-
DataError,
1002-
"withSchema.toCBORHex",
1003-
options
1004-
),
1005-
toCBORBytes: Function.makeCBOREncodeSync(
1006-
Schema.compose(FromCDDL, schema),
1007-
DataError,
1008-
"withSchema.toCBORBytes",
1009-
options
1010-
),
1011-
fromCBORHex: Function.makeCBORDecodeHexSync(
1012-
Schema.compose(FromCDDL, schema),
1013-
DataError,
1014-
"withSchema.fromCBORHex",
1015-
options
1016-
),
1017-
fromCBORBytes: Function.makeCBORDecodeSync(
1018-
Schema.compose(FromCDDL, schema),
1019-
DataError,
1020-
"withSchema.fromCBORBytes",
1021-
options
1022-
)
912+
toData: Schema.encodeSync(schema),
913+
fromData: Schema.decodeSync(schema),
914+
toCBORHex: Schema.encodeSync(Schema.compose(FromCBORHex(options), schema)),
915+
toCBORBytes: Schema.encodeSync(Schema.compose(FromCBORBytes(options), schema)),
916+
fromCBORHex: Schema.decodeSync(Schema.compose(FromCBORHex(options), schema)),
917+
fromCBORBytes: Schema.decodeSync(Schema.compose(FromCBORBytes(options), schema))
1023918
}
1024919
}

packages/evolution/src/sdk/builders/TxBuilderImpl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ export const assembleTransaction = (
627627
const plutusDataArray: Array<PlutusData.Data> = []
628628
for (const utxo of state.selectedUtxos) {
629629
if (utxo.datumOption?.type === "inlineDatum") {
630-
const datum = yield* PlutusData.Either.fromCBORHex(utxo.datumOption.inline)
630+
const datum = yield* Schema.decode(PlutusData.FromCBORHex())(utxo.datumOption.inline)
631631
plutusDataArray.push(datum)
632632
yield* Effect.logDebug(`[Assembly] Extracted inline datum from UTxO`)
633633
}

0 commit comments

Comments
 (0)