@@ -270,19 +270,79 @@ export const coalesceChangeBundlesForMinCoinRequirement = (
270270 return sortedBundles . filter ( ( bundle ) => bundle . coins > 0n || ( bundle . assets ?. size || 0 ) > 0 ) ;
271271} ;
272272
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+
273331const computeChangeBundles = ( {
274332 utxoSelection,
275333 outputValues,
276334 uniqueTxAssetIDs,
277335 implicitValue,
278336 computeMinimumCoinQuantity,
337+ tokenBundleSizeExceedsLimit,
279338 fee = 0n
280339} : {
281340 utxoSelection : UtxoSelection ;
282341 outputValues : Cardano . Value [ ] ;
283342 uniqueTxAssetIDs : Cardano . AssetId [ ] ;
284343 implicitValue : RequiredImplicitValue ;
285344 computeMinimumCoinQuantity : ComputeMinimumCoinQuantity ;
345+ tokenBundleSizeExceedsLimit : TokenBundleSizeExceedsLimit ;
286346 fee ?: bigint ;
287347} ) : ( UtxoSelection & { changeBundles : Cardano . Value [ ] } ) | false => {
288348 const requestedAssetChangeBundles = computeRequestedAssetChangeBundles (
@@ -304,7 +364,17 @@ const computeChangeBundles = ({
304364 if ( ! changeBundles ) {
305365 return false ;
306366 }
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 } ;
308378} ;
309379
310380const validateChangeBundles = (
@@ -365,6 +435,7 @@ export const computeChangeAndAdjustForFee = async ({
365435 computeMinimumCoinQuantity,
366436 implicitValue,
367437 outputValues,
438+ tokenBundleSizeExceedsLimit,
368439 uniqueTxAssetIDs,
369440 utxoSelection
370441 } ) ;
@@ -395,6 +466,7 @@ export const computeChangeAndAdjustForFee = async ({
395466 fee : estimatedCosts . fee ,
396467 implicitValue,
397468 outputValues,
469+ tokenBundleSizeExceedsLimit,
398470 uniqueTxAssetIDs,
399471 utxoSelection : pick ( selectionWithChangeAndFee , [ 'utxoRemaining' , 'utxoSelected' ] )
400472 } ) ;
0 commit comments