@@ -5,22 +5,23 @@ import * as CBOR from "../CBOR.js"
55import * as Coin from "../Coin.js"
66import * as MultiAsset from "../MultiAsset.js"
77import * as PolicyId from "../PolicyId.js"
8- import * as PositiveCoin from "../PositiveCoin.js"
8+
99
1010/**
1111 * Assets representing both ADA and native tokens.
1212 *
13- * This is a simplified, unified structure where:
14- * - `coin` always represents the ADA/Lovelace amount
15- * - `multiAsset` optionally contains native tokens
13+ * This is a **base type** with no constraints on values.
14+ * Lovelace and token quantities can be positive, negative, or zero
15+ * to support arithmetic operations (merge, subtract, negate).
1616 *
17- * CDDL spec: `value = coin / [coin, multiasset<positive_coin>]`
17+ * Constraints (positive values) are applied at boundaries like
18+ * `TransactionOutput` where CDDL requires `value = coin / [coin, multiasset<positive_coin>]`.
1819 *
1920 * @since 2.0.0
2021 * @category model
2122 */
2223export class Assets extends Schema . Class < Assets > ( "Assets" ) ( {
23- lovelace : Coin . Coin ,
24+ lovelace : Schema . BigInt ,
2425 multiAsset : Schema . optional ( MultiAsset . MultiAsset )
2526} ) {
2627 toJSON ( ) {
@@ -60,15 +61,15 @@ export class Assets extends Schema.Class<Assets>("Assets")({
6061 * @since 2.0.0
6162 * @category constructors
6263 */
63- export const fromLovelace = ( lovelace : Coin . Coin ) : Assets => new Assets ( { lovelace } )
64+ export const fromLovelace = ( lovelace : bigint ) : Assets => new Assets ( { lovelace } )
6465
6566/**
6667 * Create Assets containing ADA and native tokens.
6768 *
6869 * @since 2.0.0
6970 * @category constructors
7071 */
71- export const withMultiAsset = ( lovelace : Coin . Coin , multiAsset : MultiAsset . MultiAsset ) : Assets =>
72+ export const withMultiAsset = ( lovelace : bigint , multiAsset : MultiAsset . MultiAsset ) : Assets =>
7273 new Assets ( { lovelace, multiAsset } )
7374
7475/**
@@ -80,8 +81,8 @@ export const withMultiAsset = (lovelace: Coin.Coin, multiAsset: MultiAsset.Multi
8081export const fromAsset = (
8182 policyId : PolicyId . PolicyId ,
8283 assetName : AssetName . AssetName ,
83- quantity : PositiveCoin . PositiveCoin ,
84- lovelace : Coin . Coin = 0n
84+ quantity : bigint ,
85+ lovelace : bigint = 0n
8586) : Assets => {
8687 const assetMap = new Map ( [ [ assetName , quantity ] ] )
8788 const multiAsset = new MultiAsset . MultiAsset ( { map : new Map ( [ [ policyId , assetMap ] ] ) } )
@@ -97,7 +98,7 @@ export const fromAsset = (
9798export const fromUnit = (
9899 unit : string ,
99100 quantity : bigint ,
100- lovelace : Coin . Coin = 0n
101+ lovelace : bigint = 0n
101102) : Eff . Effect < Assets , Error > =>
102103 Eff . gen ( function * ( ) {
103104 // Parse "policyId.assetName" or "policyId" (empty asset name)
@@ -115,7 +116,7 @@ export const fromUnit = (
115116 : new Uint8Array ( 0 )
116117 const assetName = new AssetName . AssetName ( { bytes : assetNameBytes } )
117118
118- return fromAsset ( policyId , assetName , quantity as PositiveCoin . PositiveCoin , lovelace )
119+ return fromAsset ( policyId , assetName , quantity , lovelace )
119120 } )
120121
121122/**
@@ -129,19 +130,48 @@ export const fromHexStrings = (
129130 policyIdHex : string ,
130131 assetNameHex : string ,
131132 quantity : bigint ,
132- lovelace : Coin . Coin = 0n
133+ lovelace : bigint = 0n
133134) : Assets => {
134- // Decode policy ID from hex (28 bytes = 56 hex chars)
135- const policyIdBytes = new Uint8Array ( Buffer . from ( policyIdHex , "hex" ) )
136- const policyId = new PolicyId . PolicyId ( { hash : policyIdBytes } )
135+ // Use schema-validated parsers (will throw on invalid hex/length)
136+ const policyId = PolicyId . fromHex ( policyIdHex )
137+ const assetName = AssetName . fromHex ( assetNameHex )
138+
139+ return fromAsset ( policyId , assetName , quantity , lovelace )
140+ }
141+
142+ /**
143+ * Create Assets from a record format (for convenience/testing).
144+ *
145+ * Record format:
146+ * - `lovelace`: bigint for ADA amount
147+ * - `"<policyIdHex><assetNameHex>"`: bigint for native asset quantity
148+ * where policyId is exactly 56 hex chars and assetName is remaining hex chars
149+ *
150+ * @example
151+ * ```ts
152+ * const assets = fromRecord({
153+ * lovelace: 5_000_000n,
154+ * "aabbcc...def456aabbccdd": 100n // 56 char policyId hex + assetName hex
155+ * })
156+ * ```
157+ *
158+ * @since 2.0.0
159+ * @category constructors
160+ */
161+ export const fromRecord = ( record : Record < string , bigint > ) : Assets => {
162+ let result = fromLovelace ( record . lovelace ?? 0n )
137163
138- // Decode asset name from hex (empty string yields empty bytes)
139- const assetNameBytes = assetNameHex
140- ? new Uint8Array ( Buffer . from ( assetNameHex , "hex" ) )
141- : new Uint8Array ( 0 )
142- const assetName = new AssetName . AssetName ( { bytes : assetNameBytes } )
164+ for ( const [ key , value ] of Object . entries ( record ) ) {
165+ if ( key === "lovelace" ) continue
166+
167+ // First 56 chars are policyId, rest is assetName
168+ // Schema validation in addByHex will handle invalid inputs
169+ const policyIdHex = key . slice ( 0 , 56 )
170+ const assetNameHex = key . slice ( 56 )
171+ result = addByHex ( result , policyIdHex , assetNameHex , value )
172+ }
143173
144- return fromAsset ( policyId , assetName , quantity as PositiveCoin . PositiveCoin , lovelace )
174+ return result
145175}
146176
147177/**
@@ -162,7 +192,7 @@ export const zero: Assets = new Assets({ lovelace: 0n })
162192 * @since 2.0.0
163193 * @category inspection
164194 */
165- export const lovelaceOf = ( assets : Assets ) : Coin . Coin => assets . lovelace
195+ export const lovelaceOf = ( assets : Assets ) : bigint => assets . lovelace
166196
167197/**
168198 * Check if Assets contains native tokens.
@@ -188,6 +218,30 @@ export const getMultiAsset = (assets: Assets): MultiAsset.MultiAsset | undefined
188218 */
189219export const isZero = ( assets : Assets ) : boolean => assets . lovelace === 0n && ! hasMultiAsset ( assets )
190220
221+ /**
222+ * Check if all quantities are positive (lovelace >= 0, tokens > 0).
223+ * Used for validation at transaction output boundaries per CDDL:
224+ * `value = coin / [coin, multiasset<positive_coin>]`
225+ *
226+ * @since 2.0.0
227+ * @category inspection
228+ */
229+ export const allPositive = ( assets : Assets ) : boolean => {
230+ // Lovelace must be non-negative
231+ if ( assets . lovelace < 0n ) return false
232+
233+ // All token quantities must be positive
234+ if ( assets . multiAsset ) {
235+ for ( const [ , assetMap ] of assets . multiAsset . map . entries ( ) ) {
236+ for ( const [ , quantity ] of assetMap . entries ( ) ) {
237+ if ( quantity <= 0n ) return false
238+ }
239+ }
240+ }
241+
242+ return true
243+ }
244+
191245/**
192246 * Get quantity of a specific asset.
193247 *
@@ -244,7 +298,7 @@ export const tokens = (assets: Assets, policyId: PolicyId.PolicyId): Map<AssetNa
244298 * @category combining
245299 */
246300export const merge = ( a : Assets , b : Assets ) : Assets => {
247- const totalLovelace = Coin . add ( a . lovelace , b . lovelace )
301+ const totalLovelace = a . lovelace + b . lovelace
248302
249303 // Both have no multiAsset
250304 if ( ! a . multiAsset && ! b . multiAsset ) {
@@ -282,7 +336,7 @@ export const add = (
282336 assetName : AssetName . AssetName ,
283337 quantity : bigint
284338) : Assets => {
285- const toAdd = fromAsset ( policyId , assetName , quantity as PositiveCoin . PositiveCoin , 0n )
339+ const toAdd = fromAsset ( policyId , assetName , quantity , 0n )
286340 return merge ( assets , toAdd )
287341}
288342
@@ -317,9 +371,9 @@ export const negate = (assets: Assets): Assets => {
317371
318372 const negatedMap = new Map < PolicyId . PolicyId , MultiAsset . AssetMap > ( )
319373 for ( const [ policyId , assetMap ] of assets . multiAsset . map . entries ( ) ) {
320- const negatedAssets = new Map < AssetName . AssetName , PositiveCoin . PositiveCoin > ( )
374+ const negatedAssets = new Map < AssetName . AssetName , bigint > ( )
321375 for ( const [ assetName , quantity ] of assetMap . entries ( ) ) {
322- negatedAssets . set ( assetName , - quantity as PositiveCoin . PositiveCoin )
376+ negatedAssets . set ( assetName , - quantity )
323377 }
324378 negatedMap . set ( policyId , negatedAssets )
325379 }
@@ -349,7 +403,7 @@ export const withoutLovelace = (assets: Assets): Assets => {
349403 * @since 2.0.0
350404 * @category combining
351405 */
352- export const withLovelace = ( assets : Assets , lovelace : Coin . Coin ) : Assets => {
406+ export const withLovelace = ( assets : Assets , lovelace : bigint ) : Assets => {
353407 return new Assets ( { lovelace, multiAsset : assets . multiAsset } )
354408}
355409
@@ -359,7 +413,7 @@ export const withLovelace = (assets: Assets, lovelace: Coin.Coin): Assets => {
359413 * @since 2.0.0
360414 * @category combining
361415 */
362- export const addLovelace = ( assets : Assets , additionalLovelace : Coin . Coin ) : Assets => {
416+ export const addLovelace = ( assets : Assets , additionalLovelace : bigint ) : Assets => {
363417 return new Assets ( { lovelace : assets . lovelace + additionalLovelace , multiAsset : assets . multiAsset } )
364418}
365419
@@ -369,7 +423,7 @@ export const addLovelace = (assets: Assets, additionalLovelace: Coin.Coin): Asse
369423 * @since 2.0.0
370424 * @category combining
371425 */
372- export const subtractLovelace = ( assets : Assets , lovelaceToSubtract : Coin . Coin ) : Assets => {
426+ export const subtractLovelace = ( assets : Assets , lovelaceToSubtract : bigint ) : Assets => {
373427 return new Assets ( { lovelace : assets . lovelace - lovelaceToSubtract , multiAsset : assets . multiAsset } )
374428}
375429
@@ -402,7 +456,7 @@ export const filter = (assets: Assets, predicate: (unit: string, amount: bigint)
402456 const filteredMap = new Map < PolicyId . PolicyId , MultiAsset . AssetMap > ( )
403457 for ( const [ policyId , assetMap ] of assets . multiAsset . map . entries ( ) ) {
404458 const policyIdHex = PolicyId . toHex ( policyId )
405- const filteredAssets = new Map < AssetName . AssetName , PositiveCoin . PositiveCoin > ( )
459+ const filteredAssets = new Map < AssetName . AssetName , bigint > ( )
406460 for ( const [ assetName , quantity ] of assetMap . entries ( ) ) {
407461 const assetNameHex = AssetName . toHex ( assetName )
408462 const unit = assetNameHex ? `${ policyIdHex } .${ assetNameHex } ` : policyIdHex
@@ -507,9 +561,12 @@ export const getUnits = (assets: Assets): Array<string> => {
507561 if ( assets . multiAsset ) {
508562 for ( const [ policyId , assetMap ] of assets . multiAsset . map . entries ( ) ) {
509563 const policyIdHex = PolicyId . toHex ( policyId )
510- for ( const [ assetName ] of assetMap . entries ( ) ) {
511- const assetNameHex = AssetName . toHex ( assetName )
512- units . push ( `${ policyIdHex } ${ assetNameHex } ` )
564+ for ( const [ assetName , amount ] of assetMap . entries ( ) ) {
565+ // Only include units with non-zero amounts
566+ if ( amount !== 0n ) {
567+ const assetNameHex = AssetName . toHex ( assetName )
568+ units . push ( `${ policyIdHex } ${ assetNameHex } ` )
569+ }
513570 }
514571 }
515572 }
@@ -644,11 +701,10 @@ export const FromCDDL = Schema.transformOrFail(CDDLSchema, Schema.typeSchema(Ass
644701 for ( const [ policyIdBytes , assetMapCddl ] of multiAssetCddl . entries ( ) ) {
645702 const policyId = yield * ParseResult . decode ( PolicyId . FromBytes ) ( policyIdBytes )
646703
647- const assetMap = new Map < AssetName . AssetName , PositiveCoin . PositiveCoin > ( )
704+ const assetMap = new Map < AssetName . AssetName , bigint > ( )
648705 for ( const [ assetNameBytes , amount ] of assetMapCddl . entries ( ) ) {
649706 const assetName = yield * ParseResult . decode ( AssetName . FromBytes ) ( assetNameBytes )
650- const positiveCoin = yield * ParseResult . decodeUnknown ( Schema . typeSchema ( PositiveCoin . PositiveCoinSchema ) ) ( amount )
651- assetMap . set ( assetName , positiveCoin )
707+ assetMap . set ( assetName , amount )
652708 }
653709
654710 result . set ( policyId , assetMap )
0 commit comments