Skip to content

Commit 796c82c

Browse files
committed
feat: create core utxo
1 parent 87b117f commit 796c82c

File tree

2 files changed

+340
-0
lines changed

2 files changed

+340
-0
lines changed
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
import type { ParseResult} from "effect";
2+
import { Effect, Equal, Hash, HashSet, Inspectable, Schema } from "effect"
3+
4+
import type * as SDKAssets from "../sdk/Assets.js"
5+
import type * as SDKDatum from "../sdk/Datum.js"
6+
import type * as SDKScript from "../sdk/Script.js"
7+
import type * as SDKUTxO from "../sdk/UTxO.js"
8+
import * as Address from "./Address.js"
9+
import * as Assets from "./Assets/index.js"
10+
import * as Bytes32 from "./Bytes32.js"
11+
import * as PlutusData from "./Data.js"
12+
import * as DatumOption from "./DatumOption.js"
13+
import * as Numeric from "./Numeric.js"
14+
import * as ScriptRef from "./ScriptRef.js"
15+
import * as TransactionHash from "./TransactionHash.js"
16+
17+
/**
18+
* UTxO (Unspent Transaction Output) - A transaction output with its on-chain reference.
19+
*
20+
* Combines TransactionOutput with the transaction reference (transactionId + index)
21+
* that uniquely identifies it on the blockchain.
22+
*
23+
* @since 2.0.0
24+
* @category model
25+
*/
26+
export class UTxO extends Schema.TaggedClass<UTxO>()("UTxO", {
27+
transactionId: TransactionHash.TransactionHash,
28+
index: Numeric.Uint16Schema,
29+
address: Address.Address,
30+
assets: Assets.Assets,
31+
datumOption: Schema.optional(DatumOption.DatumOptionSchema),
32+
scriptRef: Schema.optional(ScriptRef.ScriptRef)
33+
}) {
34+
toJSON() {
35+
return {
36+
_tag: this._tag,
37+
transactionId: this.transactionId.toJSON(),
38+
index: this.index.toString(),
39+
address: this.address.toJSON(),
40+
assets: this.assets.toJSON(),
41+
datumOption: this.datumOption?.toJSON(),
42+
scriptRef: this.scriptRef?.toJSON()
43+
}
44+
}
45+
46+
toString(): string {
47+
return Inspectable.format(this.toJSON())
48+
}
49+
50+
[Inspectable.NodeInspectSymbol](): unknown {
51+
return this.toJSON()
52+
}
53+
54+
[Equal.symbol](that: unknown): boolean {
55+
return (
56+
that instanceof UTxO &&
57+
Equal.equals(this.transactionId, that.transactionId) &&
58+
this.index === that.index &&
59+
Equal.equals(this.address, that.address) &&
60+
Equal.equals(this.assets, that.assets) &&
61+
Equal.equals(this.datumOption, that.datumOption) &&
62+
Equal.equals(this.scriptRef, that.scriptRef)
63+
)
64+
}
65+
66+
[Hash.symbol](): number {
67+
return Hash.cached(this, Hash.combine(Hash.hash(this.transactionId))(Hash.number(Number(this.index))))
68+
}
69+
}
70+
71+
/**
72+
* Check if the given value is a valid UTxO.
73+
*
74+
* @since 2.0.0
75+
* @category predicates
76+
*/
77+
export const isUTxO = Schema.is(UTxO)
78+
79+
// =============================================================================
80+
// UTxO Set (Collection)
81+
// =============================================================================
82+
83+
/**
84+
* A set of UTxOs with efficient lookups and set operations.
85+
* Uses Effect's HashSet for automatic deduplication via Hash protocol.
86+
*
87+
* @since 2.0.0
88+
* @category models
89+
*/
90+
export type UTxOSet = HashSet.HashSet<UTxO>
91+
92+
/**
93+
* Create an empty UTxO set.
94+
*
95+
* @since 2.0.0
96+
* @category constructors
97+
*/
98+
export const empty = (): UTxOSet => HashSet.empty()
99+
100+
/**
101+
* Create a UTxO set from an iterable of UTxOs.
102+
*
103+
* @since 2.0.0
104+
* @category constructors
105+
*/
106+
export const fromIterable = (utxos: Iterable<UTxO>): UTxOSet => HashSet.fromIterable(utxos)
107+
108+
/**
109+
* Add a UTxO to the set.
110+
*
111+
* @since 2.0.0
112+
* @category combinators
113+
*/
114+
export const add = (set: UTxOSet, utxo: UTxO): UTxOSet => HashSet.add(set, utxo)
115+
116+
/**
117+
* Remove a UTxO from the set.
118+
*
119+
* @since 2.0.0
120+
* @category combinators
121+
*/
122+
export const remove = (set: UTxOSet, utxo: UTxO): UTxOSet => HashSet.remove(set, utxo)
123+
124+
/**
125+
* Check if a UTxO exists in the set.
126+
*
127+
* @since 2.0.0
128+
* @category predicates
129+
*/
130+
export const has = (set: UTxOSet, utxo: UTxO): boolean => HashSet.has(set, utxo)
131+
132+
/**
133+
* Union of two UTxO sets.
134+
*
135+
* @since 2.0.0
136+
* @category combinators
137+
*/
138+
export const union = (a: UTxOSet, b: UTxOSet): UTxOSet => HashSet.union(a, b)
139+
140+
/**
141+
* Intersection of two UTxO sets.
142+
*
143+
* @since 2.0.0
144+
* @category combinators
145+
*/
146+
export const intersection = (a: UTxOSet, b: UTxOSet): UTxOSet => HashSet.intersection(a, b)
147+
148+
/**
149+
* Difference of two UTxO sets (elements in a but not in b).
150+
*
151+
* @since 2.0.0
152+
* @category combinators
153+
*/
154+
export const difference = (a: UTxOSet, b: UTxOSet): UTxOSet => HashSet.difference(a, b)
155+
156+
/**
157+
* Filter UTxOs in the set by predicate.
158+
*
159+
* @since 2.0.0
160+
* @category combinators
161+
*/
162+
export const filter = (set: UTxOSet, predicate: (utxo: UTxO) => boolean): UTxOSet =>
163+
HashSet.filter(set, predicate)
164+
165+
/**
166+
* Get the number of UTxOs in the set.
167+
*
168+
* @since 2.0.0
169+
* @category getters
170+
*/
171+
export const size = (set: UTxOSet): number => HashSet.size(set)
172+
173+
/**
174+
* Check if the set is empty.
175+
*
176+
* @since 2.0.0
177+
* @category predicates
178+
*/
179+
export const isEmpty = (set: UTxOSet): boolean => HashSet.size(set) === 0
180+
181+
/**
182+
* Convert a UTxO set to an array.
183+
*
184+
* @since 2.0.0
185+
* @category conversions
186+
*/
187+
export const toArray = (set: UTxOSet): Array<UTxO> => Array.from(set)
188+
189+
// =============================================================================
190+
// SDK Conversion Utilities
191+
// =============================================================================
192+
193+
/**
194+
* Convert SDK DatumOption to Core DatumOption.
195+
*
196+
* @since 2.0.0
197+
* @category conversions
198+
*/
199+
export const datumOptionFromSDK = (
200+
datum: SDKDatum.Datum
201+
): Effect.Effect<DatumOption.DatumOption, ParseResult.ParseError> =>
202+
Effect.gen(function* () {
203+
if (datum.type === "inlineDatum") {
204+
const plutusData = yield* Schema.decodeUnknown(PlutusData.FromCBORHex())(datum.inline)
205+
return new DatumOption.InlineDatum({ data: plutusData })
206+
}
207+
// datumHash
208+
const hashBytes = yield* Schema.decodeUnknown(Bytes32.BytesFromHex)(datum.hash)
209+
return new DatumOption.DatumHash({ hash: hashBytes })
210+
})
211+
212+
/**
213+
* Convert Core DatumOption to SDK Datum.
214+
*
215+
* @since 2.0.0
216+
* @category conversions
217+
*/
218+
export const datumOptionToSDK = (datumOption: DatumOption.DatumOption): SDKDatum.Datum => {
219+
if (datumOption._tag === "InlineDatum") {
220+
return {
221+
type: "inlineDatum",
222+
inline: PlutusData.toCBORHex(datumOption.data)
223+
}
224+
}
225+
// DatumHash
226+
return {
227+
type: "datumHash",
228+
hash: Bytes32.toHex(datumOption.hash)
229+
}
230+
}
231+
232+
/**
233+
* Convert SDK Script to Core ScriptRef.
234+
*
235+
* @since 2.0.0
236+
* @category conversions
237+
*/
238+
export const scriptRefFromSDK = (
239+
script: SDKScript.Script
240+
): Effect.Effect<ScriptRef.ScriptRef, ParseResult.ParseError> =>
241+
Schema.decodeUnknown(ScriptRef.FromHex)(script.script)
242+
243+
/**
244+
* Convert Core ScriptRef to SDK Script type string.
245+
* Note: We lose the script type information as ScriptRef only stores bytes.
246+
*
247+
* @since 2.0.0
248+
* @category conversions
249+
*/
250+
export const scriptRefToSDKHex = (scriptRef: ScriptRef.ScriptRef): string =>
251+
Schema.encodeSync(Schema.Uint8ArrayFromHex)(scriptRef.bytes)
252+
253+
/**
254+
* Convert SDK UTxO to Core UTxO.
255+
*
256+
* @since 2.0.0
257+
* @category conversions
258+
*/
259+
export const fromSDK = (
260+
utxo: SDKUTxO.UTxO,
261+
toCoreAssets: (assets: SDKAssets.Assets) => Assets.Assets
262+
): Effect.Effect<UTxO, ParseResult.ParseError> =>
263+
Effect.gen(function* () {
264+
// Parse transaction hash
265+
const transactionId = yield* Schema.decodeUnknown(TransactionHash.FromHex)(utxo.txHash)
266+
267+
// Parse address from bech32
268+
const address = yield* Schema.decodeUnknown(Address.FromBech32)(utxo.address)
269+
270+
// Convert assets
271+
const assets = toCoreAssets(utxo.assets)
272+
273+
// Convert datum if present
274+
const datumOption = utxo.datumOption ? yield* datumOptionFromSDK(utxo.datumOption) : undefined
275+
276+
// Convert script ref if present
277+
const scriptRef = utxo.scriptRef ? yield* scriptRefFromSDK(utxo.scriptRef) : undefined
278+
279+
return new UTxO({
280+
transactionId,
281+
index: BigInt(utxo.outputIndex),
282+
address,
283+
assets,
284+
datumOption,
285+
scriptRef
286+
})
287+
})
288+
289+
/**
290+
* Convert Core UTxO to SDK UTxO.
291+
*
292+
* @since 2.0.0
293+
* @category conversions
294+
*/
295+
export const toSDK = (
296+
utxo: UTxO,
297+
fromCoreAssets: (assets: Assets.Assets) => SDKAssets.Assets
298+
): SDKUTxO.UTxO => ({
299+
txHash: TransactionHash.toHex(utxo.transactionId),
300+
outputIndex: Number(utxo.index),
301+
address: Schema.encodeSync(Address.FromBech32)(utxo.address),
302+
assets: fromCoreAssets(utxo.assets),
303+
datumOption: utxo.datumOption ? datumOptionToSDK(utxo.datumOption) : undefined,
304+
scriptRef: utxo.scriptRef
305+
? {
306+
type: "PlutusV3" as const, // Default type - we lose type info in ScriptRef
307+
script: scriptRefToSDKHex(utxo.scriptRef)
308+
}
309+
: undefined
310+
})
311+
312+
/**
313+
* Convert an array of SDK UTxOs to a Core UTxOSet.
314+
*
315+
* @since 2.0.0
316+
* @category conversions
317+
*/
318+
export const fromSDKArray = (
319+
utxos: ReadonlyArray<SDKUTxO.UTxO>,
320+
toCoreAssets: (assets: SDKAssets.Assets) => Assets.Assets
321+
): Effect.Effect<UTxOSet, ParseResult.ParseError> =>
322+
Effect.gen(function* () {
323+
const coreUtxos: Array<UTxO> = []
324+
for (const utxo of utxos) {
325+
coreUtxos.push(yield* fromSDK(utxo, toCoreAssets))
326+
}
327+
return fromIterable(coreUtxos)
328+
})
329+
330+
/**
331+
* Convert a Core UTxOSet to an array of SDK UTxOs.
332+
*
333+
* @since 2.0.0
334+
* @category conversions
335+
*/
336+
export const toSDKArray = (
337+
set: UTxOSet,
338+
fromCoreAssets: (assets: Assets.Assets) => SDKAssets.Assets
339+
): Array<SDKUTxO.UTxO> => toArray(set).map((utxo) => toSDK(utxo, fromCoreAssets))

packages/evolution/src/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export * as TSchema from "./TSchema.js"
110110
export * as UnitInterval from "./UnitInterval.js"
111111
export * as UPLC from "./uplc/index.js"
112112
export * as Url from "./Url.js"
113+
export * as UTxO from "./UTxO.js"
113114
export * as Value from "./Value.js"
114115
export * as VKey from "./VKey.js"
115116
export * as VotingProcedures from "./VotingProcedures.js"

0 commit comments

Comments
 (0)