@@ -270,19 +270,79 @@ export const coalesceChangeBundlesForMinCoinRequirement = (
270
270
return sortedBundles . filter ( ( bundle ) => bundle . coins > 0n || ( bundle . assets ?. size || 0 ) > 0 ) ;
271
271
} ;
272
272
273
+ /**
274
+ * Splits change bundles if the token bundle size exceeds the specified limit. Each bundle is checked,
275
+ * and if it exceeds the limit, it's split into smaller bundles such that each conforms to the limit.
276
+ * It also ensures that each bundle has a minimum coin quantity.
277
+ *
278
+ * @param changeBundles - The array of change bundles, each containing assets and their quantities.
279
+ * @param computeMinimumCoinQuantity - A function to compute the minimum coin quantity required for a transaction output.
280
+ * @param tokenBundleSizeExceedsLimit - A function to determine if the token bundle size of a set of assets exceeds a predefined limit.
281
+ * @returns The array of adjusted change bundles, conforming to the token bundle size limits and each having the necessary minimum coin quantity.
282
+ * @throws Throws an error if the total coin amount is fully depleted and cannot cover the minimum required coin quantity.
283
+ */
284
+ const splitChangeIfTokenBundlesSizeExceedsLimit = (
285
+ changeBundles : Cardano . Value [ ] ,
286
+ computeMinimumCoinQuantity : ComputeMinimumCoinQuantity ,
287
+ tokenBundleSizeExceedsLimit : TokenBundleSizeExceedsLimit
288
+ ) : Cardano . Value [ ] => {
289
+ const result : Cardano . Value [ ] = [ ] ;
290
+
291
+ for ( const bundle of changeBundles ) {
292
+ const { assets, coins } = bundle ;
293
+ if ( ! assets || assets . size === 0 || ! tokenBundleSizeExceedsLimit ( assets ) ) {
294
+ result . push ( { assets, coins } ) ;
295
+ continue ;
296
+ }
297
+
298
+ const newValues = [ ] ;
299
+ let newValue = { assets : new Map ( ) , coins : 0n } ;
300
+
301
+ for ( const [ assetId , quantity ] of assets . entries ( ) ) {
302
+ newValue . assets . set ( assetId , quantity ) ;
303
+
304
+ if ( tokenBundleSizeExceedsLimit ( newValue . assets ) && newValue . assets . size > 1 ) {
305
+ newValue . assets . delete ( assetId ) ;
306
+ newValues . push ( newValue ) ;
307
+ newValue = { assets : new Map ( [ [ assetId , quantity ] ] ) , coins : 0n } ;
308
+ }
309
+ }
310
+
311
+ newValues . push ( newValue ) ;
312
+
313
+ let totalMinCoin = 0n ;
314
+ for ( const value of newValues ) {
315
+ const minCoin = computeMinimumCoinQuantity ( { address : stubMaxSizeAddress , value } ) ;
316
+ value . coins = minCoin ;
317
+ totalMinCoin += minCoin ;
318
+ }
319
+
320
+ if ( coins < totalMinCoin ) {
321
+ throw new InputSelectionError ( InputSelectionFailure . UtxoFullyDepleted ) ;
322
+ }
323
+
324
+ newValues [ 0 ] . coins += coins - totalMinCoin ;
325
+ result . push ( ...newValues ) ;
326
+ }
327
+
328
+ return result ;
329
+ } ;
330
+
273
331
const computeChangeBundles = ( {
274
332
utxoSelection,
275
333
outputValues,
276
334
uniqueTxAssetIDs,
277
335
implicitValue,
278
336
computeMinimumCoinQuantity,
337
+ tokenBundleSizeExceedsLimit,
279
338
fee = 0n
280
339
} : {
281
340
utxoSelection : UtxoSelection ;
282
341
outputValues : Cardano . Value [ ] ;
283
342
uniqueTxAssetIDs : Cardano . AssetId [ ] ;
284
343
implicitValue : RequiredImplicitValue ;
285
344
computeMinimumCoinQuantity : ComputeMinimumCoinQuantity ;
345
+ tokenBundleSizeExceedsLimit : TokenBundleSizeExceedsLimit ;
286
346
fee ?: bigint ;
287
347
} ) : ( UtxoSelection & { changeBundles : Cardano . Value [ ] } ) | false => {
288
348
const requestedAssetChangeBundles = computeRequestedAssetChangeBundles (
@@ -304,7 +364,17 @@ const computeChangeBundles = ({
304
364
if ( ! changeBundles ) {
305
365
return false ;
306
366
}
307
- return { changeBundles, ...utxoSelection } ;
367
+
368
+ // Make sure the change outputs do not exceed token bundle size limit, this can happen if the UTXO set
369
+ // has too many assets and the selection strategy selects enough of them to violates this constraint for the resulting
370
+ // change output set.
371
+ const adjustedChange = splitChangeIfTokenBundlesSizeExceedsLimit (
372
+ changeBundles ,
373
+ computeMinimumCoinQuantity ,
374
+ tokenBundleSizeExceedsLimit
375
+ ) ;
376
+
377
+ return { changeBundles : adjustedChange , ...utxoSelection } ;
308
378
} ;
309
379
310
380
const validateChangeBundles = (
@@ -365,6 +435,7 @@ export const computeChangeAndAdjustForFee = async ({
365
435
computeMinimumCoinQuantity,
366
436
implicitValue,
367
437
outputValues,
438
+ tokenBundleSizeExceedsLimit,
368
439
uniqueTxAssetIDs,
369
440
utxoSelection
370
441
} ) ;
@@ -395,6 +466,7 @@ export const computeChangeAndAdjustForFee = async ({
395
466
fee : estimatedCosts . fee ,
396
467
implicitValue,
397
468
outputValues,
469
+ tokenBundleSizeExceedsLimit,
398
470
uniqueTxAssetIDs,
399
471
utxoSelection : pick ( selectionWithChangeAndFee , [ 'utxoRemaining' , 'utxoSelected' ] )
400
472
} ) ;
0 commit comments