1+ import { Option } from "effect"
2+
3+ import * as AssetName from "../core/AssetName.js"
4+ import * as Coin from "../core/Coin.js"
5+ import * as MultiAsset from "../core/MultiAsset.js"
6+ import * as CorePolicyId from "../core/PolicyId.js"
7+ import * as PositiveCoin from "../core/PositiveCoin.js"
8+ import * as CoreValue from "../core/Value.js"
9+ import * as Unit from "./Unit.js"
10+
111export interface Assets {
212 lovelace : bigint
313 [ key : string ] : bigint
414}
515
16+ /**
17+ * Sort assets according to CBOR canonical ordering rules (RFC 7049 section 3.9).
18+ * Lovelace comes first, then assets sorted by policy ID length, then lexicographically.
19+ */
20+ export const sortCanonical = ( assets : Assets ) : Assets => {
21+ const entries = Object . entries ( assets ) . sort ( ( [ aUnit ] , [ bUnit ] ) => {
22+ const a = Unit . fromUnit ( aUnit )
23+ const b = Unit . fromUnit ( bUnit )
24+
25+ // Compare policy lengths
26+ // NOTE: all policies have the same length, because they must be 28 bytes
27+ // but because Lovelace is in the assets we must compare the length
28+ if ( a . policyId . length !== b . policyId . length ) {
29+ return a . policyId . length - b . policyId . length
30+ }
31+
32+ // If policy IDs are the same, compare asset names length
33+ if ( a . policyId === b . policyId ) {
34+ const aAssetName = a . assetName || ""
35+ const bAssetName = b . assetName || ""
36+ if ( aAssetName . length !== bAssetName . length ) {
37+ return aAssetName . length - bAssetName . length
38+ }
39+ // If asset names have same length, compare them lexicographically
40+ return aAssetName . localeCompare ( bAssetName )
41+ }
42+
43+ // If policy IDs have same length but are different, compare them lexicographically
44+ return a . policyId . localeCompare ( b . policyId )
45+ } )
46+
47+ return Object . fromEntries ( entries ) as Assets
48+ }
49+
50+ /**
51+ * Multiply all asset amounts by a factor.
52+ * Useful for calculating fees, rewards, or scaling asset amounts.
53+ */
54+ export const multiply = ( assets : Assets , factor : bigint ) : Assets => {
55+ const result : Record < string , bigint > = { lovelace : assets . lovelace * factor }
56+
57+ for ( const [ unit , amount ] of Object . entries ( assets ) ) {
58+ if ( unit !== "lovelace" ) {
59+ result [ unit ] = amount * factor
60+ }
61+ }
62+
63+ return result as Assets
64+ }
65+
66+ /**
67+ * Negate all asset amounts.
68+ * Useful for calculating what needs to be subtracted or for representing debts.
69+ */
70+ export const negate = ( assets : Assets ) : Assets => {
71+ const result : Record < string , bigint > = { lovelace : - assets . lovelace }
72+
73+ for ( const [ unit , amount ] of Object . entries ( assets ) ) {
74+ if ( unit !== "lovelace" ) {
75+ result [ unit ] = - amount
76+ }
77+ }
78+
79+ return result as Assets
80+ }
81+
682// Constructor and factory functions
783export const make = ( lovelace : bigint , tokens : Record < string , bigint > = { } ) : Assets => ( {
884 lovelace,
@@ -91,19 +167,6 @@ export const hasAsset = (assets: Assets, unit: string): boolean => {
91167 return unit in assets && assets [ unit ] > 0n
92168}
93169
94- export const isValid = ( assets : Assets ) : boolean => {
95- try {
96- // Check if it has required lovelace field and all values are bigint
97- if ( typeof assets . lovelace !== "bigint" ) return false
98- for ( const [ unit , amount ] of Object . entries ( assets ) ) {
99- if ( unit !== "lovelace" && typeof amount !== "bigint" ) return false
100- }
101- return true
102- } catch {
103- return false
104- }
105- }
106-
107170export const isEmpty = ( assets : Assets ) : boolean => {
108171 if ( assets . lovelace > 0n ) return false
109172 for ( const [ unit , amount ] of Object . entries ( assets ) ) {
@@ -120,16 +183,78 @@ export const getUnits = (assets: Assets): Array<string> => {
120183 return units
121184}
122185
123- // String representation
124- export const toString = ( assets : Assets ) : string => {
125- const tokens = [ ]
126- tokens . push ( `lovelace: ${ assets . lovelace . toString ( ) } ` )
186+ /**
187+ * Convert a core Value to the Assets interface format.
188+ */
189+ export const valueToAssets = ( value : CoreValue . Value ) : Assets => {
190+ const assets : Assets = { lovelace : 0n }
127191
128- for ( const [ unit , amount ] of Object . entries ( assets ) ) {
129- if ( unit !== "lovelace" ) {
130- tokens . push ( `${ unit } : ${ amount . toString ( ) } ` )
192+ // Add ADA (lovelace) from the Value
193+ const adaAmount = CoreValue . getAda ( value )
194+ assets . lovelace = BigInt ( adaAmount . toString ( ) )
195+
196+ // Get MultiAsset if it exists
197+ const multiAsset = CoreValue . getAssets ( value )
198+ if ( Option . isSome ( multiAsset ) ) {
199+ // Iterate through all policy IDs
200+ const policyIds = MultiAsset . getPolicyIds ( multiAsset . value )
201+
202+ for ( const policyId of policyIds ) {
203+ const policyIdStr = CorePolicyId . toHex ( policyId )
204+ const assetsByPolicy = MultiAsset . getAssetsByPolicy ( multiAsset . value , policyId )
205+
206+ for ( const [ assetName , amount ] of assetsByPolicy ) {
207+ const assetNameStr = AssetName . toHex ( assetName )
208+ const unit = policyIdStr + assetNameStr
209+ assets [ unit ] = BigInt ( amount . toString ( ) )
210+ }
211+ }
212+ }
213+
214+ return assets
215+ }
216+
217+ /**
218+ * Convert Assets interface format to a core Value.
219+ */
220+ export const assetsToValue = ( assets : Assets ) : CoreValue . Value => {
221+ // Extract ADA amount (lovelace key)
222+ const adaAmount = assets . lovelace || BigInt ( 0 )
223+ const coin = Coin . make ( adaAmount )
224+
225+ // Filter out ADA to get only native assets
226+ const nativeAssets = Object . entries ( assets ) . filter ( ( [ unit ] ) => unit !== "lovelace" )
227+
228+ if ( nativeAssets . length === 0 ) {
229+ // Only ADA, return OnlyCoin
230+ return CoreValue . onlyCoin ( coin )
231+ }
232+
233+ // Build MultiAsset
234+ const multiAssetMap = MultiAsset . empty ( )
235+
236+ for ( const [ unit , amount ] of nativeAssets ) {
237+ const { assetName, policyId } = Unit . fromUnit ( unit )
238+ const positiveAmount = PositiveCoin . make ( amount )
239+
240+ // Create core policy ID from hex string
241+ const corePolicyId = CorePolicyId . fromHex ( policyId )
242+ // Create core asset name from hex string (or empty if undefined)
243+ const coreAssetName = AssetName . fromHex ( assetName || "" )
244+
245+ // Get or create policy map
246+ let policyMap = multiAssetMap . get ( corePolicyId )
247+ if ( ! policyMap ) {
248+ policyMap = new Map ( )
249+ multiAssetMap . set ( corePolicyId , policyMap )
131250 }
251+
252+ // Add asset to policy map
253+ policyMap . set ( coreAssetName , positiveAmount )
132254 }
133255
134- return `{ ${ tokens . join ( ", " ) } }`
256+ // Create the MultiAsset using the make function
257+ const multiAsset = MultiAsset . make ( multiAssetMap )
258+
259+ return CoreValue . withAssets ( coin , multiAsset )
135260}
0 commit comments