1+ // packages/experimental/src/BabbageTransactionOutput.ts
2+
13import {
24 Data ,
35 Effect ,
@@ -11,6 +13,7 @@ import {
1113import * as CBOR from "./CBOR.js" ;
1214import * as Hex from "./Hex.js" ;
1315import * as Address from "./Address.js" ;
16+ import * as DatumOption from "./DatumOption.js" ;
1417
1518/**
1619 * CDDL specs
@@ -30,7 +33,7 @@ export class BabbageTransactionOutput extends Schema.TaggedClass<BabbageTransact
3033) ( "BabbageTransactionOutput" , {
3134 address : Address . Address ,
3235 value : Schema . BigIntFromSelf ,
33- datumOption : Schema . optional ( Schema . Uint8ArrayFromSelf ) ,
36+ datumOption : Schema . optional ( DatumOption . DatumOption ) ,
3437 scriptRef : Schema . optional ( Schema . Uint8ArrayFromSelf ) ,
3538} ) {
3639 [ Inspectable . NodeInspectSymbol ] ( ) {
@@ -66,7 +69,7 @@ export class BabbageTransactionOutputError extends Data.TaggedError(
6669export const isBabbageTransactionOutput = Schema . is ( BabbageTransactionOutput ) ;
6770
6871/**
69- * Schema for transforming between CBOR bytes and BabbageTransactionOutput
72+ * Schema for transforming between CBOR bytes and BabbageTransactionOutput.
7073 *
7174 * @since 2.0.0
7275 * @category encoding/decoding
@@ -80,59 +83,144 @@ export const CBORBytes = Schema.transformOrFail(
8083 strict : true ,
8184 encode : ( toI , options , ast , toA ) =>
8285 pipe (
83- ParseResult . encode ( Address . Address ) ( toA . address ) ,
84- Effect . map ( ( addressBytes ) => {
86+ ParseResult . encode ( Address . BytesSchema ) ( toA . address ) ,
87+ Effect . flatMap ( ( addressBytes ) => {
8588 const map = new Map < number , unknown > ( [
8689 [ 0 , addressBytes ] ,
8790 [ 1 , toA . value ] ,
8891 ] ) ;
8992
90- if ( toA . datumOption ) {
91- map . set ( 2 , toA . datumOption ) ;
93+ // Encode datumOption if present
94+ const datumOptionEffect =
95+ toA . datumOption !== undefined
96+ ? pipe (
97+ ParseResult . encode ( DatumOption . CBORBytes ) ( toA . datumOption ) ,
98+ Effect . map ( ( encodedDatum ) => {
99+ map . set ( 2 , encodedDatum ) ;
100+ return map ;
101+ } ) ,
102+ )
103+ : Effect . succeed ( map ) ;
104+
105+ return pipe (
106+ datumOptionEffect ,
107+ Effect . map ( ( updatedMap ) => {
108+ if ( toA . scriptRef !== undefined ) {
109+ updatedMap . set ( 3 , toA . scriptRef ) ;
110+ }
111+ // Ensure we encode a Map, as per CDDL
112+ return CBOR . encodeAsBytesOrThrow ( updatedMap ) ;
113+ } ) ,
114+ ) ;
115+ } ) ,
116+ ) ,
117+ decode : ( fromA , options , ast , fromI ) =>
118+ pipe (
119+ CBOR . decodeBytes ( fromA ) ,
120+ Effect . mapError (
121+ ( e ) =>
122+ new ParseResult . Type (
123+ ast ,
124+ fromA ,
125+ `CBOR decoding failed: ${ e . message } ` ,
126+ ) ,
127+ ) ,
128+ Effect . flatMap ( ( decodedData ) => {
129+ const getField = ( key : number ) : unknown => {
130+ if ( decodedData instanceof Map ) {
131+ return decodedData . get ( key ) ;
132+ } else if (
133+ typeof decodedData === "object" &&
134+ decodedData !== null
135+ ) {
136+ return ( decodedData as Record < number , unknown > ) [ key ] ;
137+ }
138+ return undefined ; // If decodedData is neither expected type, return undefined
139+ } ;
140+
141+ const addressBytes = getField ( 0 ) ;
142+ const valueRaw = getField ( 1 ) ;
143+ const datumOptionBytes = getField ( 2 ) ;
144+ const scriptRefRaw = getField ( 3 ) ;
145+
146+ if ( addressBytes === undefined || valueRaw === undefined ) {
147+ return ParseResult . fail (
148+ new ParseResult . Type (
149+ ast ,
150+ fromA ,
151+ "Missing required fields (address (0) or value (1)) in decoded CBOR data for BabbageTransactionOutput." ,
152+ ) ,
153+ ) ;
92154 }
93155
94- if ( toA . scriptRef ) {
95- map . set ( 3 , toA . scriptRef ) ;
156+ if (
157+ ! ( decodedData instanceof Map ) &&
158+ ! ( typeof decodedData === "object" && decodedData !== null )
159+ ) {
160+ return ParseResult . fail (
161+ new ParseResult . Type (
162+ ast ,
163+ fromA ,
164+ `Expected a Map or a plain object for BabbageTransactionOutput, but got ${ typeof decodedData } .` ,
165+ ) ,
166+ ) ;
96167 }
97168
98- return CBOR . encodeAsBytesOrThrow ( map ) ;
169+ return Effect . gen ( function * ( ) {
170+ // Decode and validate each field using its respective schema
171+ const address = yield * ParseResult . decodeUnknown (
172+ Address . BytesSchema ,
173+ ) ( addressBytes ) ;
174+
175+ const value = yield * ParseResult . decodeUnknown (
176+ Schema . BigIntFromSelf ,
177+ ) ( valueRaw ) ;
178+
179+ const datumOption =
180+ datumOptionBytes !== undefined
181+ ? yield * ParseResult . decodeUnknown ( DatumOption . CBORBytes ) (
182+ datumOptionBytes ,
183+ )
184+ : undefined ;
185+
186+ const scriptRef =
187+ scriptRefRaw !== undefined
188+ ? yield * ParseResult . decodeUnknown ( Schema . Uint8ArrayFromSelf ) (
189+ scriptRefRaw ,
190+ )
191+ : undefined ;
192+
193+ return new BabbageTransactionOutput ( {
194+ address,
195+ value,
196+ datumOption,
197+ scriptRef,
198+ } ) ;
199+ } ) ;
99200 } ) ,
100201 ) ,
101- decode : ( fromA , options , ast , fromI ) =>
102- Effect . gen ( function * ( ) {
103- const decoded = yield * CBOR . decodeBytes ( fromA ) . pipe (
104- Effect . mapError (
105- ( error ) => new ParseResult . Type ( ast , fromA , error . message ) ,
106- ) ,
107- ) ;
108-
109- if ( ! ( decoded instanceof Map ) ) {
110- return yield * ParseResult . fail (
111- new ParseResult . Type ( ast , fromA , "Expected CBOR Map" ) ,
112- ) ;
113- }
114-
115- const addressBytes = decoded . get ( 0 ) as Uint8Array ;
116- const value = decoded . get ( 1 ) as bigint ;
117- const datumOption = decoded . get ( 2 ) as Uint8Array | undefined ;
118- const scriptRef = decoded . get ( 3 ) as Uint8Array | undefined ;
119-
120- const address = yield * ParseResult . decodeUnknown ( Address . Address ) (
121- addressBytes ,
122- ) ;
123-
124- return new BabbageTransactionOutput ( {
125- address,
126- value,
127- datumOption,
128- scriptRef,
129- } ) ;
130- } ) ,
131202 } ,
132203) ;
133204
134205/**
135- * Schema for transforming between CBOR hex and BabbageTransactionOutput
206+ * Schema for transforming between CBOR hex string and BabbageTransactionOutput.
207+ * This schema handles encoding BabbageTransactionOutput to a CBOR hex string
208+ * and decoding a CBOR hex string back into a BabbageTransactionOutput.
209+ *
210+ * @example
211+ * import { BabbageTransactionOutput } from "@lucid-evolution/experimental/BabbageTransactionOutput";
212+ * import * as Hex from "@lucid-evolution/experimental/Hex";
213+ * import { Effect } from "effect";
214+ * import assert from "assert";
215+ *
216+ * const exampleHex = "a400581c810477813a4362d26f6323a63339f4083a62885994b7c617b010000000000000001028200581ca55a305d233157b545d13783a649842f21950c41031c260386e81e370358204618e4740e53a31c51082c5f110753066d525287f3b8908f906f32e60000000000000003"; // A complex example
217+ * const cborHexResult = BabbageTransactionOutput.CBORHex.decode(Hex.makeOrThrow(exampleHex));
218+ * const decodedOutput = Effect.runSync(cborHexResult);
219+ *
220+ * assert(decodedOutput.address._tag === "BaseAddress");
221+ * assert(decodedOutput.value === 1n);
222+ * assert(decodedOutput.datumOption?._tag === "Hash");
223+ * assert(decodedOutput.scriptRef !== undefined);
136224 *
137225 * @since 2.0.0
138226 * @category encoding/decoding
@@ -154,6 +242,24 @@ export const CBORHex = Schema.transformOrFail(
154242/**
155243 * Check if two BabbageTransactionOutput instances are equal.
156244 *
245+ * @example
246+ * import { BabbageTransactionOutput } from "@lucid-evolution/experimental/BabbageTransactionOutput";
247+ * import * as Address from "@lucid-evolution/experimental/Address";
248+ * import * as Hex from "@lucid-evolution/experimental/Hex";
249+ * import assert from "assert";
250+ *
251+ * const addr1 = Address.BaseAddress.makeOrThrow({
252+ * payment: { _tag: "KeyHash", hash: Hex.makeOrThrow("c37b1b5dc0669f1d3c61a6fddb2e8fde96be87b881c60bce8e8d542f") },
253+ * stake: { _tag: "ScriptHash", hash: Hex.makeOrThrow("1a4a40d588523c1186716a505b3191f6368d40026e6d1c810c90d6e2") },
254+ * network: "Preview"
255+ * });
256+ * const output1 = new BabbageTransactionOutput({ address: addr1, value: 100n });
257+ * const output2 = new BabbageTransactionOutput({ address: addr1, value: 100n });
258+ * const output3 = new BabbageTransactionOutput({ address: addr1, value: 200n });
259+ *
260+ * assert(BabbageTransactionOutput.equals(output1, output2) === true);
261+ * assert(BabbageTransactionOutput.equals(output1, output3) === false);
262+ *
157263 * @since 2.0.0
158264 * @category equality
159265 */
@@ -171,15 +277,27 @@ export const equals = (
171277} ;
172278
173279/**
174- * Generator for creating random BabbageTransactionOutput instances for testing
280+ * Generate a random BabbageTransactionOutput instance for property-based testing.
281+ *
282+ * @example
283+ * import { BabbageTransactionOutput } from "@lucid-evolution/experimental/BabbageTransactionOutput";
284+ * import { FastCheck } from "effect";
285+ * import assert from "assert";
286+ *
287+ * const randomSamples = FastCheck.sample(BabbageTransactionOutput.generator, 5);
288+ * randomSamples.forEach((output) => {
289+ * assert(output instanceof BabbageTransactionOutput);
290+ * assert(output.value >= 1n && output.value <= 1000000000n);
291+ * // Further assertions on address, datumOption, scriptRef can be added
292+ * });
175293 *
176294 * @since 2.0.0
177295 * @category generators
178296 */
179297export const generator = FastCheck . tuple (
180298 Address . generator ,
181299 FastCheck . bigInt ( { min : 1n , max : 1000000000n } ) ,
182- FastCheck . option ( FastCheck . uint8Array ( { minLength : 1 , maxLength : 100 } ) ) ,
300+ FastCheck . option ( DatumOption . generator ) ,
183301 FastCheck . option ( FastCheck . uint8Array ( { minLength : 1 , maxLength : 200 } ) ) ,
184302) . map (
185303 ( [ address , value , datumOption , scriptRef ] ) =>
0 commit comments