Socket
Socket
Sign inDemoInstall

@cardano-sdk/cip2

Package Overview
Dependencies
9
Maintainers
1
Versions
30
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.1.2 to 0.1.3

5

dist/RoundRobinRandomImprove/change.d.ts

@@ -1,4 +0,3 @@

import { CardanoSerializationLib, CSL } from '@cardano-sdk/core';
import { CardanoSerializationLib, CSL, Ogmios } from '@cardano-sdk/core';
import { ComputeMinimumCoinQuantity, TokenBundleSizeExceedsLimit } from '../types';
import { OgmiosValue } from '../util';
import { UtxoSelection } from './util';

@@ -9,3 +8,3 @@ declare type EstimateTxFeeWithOriginalOutputs = (utxo: CSL.TransactionUnspentOutput[], change: CSL.Value[]) => Promise<bigint>;

utxoSelection: UtxoSelection;
outputValues: OgmiosValue[];
outputValues: Ogmios.util.OgmiosValue[];
uniqueOutputAssetIDs: string[];

@@ -12,0 +11,0 @@ estimateTxFee: EstimateTxFeeWithOriginalOutputs;

23

dist/RoundRobinRandomImprove/change.js

@@ -7,4 +7,3 @@ "use strict";

const InputSelectionError_1 = require("../InputSelectionError");
const util_1 = require("../util");
const util_2 = require("./util");
const util_1 = require("./util");
const getLeftoverAssets = (utxoSelected, uniqueOutputAssetIDs) => {

@@ -73,8 +72,8 @@ const leftovers = {};

assetTotals[assetId] = {
selected: util_2.assetWithValueQuantitySelector(assetId)(utxoSelected),
requested: util_2.assetQuantitySelector(assetId)(outputValues)
selected: util_1.assetWithValueQuantitySelector(assetId)(utxoSelected),
requested: util_1.assetQuantitySelector(assetId)(outputValues)
};
}
const coinTotalSelected = util_2.getWithValuesCoinQuantity(utxoSelected);
const coinTotalRequested = util_2.getCoinQuantity(outputValues) + fee;
const coinTotalSelected = util_1.getWithValuesCoinQuantity(utxoSelected);
const coinTotalRequested = util_1.getCoinQuantity(outputValues) + fee;
const coinChangeTotal = coinTotalSelected - coinTotalRequested;

@@ -115,6 +114,9 @@ const { totalCoinBundled, bundles, totalAssetsBundled } = createBundlePerOutput(outputValues, coinTotalRequested, coinChangeTotal, assetTotals);

let sortedBundles = lodash_es_1.orderBy(changeBundles, ({ coins }) => coins, 'desc');
const satisfiesMinCoinRequirement = (valueQuantities) => valueQuantities.coins >= computeMinimumCoinQuantity(util_1.ogmiosValueToCslValue(valueQuantities, csl).multiasset());
const satisfiesMinCoinRequirement = (valueQuantities) => valueQuantities.coins >= computeMinimumCoinQuantity(core_1.Ogmios.ogmiosToCsl(csl).value(valueQuantities).multiasset());
while (sortedBundles.length > 1 && !satisfiesMinCoinRequirement(sortedBundles[sortedBundles.length - 1])) {
const smallestBundle = sortedBundles.pop();
sortedBundles[sortedBundles.length - 1] = core_1.Ogmios.util.coalesceValueQuantities(sortedBundles[sortedBundles.length - 1], smallestBundle);
sortedBundles[sortedBundles.length - 1] = core_1.Ogmios.util.coalesceValueQuantities([
sortedBundles[sortedBundles.length - 1],
smallestBundle
]);
sortedBundles = lodash_es_1.orderBy(sortedBundles, ({ coins }) => coins, 'desc');

@@ -147,3 +149,4 @@ }

const changeBundlesToValues = (csl, changeBundles, tokenBundleSizeExceedsLimit) => {
const values = changeBundles.map((bundle) => util_1.ogmiosValueToCslValue(bundle, csl));
const otc = core_1.Ogmios.ogmiosToCsl(csl);
const values = changeBundles.map((bundle) => otc.value(bundle));
for (const value of values) {

@@ -169,3 +172,3 @@ const multiasset = value.multiasset();

const outputValuesWithFee = [...outputValues, { coins: fee }];
if (util_2.getCoinQuantity(outputValuesWithFee) > util_2.getWithValuesCoinQuantity(changeInclFee.utxoSelected)) {
if (util_1.getCoinQuantity(outputValuesWithFee) > util_1.getWithValuesCoinQuantity(changeInclFee.utxoSelected)) {
if (changeInclFee.utxoRemaining.length === 0) {

@@ -172,0 +175,0 @@ throw new InputSelectionError_1.InputSelectionError(InputSelectionError_1.InputSelectionFailure.UtxoBalanceInsufficient);

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.roundRobinRandomImprove = void 0;
const core_1 = require("@cardano-sdk/core");
const InputSelectionError_1 = require("../InputSelectionError");
const util_1 = require("../util");
const change_1 = require("./change");
const roundRobin_1 = require("./roundRobin");
const util_2 = require("./util");
const util_1 = require("./util");
const roundRobinRandomImprove = (csl) => ({
select: async ({ utxo, outputs, constraints: { computeMinimumCost, computeSelectionLimit, computeMinimumCoinQuantity, tokenBundleSizeExceedsLimit } }) => {
const { uniqueOutputAssetIDs, utxosWithValue, outputsWithValue } = util_2.preprocessArgs(utxo, outputs);
const utxoValues = util_2.withValuesToValues(utxosWithValue);
const outputValues = util_2.withValuesToValues(outputsWithValue);
util_2.assertIsBalanceSufficient(uniqueOutputAssetIDs, utxoValues, outputValues);
const { uniqueOutputAssetIDs, utxosWithValue, outputsWithValue } = util_1.preprocessArgs(utxo, outputs);
const utxoValues = util_1.withValuesToValues(utxosWithValue);
const outputValues = util_1.withValuesToValues(outputsWithValue);
util_1.assertIsBalanceSufficient(uniqueOutputAssetIDs, utxoValues, outputValues);
const roundRobinSelectionResult = roundRobin_1.roundRobinSelection(utxosWithValue, outputsWithValue, uniqueOutputAssetIDs);
const { change, inputs, remainingUTxO, fee } = await change_1.computeChangeAndAdjustForFee({
const result = await change_1.computeChangeAndAdjustForFee({
csl,

@@ -24,10 +24,12 @@ computeMinimumCoinQuantity,

estimateTxFee: (utxos, changeValues) => computeMinimumCost({
inputs: utxos,
change: changeValues,
fee: util_1.maxBigNum(csl),
inputs: new Set(utxos),
change: new Set(changeValues),
fee: core_1.cslUtil.maxBigNum(csl),
outputs
})
});
const feeBigNum = csl.BigNum.from_str(fee.toString());
if (inputs.length > (await computeSelectionLimit({ inputs, change, fee: feeBigNum, outputs }))) {
const inputs = new Set(result.inputs);
const change = new Set(result.change);
const fee = csl.BigNum.from_str(result.fee.toString());
if (result.inputs.length > (await computeSelectionLimit({ inputs, change, fee, outputs }))) {
throw new InputSelectionError_1.InputSelectionError(InputSelectionError_1.InputSelectionFailure.MaximumInputCountExceeded);

@@ -40,5 +42,5 @@ }

outputs,
fee: feeBigNum
fee
},
remainingUTxO
remainingUTxO: new Set(result.remainingUTxO)
};

@@ -45,0 +47,0 @@ }

@@ -1,5 +0,4 @@

import { CSL } from '@cardano-sdk/core';
import { OgmiosValue } from '../util';
import { CSL, Ogmios } from '@cardano-sdk/core';
export interface WithValue {
value: OgmiosValue;
value: Ogmios.util.OgmiosValue;
}

@@ -21,10 +20,10 @@ export interface UtxoWithValue extends WithValue {

}
export declare const preprocessArgs: (availableUtxo: CSL.TransactionUnspentOutput[], outputs: CSL.TransactionOutput[]) => RoundRobinRandomImproveArgs;
export declare const withValuesToValues: (totals: WithValue[]) => import("@cardano-sdk/core/dist/Ogmios/util").OgmiosValue[];
export declare const assetQuantitySelector: (id: string) => (quantities: OgmiosValue[]) => bigint;
export declare const preprocessArgs: (availableUtxo: Set<CSL.TransactionUnspentOutput>, outputs: Set<CSL.TransactionOutput>) => RoundRobinRandomImproveArgs;
export declare const withValuesToValues: (totals: WithValue[]) => Ogmios.util.OgmiosValue[];
export declare const assetQuantitySelector: (id: string) => (quantities: Ogmios.util.OgmiosValue[]) => bigint;
export declare const assetWithValueQuantitySelector: (id: string) => (totals: WithValue[]) => bigint;
export declare const getCoinQuantity: (quantities: OgmiosValue[]) => bigint;
export declare const getCoinQuantity: (quantities: Ogmios.util.OgmiosValue[]) => bigint;
export declare const getWithValuesCoinQuantity: (totals: WithValue[]) => bigint;
export declare const assertIsCoinBalanceSufficient: (utxoValues: OgmiosValue[], outputValues: OgmiosValue[]) => void;
export declare const assertIsBalanceSufficient: (uniqueOutputAssetIDs: string[], utxoValues: OgmiosValue[], outputValues: OgmiosValue[]) => void;
export declare const assertIsCoinBalanceSufficient: (utxoValues: Ogmios.util.OgmiosValue[], outputValues: Ogmios.util.OgmiosValue[]) => void;
export declare const assertIsBalanceSufficient: (uniqueOutputAssetIDs: string[], utxoValues: Ogmios.util.OgmiosValue[], outputValues: Ogmios.util.OgmiosValue[]) => void;
//# sourceMappingURL=util.d.ts.map

@@ -7,11 +7,10 @@ "use strict";

const InputSelectionError_1 = require("../InputSelectionError");
const util_1 = require("../util");
const preprocessArgs = (availableUtxo, outputs) => {
const utxosWithValue = availableUtxo.map((utxo) => ({
const utxosWithValue = [...availableUtxo].map((utxo) => ({
utxo,
value: util_1.valueToValueQuantities(utxo.output().amount())
value: core_1.Ogmios.cslToOgmios.value(utxo.output().amount())
}));
const outputsWithValue = outputs.map((output) => ({
const outputsWithValue = [...outputs].map((output) => ({
output,
value: util_1.valueToValueQuantities(output.amount())
value: core_1.Ogmios.cslToOgmios.value(output.amount())
}));

@@ -18,0 +17,0 @@ const uniqueOutputAssetIDs = lodash_es_1.uniq(outputsWithValue.flatMap(({ value: { assets } }) => (assets && Object.keys(assets)) || []));

@@ -1,15 +0,15 @@

import { CardanoSerializationLib, CSL, ProtocolParametersRequiredByWallet } from '@cardano-sdk/core';
import { ComputeSelectionLimit, SelectionConstraints, TokenBundleSizeExceedsLimit } from '.';
import { ComputeMinimumCoinQuantity, EstimateTxFee, SelectionSkeleton } from './types';
import { CardanoSerializationLib, CSL } from '@cardano-sdk/core';
import { ProtocolParametersRequiredByInputSelection } from '.';
import { TokenBundleSizeExceedsLimit, ComputeMinimumCoinQuantity, EstimateTxFee, SelectionSkeleton, ComputeSelectionLimit, ProtocolParametersForInputSelection, SelectionConstraints } from './types';
export declare type BuildTx = (selection: SelectionSkeleton) => Promise<CSL.Transaction>;
export interface DefaultSelectionConstraintsProps {
csl: CardanoSerializationLib;
protocolParameters: ProtocolParametersRequiredByWallet;
protocolParameters: ProtocolParametersForInputSelection;
buildTx: BuildTx;
}
export declare const computeMinimumCost: (csl: CardanoSerializationLib, { minFeeCoefficient, minFeeConstant }: ProtocolParametersRequiredByWallet, buildTx: BuildTx) => EstimateTxFee;
export declare const computeMinimumCoinQuantity: (csl: CardanoSerializationLib, { coinsPerUtxoWord }: ProtocolParametersRequiredByWallet) => ComputeMinimumCoinQuantity;
export declare const tokenBundleSizeExceedsLimit: (csl: CardanoSerializationLib, { maxValueSize }: ProtocolParametersRequiredByWallet) => TokenBundleSizeExceedsLimit;
export declare const computeSelectionLimit: ({ maxTxSize }: ProtocolParametersRequiredByWallet, buildTx: BuildTx) => ComputeSelectionLimit;
export declare const defaultSelectionConstraints: ({ csl, protocolParameters, buildTx }: DefaultSelectionConstraintsProps) => SelectionConstraints;
export declare const computeMinimumCost: (csl: CardanoSerializationLib, { minFeeCoefficient, minFeeConstant }: Pick<ProtocolParametersRequiredByInputSelection, 'minFeeCoefficient' | 'minFeeConstant'>, buildTx: BuildTx) => EstimateTxFee;
export declare const computeMinimumCoinQuantity: (csl: CardanoSerializationLib, coinsPerUtxoWord: ProtocolParametersRequiredByInputSelection['coinsPerUtxoWord']) => ComputeMinimumCoinQuantity;
export declare const tokenBundleSizeExceedsLimit: (csl: CardanoSerializationLib, maxValueSize: ProtocolParametersRequiredByInputSelection['maxValueSize']) => TokenBundleSizeExceedsLimit;
export declare const computeSelectionLimit: (maxTxSize: ProtocolParametersRequiredByInputSelection['maxTxSize'], buildTx: BuildTx) => ComputeSelectionLimit;
export declare const defaultSelectionConstraints: ({ csl, protocolParameters: { coinsPerUtxoWord, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant }, buildTx }: DefaultSelectionConstraintsProps) => SelectionConstraints;
//# sourceMappingURL=selectionConstraints.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultSelectionConstraints = exports.computeSelectionLimit = exports.tokenBundleSizeExceedsLimit = exports.computeMinimumCoinQuantity = exports.computeMinimumCost = void 0;
const util_1 = require("./util");
const core_1 = require("@cardano-sdk/core");
const computeMinimumCost = (csl, { minFeeCoefficient, minFeeConstant }, buildTx) => async (selection) => {

@@ -12,3 +12,3 @@ const tx = await buildTx(selection);

exports.computeMinimumCost = computeMinimumCost;
const computeMinimumCoinQuantity = (csl, { coinsPerUtxoWord }) => (multiasset) => {
const computeMinimumCoinQuantity = (csl, coinsPerUtxoWord) => (multiasset) => {
const minUTxOValue = csl.BigNum.from_str((coinsPerUtxoWord * 29).toString());

@@ -22,7 +22,7 @@ const value = csl.Value.new(csl.BigNum.from_str('0'));

exports.computeMinimumCoinQuantity = computeMinimumCoinQuantity;
const tokenBundleSizeExceedsLimit = (csl, { maxValueSize }) => (tokenBundle) => {
const tokenBundleSizeExceedsLimit = (csl, maxValueSize) => (tokenBundle) => {
if (!tokenBundle) {
return false;
}
const value = csl.Value.new(util_1.maxBigNum(csl));
const value = csl.Value.new(core_1.cslUtil.maxBigNum(csl));
value.set_multiasset(tokenBundle);

@@ -33,18 +33,23 @@ return value.to_bytes().length > maxValueSize;

const getTxSize = (tx) => tx.to_bytes().length;
const computeSelectionLimit = ({ maxTxSize }, buildTx) => async (selectionSkeleton) => {
const computeSelectionLimit = (maxTxSize, buildTx) => async (selectionSkeleton) => {
const tx = await buildTx(selectionSkeleton);
const txSize = getTxSize(tx);
if (txSize <= maxTxSize) {
return selectionSkeleton.inputs.length;
return selectionSkeleton.inputs.size;
}
return selectionSkeleton.inputs.length + 1;
return selectionSkeleton.inputs.size + 1;
};
exports.computeSelectionLimit = computeSelectionLimit;
const defaultSelectionConstraints = ({ csl, protocolParameters, buildTx }) => ({
computeMinimumCost: exports.computeMinimumCost(csl, protocolParameters, buildTx),
computeMinimumCoinQuantity: exports.computeMinimumCoinQuantity(csl, protocolParameters),
computeSelectionLimit: exports.computeSelectionLimit(protocolParameters, buildTx),
tokenBundleSizeExceedsLimit: exports.tokenBundleSizeExceedsLimit(csl, protocolParameters)
});
const defaultSelectionConstraints = ({ csl, protocolParameters: { coinsPerUtxoWord, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant }, buildTx }) => {
if (!coinsPerUtxoWord || !maxTxSize || !maxValueSize || !minFeeCoefficient || !minFeeConstant) {
throw new core_1.InvalidProtocolParametersError('Missing one of: coinsPerUtxoWord, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant');
}
return {
computeMinimumCost: exports.computeMinimumCost(csl, { minFeeCoefficient, minFeeConstant }, buildTx),
computeMinimumCoinQuantity: exports.computeMinimumCoinQuantity(csl, coinsPerUtxoWord),
computeSelectionLimit: exports.computeSelectionLimit(maxTxSize, buildTx),
tokenBundleSizeExceedsLimit: exports.tokenBundleSizeExceedsLimit(csl, maxValueSize)
};
};
exports.defaultSelectionConstraints = defaultSelectionConstraints;
//# sourceMappingURL=selectionConstraints.js.map

@@ -0,17 +1,14 @@

import { ProtocolParametersAlonzo } from '@cardano-ogmios/schema';
import { CSL } from '@cardano-sdk/core';
export interface SelectionResult {
selection: {
inputs: CSL.TransactionUnspentOutput[];
outputs: CSL.TransactionOutput[];
change: CSL.Value[];
fee: CSL.BigNum;
};
remainingUTxO: CSL.TransactionUnspentOutput[];
}
export interface SelectionSkeleton {
inputs: CSL.TransactionUnspentOutput[];
outputs: CSL.TransactionOutput[];
change: CSL.Value[];
inputs: Set<CSL.TransactionUnspentOutput>;
outputs: Set<CSL.TransactionOutput>;
change: Set<CSL.Value>;
fee: CSL.BigNum;
}
export declare type Selection = SelectionSkeleton;
export interface SelectionResult {
selection: Selection;
remainingUTxO: Set<CSL.TransactionUnspentOutput>;
}
export declare type EstimateTxFee = (selectionSkeleton: SelectionSkeleton) => Promise<bigint>;

@@ -28,4 +25,4 @@ export declare type TokenBundleSizeExceedsLimit = (tokenBundle?: CSL.MultiAsset) => boolean;

export interface InputSelectionParameters {
utxo: CSL.TransactionUnspentOutput[];
outputs: CSL.TransactionOutput[];
utxo: Set<CSL.TransactionUnspentOutput>;
outputs: Set<CSL.TransactionOutput>;
constraints: SelectionConstraints;

@@ -36,2 +33,6 @@ }

}
export declare type ProtocolParametersForInputSelection = Pick<ProtocolParametersAlonzo, 'coinsPerUtxoWord' | 'maxTxSize' | 'maxValueSize' | 'minFeeCoefficient' | 'minFeeConstant'>;
export declare type ProtocolParametersRequiredByInputSelection = {
[k in keyof ProtocolParametersForInputSelection]: NonNullable<ProtocolParametersForInputSelection[k]>;
};
//# sourceMappingURL=types.d.ts.map
module.exports = {
setupFilesAfterEnv: ['./test/jest.setup.js'],
preset: 'ts-jest',
transform: {
"^.+\\.test.ts?$": "ts-jest"
},
coveragePathIgnorePatterns: ['\.config\.js'],
testTimeout: process.env.CI ? 120000 : 12000,
}
...require('../../test/jest.config'),
setupFilesAfterEnv: ['../../test/jest.setup.js', './test/jest.setup.js'],
};
{
"name": "@cardano-sdk/cip2",
"version": "0.1.2",
"version": "0.1.3",
"description": "TypeScript definitions for CIP2 (Coin Selection Algorithms for Cardano)",

@@ -22,3 +22,4 @@ "engines": {

"devDependencies": {
"@cardano-ogmios/schema": "4.0.0",
"@cardano-ogmios/schema": "4.1.0",
"@cardano-sdk/util-dev": "0.1.3",
"@types/lodash-es": "^4.17.5",

@@ -30,3 +31,3 @@ "fast-check": "^2.17.0",

"dependencies": {
"@cardano-sdk/core": "0.1.2",
"@cardano-sdk/core": "0.1.3",
"lodash-es": "^4.17.21",

@@ -33,0 +34,0 @@ "ts-custom-error": "^3.2.0"

@@ -5,3 +5,2 @@ import { CardanoSerializationLib, CSL, Ogmios } from '@cardano-sdk/core';

import { InputSelectionError, InputSelectionFailure } from '../InputSelectionError';
import { TokenMap, OgmiosValue, ogmiosValueToCslValue } from '../util';
import {

@@ -21,3 +20,3 @@ assetQuantitySelector,

utxoSelection: UtxoSelection;
outputValues: OgmiosValue[];
outputValues: Ogmios.util.OgmiosValue[];
uniqueOutputAssetIDs: string[];

@@ -60,3 +59,3 @@ estimateTxFee: EstimateTxFeeWithOriginalOutputs;

utxoSelected: UtxoWithValue[],
requestedAssetChangeBundles: OgmiosValue[],
requestedAssetChangeBundles: Ogmios.util.OgmiosValue[],
uniqueOutputAssetIDs: string[]

@@ -74,3 +73,3 @@ ) => {

// Coalesce the smallest quantities together
const smallestQuantity = quantities.pop();
const smallestQuantity = quantities.pop()!;
quantities[quantities.length - 1] += smallestQuantity;

@@ -93,3 +92,3 @@ }

const createBundlePerOutput = (
outputValues: OgmiosValue[],
outputValues: Ogmios.util.OgmiosValue[],
coinTotalRequested: bigint,

@@ -107,3 +106,3 @@ coinChangeTotal: bigint,

}
const assets: TokenMap = {};
const assets: Ogmios.util.TokenMap = {};
for (const assetId of Object.keys(value.assets)) {

@@ -132,6 +131,6 @@ const outputAmount = value.assets[assetId];

utxoSelected: UtxoWithValue[],
outputValues: OgmiosValue[],
outputValues: Ogmios.util.OgmiosValue[],
uniqueOutputAssetIDs: string[],
fee: bigint
): OgmiosValue[] => {
): Ogmios.util.OgmiosValue[] => {
const assetTotals: Record<string, { selected: bigint; requested: bigint }> = {};

@@ -168,4 +167,4 @@ for (const assetId of uniqueOutputAssetIDs) {

if (assetLost > 0n) {
const anyBundle = bundles.find(({ assets }) => typeof assets?.[assetId] === 'bigint');
anyBundle.assets[assetId] = (anyBundle.assets[assetId] || 0n) + assetLost;
const anyBundle = bundles.find(({ assets }) => typeof assets?.[assetId] === 'bigint')!;
anyBundle.assets![assetId] = (anyBundle.assets![assetId] || 0n) + assetLost;
}

@@ -193,5 +192,5 @@ }

csl: CardanoSerializationLib,
changeBundles: OgmiosValue[],
changeBundles: Ogmios.util.OgmiosValue[],
computeMinimumCoinQuantity: ComputeMinimumCoinQuantity
): OgmiosValue[] | undefined => {
): Ogmios.util.OgmiosValue[] | undefined => {
if (changeBundles.length === 0) {

@@ -202,11 +201,11 @@ return changeBundles;

let sortedBundles = orderBy(changeBundles, ({ coins }) => coins, 'desc');
const satisfiesMinCoinRequirement = (valueQuantities: OgmiosValue) =>
valueQuantities.coins >= computeMinimumCoinQuantity(ogmiosValueToCslValue(valueQuantities, csl).multiasset());
const satisfiesMinCoinRequirement = (valueQuantities: Ogmios.util.OgmiosValue) =>
valueQuantities.coins >= computeMinimumCoinQuantity(Ogmios.ogmiosToCsl(csl).value(valueQuantities).multiasset());
while (sortedBundles.length > 1 && !satisfiesMinCoinRequirement(sortedBundles[sortedBundles.length - 1])) {
const smallestBundle = sortedBundles.pop();
sortedBundles[sortedBundles.length - 1] = Ogmios.util.coalesceValueQuantities(
const smallestBundle = sortedBundles.pop()!;
sortedBundles[sortedBundles.length - 1] = Ogmios.util.coalesceValueQuantities([
sortedBundles[sortedBundles.length - 1],
smallestBundle
);
]);
// Re-sort because last bundle is not necessarily the smallest one after merging it

@@ -217,7 +216,5 @@ sortedBundles = orderBy(sortedBundles, ({ coins }) => coins, 'desc');

// Coalesced all bundles to 1 and it's still less than min utxo value
// eslint-disable-next-line consistent-return
return undefined;
}
// Filter empty bundles
// eslint-disable-next-line consistent-return
return sortedBundles.filter((bundle) => bundle.coins > 0n || Object.keys(bundle.assets || {}).length > 0);

@@ -236,7 +233,7 @@ };

utxoSelection: UtxoSelection;
outputValues: OgmiosValue[];
outputValues: Ogmios.util.OgmiosValue[];
uniqueOutputAssetIDs: string[];
computeMinimumCoinQuantity: ComputeMinimumCoinQuantity;
fee?: bigint;
}): UtxoSelection & { changeBundles: OgmiosValue[] } => {
}): UtxoSelection & { changeBundles: Ogmios.util.OgmiosValue[] } => {
const requestedAssetChangeBundles = computeRequestedAssetChangeBundles(

@@ -280,6 +277,7 @@ utxoSelection.utxoSelected,

csl: CardanoSerializationLib,
changeBundles: OgmiosValue[],
changeBundles: Ogmios.util.OgmiosValue[],
tokenBundleSizeExceedsLimit: TokenBundleSizeExceedsLimit
) => {
const values = changeBundles.map((bundle) => ogmiosValueToCslValue(bundle, csl));
const otc = Ogmios.ogmiosToCsl(csl);
const values = changeBundles.map((bundle) => otc.value(bundle));
for (const value of values) {

@@ -286,0 +284,0 @@ const multiasset = value.multiasset();

@@ -1,5 +0,4 @@

import { CardanoSerializationLib } from '@cardano-sdk/core';
import { CardanoSerializationLib, cslUtil } from '@cardano-sdk/core';
import { InputSelectionError, InputSelectionFailure } from '../InputSelectionError';
import { InputSelectionParameters, InputSelector, SelectionResult } from '../types';
import { maxBigNum } from '../util';
import { computeChangeAndAdjustForFee } from './change';

@@ -23,3 +22,3 @@ import { roundRobinSelection } from './roundRobin';

const { change, inputs, remainingUTxO, fee } = await computeChangeAndAdjustForFee({
const result = await computeChangeAndAdjustForFee({
csl,

@@ -33,5 +32,5 @@ computeMinimumCoinQuantity,

computeMinimumCost({
inputs: utxos,
change: changeValues,
fee: maxBigNum(csl),
inputs: new Set(utxos),
change: new Set(changeValues),
fee: cslUtil.maxBigNum(csl),
outputs

@@ -41,4 +40,7 @@ })

const feeBigNum = csl.BigNum.from_str(fee.toString());
if (inputs.length > (await computeSelectionLimit({ inputs, change, fee: feeBigNum, outputs }))) {
const inputs = new Set(result.inputs);
const change = new Set(result.change);
const fee = csl.BigNum.from_str(result.fee.toString());
if (result.inputs.length > (await computeSelectionLimit({ inputs, change, fee, outputs }))) {
throw new InputSelectionError(InputSelectionFailure.MaximumInputCountExceeded);

@@ -52,7 +54,7 @@ }

outputs,
fee: feeBigNum
fee
},
remainingUTxO
remainingUTxO: new Set(result.remainingUTxO)
};
}
});

@@ -1,8 +0,7 @@

import { BigIntMath, CSL } from '@cardano-sdk/core';
import { BigIntMath, CSL, Ogmios } from '@cardano-sdk/core';
import { uniq } from 'lodash-es';
import { InputSelectionError, InputSelectionFailure } from '../InputSelectionError';
import { OgmiosValue, valueToValueQuantities } from '../util';
export interface WithValue {
value: OgmiosValue;
value: Ogmios.util.OgmiosValue;
}

@@ -30,12 +29,12 @@

export const preprocessArgs = (
availableUtxo: CSL.TransactionUnspentOutput[],
outputs: CSL.TransactionOutput[]
availableUtxo: Set<CSL.TransactionUnspentOutput>,
outputs: Set<CSL.TransactionOutput>
): RoundRobinRandomImproveArgs => {
const utxosWithValue = availableUtxo.map((utxo) => ({
const utxosWithValue = [...availableUtxo].map((utxo) => ({
utxo,
value: valueToValueQuantities(utxo.output().amount())
value: Ogmios.cslToOgmios.value(utxo.output().amount())
}));
const outputsWithValue = outputs.map((output) => ({
const outputsWithValue = [...outputs].map((output) => ({
output,
value: valueToValueQuantities(output.amount())
value: Ogmios.cslToOgmios.value(output.amount())
}));

@@ -51,3 +50,3 @@ const uniqueOutputAssetIDs = uniq(

(id: string) =>
(quantities: OgmiosValue[]): bigint =>
(quantities: Ogmios.util.OgmiosValue[]): bigint =>
BigIntMath.sum(quantities.map(({ assets }) => assets?.[id] || 0n));

@@ -58,7 +57,10 @@ export const assetWithValueQuantitySelector =

assetQuantitySelector(id)(withValuesToValues(totals));
export const getCoinQuantity = (quantities: OgmiosValue[]): bigint =>
export const getCoinQuantity = (quantities: Ogmios.util.OgmiosValue[]): bigint =>
BigIntMath.sum(quantities.map(({ coins }) => coins));
export const getWithValuesCoinQuantity = (totals: WithValue[]): bigint => getCoinQuantity(withValuesToValues(totals));
export const assertIsCoinBalanceSufficient = (utxoValues: OgmiosValue[], outputValues: OgmiosValue[]) => {
export const assertIsCoinBalanceSufficient = (
utxoValues: Ogmios.util.OgmiosValue[],
outputValues: Ogmios.util.OgmiosValue[]
) => {
const utxoCoinTotal = getCoinQuantity(utxoValues);

@@ -79,4 +81,4 @@ const outputsCoinTotal = getCoinQuantity(outputValues);

uniqueOutputAssetIDs: string[],
utxoValues: OgmiosValue[],
outputValues: OgmiosValue[]
utxoValues: Ogmios.util.OgmiosValue[],
outputValues: Ogmios.util.OgmiosValue[]
): void => {

@@ -83,0 +85,0 @@ for (const assetId of uniqueOutputAssetIDs) {

@@ -1,10 +0,18 @@

import { CardanoSerializationLib, CSL, ProtocolParametersRequiredByWallet } from '@cardano-sdk/core';
import { ComputeSelectionLimit, SelectionConstraints, TokenBundleSizeExceedsLimit } from '.';
import { ComputeMinimumCoinQuantity, EstimateTxFee, SelectionSkeleton } from './types';
import { maxBigNum } from './util';
import { CardanoSerializationLib, CSL, cslUtil, InvalidProtocolParametersError } from '@cardano-sdk/core';
import { ProtocolParametersRequiredByInputSelection } from '.';
import {
TokenBundleSizeExceedsLimit,
ComputeMinimumCoinQuantity,
EstimateTxFee,
SelectionSkeleton,
ComputeSelectionLimit,
ProtocolParametersForInputSelection,
SelectionConstraints
} from './types';
export type BuildTx = (selection: SelectionSkeleton) => Promise<CSL.Transaction>;
export interface DefaultSelectionConstraintsProps {
csl: CardanoSerializationLib;
protocolParameters: ProtocolParametersRequiredByWallet;
protocolParameters: ProtocolParametersForInputSelection;
buildTx: BuildTx;

@@ -16,3 +24,6 @@ }

csl: CardanoSerializationLib,
{ minFeeCoefficient, minFeeConstant }: ProtocolParametersRequiredByWallet,
{
minFeeCoefficient,
minFeeConstant
}: Pick<ProtocolParametersRequiredByInputSelection, 'minFeeCoefficient' | 'minFeeConstant'>,
buildTx: BuildTx

@@ -38,3 +49,3 @@ ): EstimateTxFee =>

csl: CardanoSerializationLib,
{ coinsPerUtxoWord }: ProtocolParametersRequiredByWallet
coinsPerUtxoWord: ProtocolParametersRequiredByInputSelection['coinsPerUtxoWord']
): ComputeMinimumCoinQuantity =>

@@ -51,3 +62,6 @@ (multiasset) => {

export const tokenBundleSizeExceedsLimit =
(csl: CardanoSerializationLib, { maxValueSize }: ProtocolParametersRequiredByWallet): TokenBundleSizeExceedsLimit =>
(
csl: CardanoSerializationLib,
maxValueSize: ProtocolParametersRequiredByInputSelection['maxValueSize']
): TokenBundleSizeExceedsLimit =>
(tokenBundle) => {

@@ -57,3 +71,3 @@ if (!tokenBundle) {

}
const value = csl.Value.new(maxBigNum(csl));
const value = csl.Value.new(cslUtil.maxBigNum(csl));
value.set_multiasset(tokenBundle);

@@ -73,3 +87,3 @@ return value.to_bytes().length > maxValueSize;

export const computeSelectionLimit =
({ maxTxSize }: ProtocolParametersRequiredByWallet, buildTx: BuildTx): ComputeSelectionLimit =>
(maxTxSize: ProtocolParametersRequiredByInputSelection['maxTxSize'], buildTx: BuildTx): ComputeSelectionLimit =>
async (selectionSkeleton) => {

@@ -79,5 +93,5 @@ const tx = await buildTx(selectionSkeleton);

if (txSize <= maxTxSize) {
return selectionSkeleton.inputs.length;
return selectionSkeleton.inputs.size;
}
return selectionSkeleton.inputs.length + 1;
return selectionSkeleton.inputs.size + 1;
};

@@ -87,9 +101,16 @@

csl,
protocolParameters,
protocolParameters: { coinsPerUtxoWord, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant },
buildTx
}: DefaultSelectionConstraintsProps): SelectionConstraints => ({
computeMinimumCost: computeMinimumCost(csl, protocolParameters, buildTx),
computeMinimumCoinQuantity: computeMinimumCoinQuantity(csl, protocolParameters),
computeSelectionLimit: computeSelectionLimit(protocolParameters, buildTx),
tokenBundleSizeExceedsLimit: tokenBundleSizeExceedsLimit(csl, protocolParameters)
});
}: DefaultSelectionConstraintsProps): SelectionConstraints => {
if (!coinsPerUtxoWord || !maxTxSize || !maxValueSize || !minFeeCoefficient || !minFeeConstant) {
throw new InvalidProtocolParametersError(
'Missing one of: coinsPerUtxoWord, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant'
);
}
return {
computeMinimumCost: computeMinimumCost(csl, { minFeeCoefficient, minFeeConstant }, buildTx),
computeMinimumCoinQuantity: computeMinimumCoinQuantity(csl, coinsPerUtxoWord),
computeSelectionLimit: computeSelectionLimit(maxTxSize, buildTx),
tokenBundleSizeExceedsLimit: tokenBundleSizeExceedsLimit(csl, maxValueSize)
};
};

@@ -0,28 +1,33 @@

import { ProtocolParametersAlonzo } from '@cardano-ogmios/schema';
import { CSL } from '@cardano-sdk/core';
export interface SelectionSkeleton {
/**
* A set of inputs, equivalent to a subset of the initial UTxO set.
*
* From the point of view of a wallet, this represents the value
* that has been selected from the wallet in order to cover the total payment value.
*/
inputs: Set<CSL.TransactionUnspentOutput>;
/**
* Set of payments to be made to recipient addresses.
*/
outputs: Set<CSL.TransactionOutput>;
/**
* A set of change values. Does not account for fee.
*
* From the point of view of a wallet, this represents the change to be returned to the wallet.
*/
change: Set<CSL.Value>;
/**
* Estimated fee for the transaction.
* This value is included in 'change', so the actual change returned by the transaction is change-fee.
*/
fee: CSL.BigNum;
}
export type Selection = SelectionSkeleton;
export interface SelectionResult {
selection: {
/**
* A set of inputs, equivalent to a subset of the initial UTxO set.
*
* From the point of view of a wallet, this represents the value
* that has been selected from the wallet in order to cover the total payment value.
*/
inputs: CSL.TransactionUnspentOutput[];
/**
* Set of payments to be made to recipient addresses.
*/
outputs: CSL.TransactionOutput[];
/**
* A set of change values. Does not account for fee.
*
* From the point of view of a wallet, this represents the change to be returned to the wallet.
*/
change: CSL.Value[];
/**
* Estimated fee for the transaction.
* This value is included in 'change', so the actual change returned by the transaction is change-fee.
*/
fee: CSL.BigNum;
};
selection: Selection;
/**

@@ -34,12 +39,5 @@ * The remaining UTxO set is a subset of the initial UTxO set.

*/
remainingUTxO: CSL.TransactionUnspentOutput[];
remainingUTxO: Set<CSL.TransactionUnspentOutput>;
}
export interface SelectionSkeleton {
inputs: CSL.TransactionUnspentOutput[];
outputs: CSL.TransactionOutput[];
change: CSL.Value[];
fee: CSL.BigNum;
}
/**

@@ -77,7 +75,7 @@ * @returns minimum transaction fee in Lovelace.

*/
utxo: CSL.TransactionUnspentOutput[];
utxo: Set<CSL.TransactionUnspentOutput>;
/**
* The set of outputs requested for payment.
*/
outputs: CSL.TransactionOutput[];
outputs: Set<CSL.TransactionOutput>;
/**

@@ -97,1 +95,10 @@ * Input selection constraints

}
export type ProtocolParametersForInputSelection = Pick<
ProtocolParametersAlonzo,
'coinsPerUtxoWord' | 'maxTxSize' | 'maxValueSize' | 'minFeeCoefficient' | 'minFeeConstant'
>;
export type ProtocolParametersRequiredByInputSelection = {
[k in keyof ProtocolParametersForInputSelection]: NonNullable<ProtocolParametersForInputSelection[k]>;
};
/* eslint-disable unicorn/prefer-module */
/* eslint-disable @typescript-eslint/no-var-requires */
// TODO: jest environment is not happy with 'lodash-es' exports.
// I think using non-es-module 'lodash' in 'dependencies' is too heavy.
// eslint-disable-next-line unicorn/prefer-module
jest.mock('lodash-es', () => require('lodash'));
const { testTimeout } = require('../jest.config');

@@ -10,0 +4,0 @@ require('fast-check').configureGlobal({

@@ -5,8 +5,3 @@ import { roundRobinRandomImprove } from '../src/RoundRobinRandomImprove';

assertFailureProperties,
createCslTestUtils,
generateSelectionParams,
NO_CONSTRAINTS,
toConstraints,
PXL_Asset,
TSLA_Asset,
testInputSelectionFailureMode,

@@ -17,2 +12,3 @@ testInputSelectionProperties

import { loadCardanoSerializationLib, CardanoSerializationLib } from '@cardano-sdk/core';
import { AssetId, CslTestUtil, SelectionConstraints } from '@cardano-sdk/util-dev';
import fc from 'fast-check';

@@ -28,5 +24,5 @@

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [utils.createUnspentTxOutput({ coins: 3_000_000n })],
createOutputs: (utils) => [utils.createOutput({ coins: 3_000_000n })],
mockConstraints: NO_CONSTRAINTS
createUtxo: (csl) => [CslTestUtil.createUnspentTxOutput(csl, { coins: 3_000_000n })],
createOutputs: (csl) => [CslTestUtil.createOutput(csl, { coins: 3_000_000n })],
mockConstraints: SelectionConstraints.MOCK_NO_CONSTRAINTS
});

@@ -38,6 +34,6 @@ });

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [utils.createUnspentTxOutput({ coins: 11_999_994n })],
createUtxo: (csl) => [CslTestUtil.createUnspentTxOutput(csl, { coins: 11_999_994n })],
createOutputs: () => [],
mockConstraints: {
...NO_CONSTRAINTS,
...SelectionConstraints.MOCK_NO_CONSTRAINTS,
minimumCoinQuantity: 9_999_991n,

@@ -54,11 +50,11 @@ minimumCost: 2_000_003n

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [
utils.createUnspentTxOutput({ coins: 3_000_000n }),
utils.createUnspentTxOutput({ coins: 10_000_000n })
createUtxo: (csl) => [
CslTestUtil.createUnspentTxOutput(csl, { coins: 3_000_000n }),
CslTestUtil.createUnspentTxOutput(csl, { coins: 10_000_000n })
],
createOutputs: (utils) => [
utils.createOutput({ coins: 12_000_000n }),
utils.createOutput({ coins: 2_000_000n })
createOutputs: (csl) => [
CslTestUtil.createOutput(csl, { coins: 12_000_000n }),
CslTestUtil.createOutput(csl, { coins: 2_000_000n })
],
mockConstraints: NO_CONSTRAINTS,
mockConstraints: SelectionConstraints.MOCK_NO_CONSTRAINTS,
expectedError: InputSelectionFailure.UtxoBalanceInsufficient

@@ -70,9 +66,9 @@ });

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [
utils.createUnspentTxOutput({ coins: 4_000_000n }),
utils.createUnspentTxOutput({ coins: 5_000_000n })
createUtxo: (csl) => [
CslTestUtil.createUnspentTxOutput(csl, { coins: 4_000_000n }),
CslTestUtil.createUnspentTxOutput(csl, { coins: 5_000_000n })
],
createOutputs: (utils) => [utils.createOutput({ coins: 9_000_000n })],
createOutputs: (csl) => [CslTestUtil.createOutput(csl, { coins: 9_000_000n })],
mockConstraints: {
...NO_CONSTRAINTS,
...SelectionConstraints.MOCK_NO_CONSTRAINTS,
minimumCost: 1n

@@ -86,7 +82,9 @@ },

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [
utils.createUnspentTxOutput({ coins: 10_000_000n, assets: { [TSLA_Asset]: 7000n } })
createUtxo: (csl) => [
CslTestUtil.createUnspentTxOutput(csl, { coins: 10_000_000n, assets: { [AssetId.TSLA]: 7000n } })
],
createOutputs: (utils) => [utils.createOutput({ coins: 5_000_000n, assets: { [TSLA_Asset]: 7001n } })],
mockConstraints: NO_CONSTRAINTS,
createOutputs: (csl) => [
CslTestUtil.createOutput(csl, { coins: 5_000_000n, assets: { [AssetId.TSLA]: 7001n } })
],
mockConstraints: SelectionConstraints.MOCK_NO_CONSTRAINTS,
expectedError: InputSelectionFailure.UtxoBalanceInsufficient

@@ -99,4 +97,4 @@ });

createUtxo: () => [],
createOutputs: (utils) => [utils.createOutput({ coins: 5_000_000n })],
mockConstraints: NO_CONSTRAINTS,
createOutputs: (csl) => [CslTestUtil.createOutput(csl, { coins: 5_000_000n })],
mockConstraints: SelectionConstraints.MOCK_NO_CONSTRAINTS,
expectedError: InputSelectionFailure.UtxoBalanceInsufficient

@@ -110,9 +108,9 @@ });

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [
utils.createUnspentTxOutput({ coins: 1_000_000n }),
utils.createUnspentTxOutput({ coins: 2_000_000n })
createUtxo: (csl) => [
CslTestUtil.createUnspentTxOutput(csl, { coins: 1_000_000n }),
CslTestUtil.createUnspentTxOutput(csl, { coins: 2_000_000n })
],
createOutputs: (utils) => [utils.createOutput({ coins: 2_999_999n })],
createOutputs: (csl) => [CslTestUtil.createOutput(csl, { coins: 2_999_999n })],
mockConstraints: {
...NO_CONSTRAINTS,
...SelectionConstraints.MOCK_NO_CONSTRAINTS,
minimumCoinQuantity: 2n

@@ -126,10 +124,16 @@ },

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [
utils.createUnspentTxOutput({ coins: 2_000_000n, assets: { [TSLA_Asset]: 1000n, [PXL_Asset]: 1000n } })
createUtxo: (csl) => [
CslTestUtil.createUnspentTxOutput(csl, {
coins: 2_000_000n,
assets: { [AssetId.TSLA]: 1000n, [AssetId.PXL]: 1000n }
})
],
createOutputs: (utils) => [
utils.createOutput({ coins: 1_000_000n, assets: { [TSLA_Asset]: 500n, [PXL_Asset]: 500n } })
createOutputs: (csl) => [
CslTestUtil.createOutput(csl, {
coins: 1_000_000n,
assets: { [AssetId.TSLA]: 500n, [AssetId.PXL]: 500n }
})
],
mockConstraints: {
...NO_CONSTRAINTS,
...SelectionConstraints.MOCK_NO_CONSTRAINTS,
maxTokenBundleSize: 1

@@ -144,10 +148,10 @@ },

getAlgorithm: getRoundRobinRandomImprove,
createUtxo: (utils) => [
utils.createUnspentTxOutput({ coins: 2_000_000n }),
utils.createUnspentTxOutput({ coins: 2_000_000n }),
utils.createUnspentTxOutput({ coins: 3_000_000n })
createUtxo: (csl) => [
CslTestUtil.createUnspentTxOutput(csl, { coins: 2_000_000n }),
CslTestUtil.createUnspentTxOutput(csl, { coins: 2_000_000n }),
CslTestUtil.createUnspentTxOutput(csl, { coins: 3_000_000n })
],
createOutputs: (utils) => [utils.createOutput({ coins: 6_000_000n })],
createOutputs: (csl) => [CslTestUtil.createOutput(csl, { coins: 6_000_000n })],
mockConstraints: {
...NO_CONSTRAINTS,
...SelectionConstraints.MOCK_NO_CONSTRAINTS,
selectionLimit: 2

@@ -163,3 +167,2 @@ },

const csl = await loadCardanoSerializationLib();
const utils = createCslTestUtils(csl);
const algorithm = getRoundRobinRandomImprove(csl);

@@ -170,12 +173,16 @@

// Run input selection
const utxo = utxoAmounts.map((valueQuantities) => utils.createUnspentTxOutput(valueQuantities));
const outputs = outputsAmounts.map((valueQuantities) => utils.createOutput(valueQuantities));
const utxo = new Set(
utxoAmounts.map((valueQuantities) => CslTestUtil.createUnspentTxOutput(csl, valueQuantities))
);
const outputs = new Set(
outputsAmounts.map((valueQuantities) => CslTestUtil.createOutput(csl, valueQuantities))
);
try {
const results = await algorithm.select({
utxo,
utxo: new Set(utxo),
outputs,
constraints: toConstraints(constraints)
constraints: SelectionConstraints.mockConstraintsToConstraints(constraints)
});
assertInputSelectionProperties({ utils, results, outputs, utxo, constraints });
assertInputSelectionProperties({ results, outputs, utxo, constraints });
} catch (error) {

@@ -182,0 +189,0 @@ if (error instanceof InputSelectionError) {

@@ -0,1 +1,2 @@

/* eslint-disable no-loop-func */
/* eslint-disable @typescript-eslint/no-explicit-any */

@@ -6,9 +7,9 @@ /* eslint-disable unicorn/consistent-function-scoping */

CSL,
loadCardanoSerializationLib,
ProtocolParametersRequiredByWallet
Ogmios,
InvalidProtocolParametersError,
loadCardanoSerializationLib
} from '@cardano-sdk/core';
import { PXL_Asset, TSLA_Asset } from './util';
import { AssetId } from '@cardano-sdk/util-dev';
import { defaultSelectionConstraints, DefaultSelectionConstraintsProps } from '../src/selectionConstraints';
import { SelectionSkeleton } from '../src/types';
import { ogmiosValueToCslValue } from '../src/util';
import { ProtocolParametersForInputSelection, SelectionSkeleton } from '../src/types';

@@ -23,6 +24,16 @@ describe('defaultSelectionConstraints', () => {

maxValueSize: 5000
} as ProtocolParametersRequiredByWallet;
} as ProtocolParametersForInputSelection;
beforeAll(async () => (csl = await loadCardanoSerializationLib()));
it('Invalid parameters', () => {
for (const param of ['minFeeCoefficient', 'minFeeConstant', 'coinsPerUtxoWord', 'maxTxSize', 'maxValueSize']) {
expect(() =>
defaultSelectionConstraints({
protocolParameters: { ...protocolParameters, [param]: null }
} as DefaultSelectionConstraintsProps)
).toThrowError(InvalidProtocolParametersError);
}
});
it('computeMinimumCost', async () => {

@@ -50,12 +61,11 @@ const fee = 200_000n;

it('computeMinimumCoinQuantity', () => {
const withAssets = ogmiosValueToCslValue(
{
const withAssets = Ogmios.ogmiosToCsl(csl)
.value({
coins: 10_000n,
assets: {
[TSLA_Asset]: 5000n,
[PXL_Asset]: 3000n
[AssetId.TSLA]: 5000n,
[AssetId.PXL]: 3000n
}
},
csl
).multiasset();
})
.multiasset();
const constraints = defaultSelectionConstraints({

@@ -79,5 +89,7 @@ csl,

protocolParameters,
buildTx: buildTxOfLength(protocolParameters.maxTxSize)
buildTx: buildTxOfLength(protocolParameters.maxTxSize!)
});
expect(await constraints.computeSelectionLimit({ inputs: [1, 2] as any } as SelectionSkeleton)).toEqual(2);
expect(await constraints.computeSelectionLimit({ inputs: new Set([1, 2]) as any } as SelectionSkeleton)).toEqual(
2
);
});

@@ -89,5 +101,7 @@

protocolParameters,
buildTx: buildTxOfLength(protocolParameters.maxTxSize + 1)
buildTx: buildTxOfLength(protocolParameters.maxTxSize! + 1)
});
expect(await constraints.computeSelectionLimit({ inputs: [1, 2] as any } as SelectionSkeleton)).toEqual(3);
expect(await constraints.computeSelectionLimit({ inputs: new Set([1, 2]) as any } as SelectionSkeleton)).toEqual(
3
);
});

@@ -119,3 +133,3 @@ });

const constraints = defaultSelectionConstraints({
csl: stubCslWithValueLength(protocolParameters.maxValueSize),
csl: stubCslWithValueLength(protocolParameters.maxValueSize!),
protocolParameters

@@ -128,3 +142,3 @@ } as DefaultSelectionConstraintsProps);

const constraints = defaultSelectionConstraints({
csl: stubCslWithValueLength(protocolParameters.maxValueSize + 1),
csl: stubCslWithValueLength(protocolParameters.maxValueSize! + 1),
protocolParameters

@@ -131,0 +145,0 @@ } as DefaultSelectionConstraintsProps);

@@ -8,5 +8,6 @@ {

{ "path": "../src" },
{ "path": "../../core/src" }
{ "path": "../../core/src" },
{ "path": "../../util-dev/src" }
]
}

@@ -1,4 +0,2 @@

export * from './util';
export * from './constraints';
export * from './properties';
export * from './tests';

@@ -1,12 +0,13 @@

import { AllAssets, containsUtxo, TestUtils } from './util';
import { AssetId, SelectionConstraints } from '@cardano-sdk/util-dev';
import { SelectionResult } from '../../src/types';
import { CSL, Ogmios } from '@cardano-sdk/core';
import { CSL, cslUtil, Ogmios } from '@cardano-sdk/core';
import { InputSelectionError, InputSelectionFailure } from '../../src/InputSelectionError';
import { TokenMap, MAX_U64, OgmiosValue, valueToValueQuantities } from '../../src/util';
import fc, { Arbitrary } from 'fast-check';
import { MockSelectionConstraints } from './constraints';
const assertExtraChangeProperties = ({ minimumCoinQuantity }: MockSelectionConstraints, results: SelectionResult) => {
const assertExtraChangeProperties = (
{ minimumCoinQuantity }: SelectionConstraints.MockSelectionConstraints,
results: SelectionResult
) => {
for (const value of results.selection.change) {
const { coins, assets } = valueToValueQuantities(value);
const { coins, assets } = Ogmios.cslToOgmios.value(value);
// Min UTxO coin requirement for change

@@ -25,4 +26,11 @@ expect(coins).toBeGreaterThanOrEqual(minimumCoinQuantity);

const totalOutputsValue = (outputs: Set<CSL.TransactionOutput>) =>
Ogmios.util.coalesceValueQuantities([...outputs].map((output) => Ogmios.cslToOgmios.value(output.amount())));
const totalUtxosValue = (results: SelectionResult) =>
Ogmios.util.coalesceValueQuantities(
[...results.selection.inputs].map((selectedUtxo) => Ogmios.cslToOgmios.value(selectedUtxo.output().amount()))
);
export const assertInputSelectionProperties = ({
utils,
results,

@@ -33,14 +41,13 @@ outputs,

}: {
utils: TestUtils;
results: SelectionResult;
outputs: CSL.TransactionOutput[];
utxo: CSL.TransactionUnspentOutput[];
constraints: MockSelectionConstraints;
outputs: Set<CSL.TransactionOutput>;
utxo: Set<CSL.TransactionUnspentOutput>;
constraints: SelectionConstraints.MockSelectionConstraints;
}) => {
const vSelected = utils.getTotalInputAmounts(results);
const vRequested = utils.getTotalOutputAmounts(outputs);
const vSelected = totalUtxosValue(results);
const vRequested = totalOutputsValue(outputs);
// Coverage of Payments
expect(vSelected.coins).toBeGreaterThanOrEqual(vRequested.coins);
for (const assetName of AllAssets) {
for (const assetName of AssetId.All) {
expect(vSelected.assets?.[assetName] || 0n).toBeGreaterThanOrEqual(vRequested.assets?.[assetName] || 0n);

@@ -50,6 +57,8 @@ }

// Correctness of Change
const vChange = utils.getTotalChangeAmounts(results);
const vChange = Ogmios.util.coalesceValueQuantities(
[...results.selection.change].map((value) => Ogmios.cslToOgmios.value(value))
);
const vFee = BigInt(results.selection.fee.to_str());
expect(vSelected.coins).toEqual(vRequested.coins + vChange.coins + vFee);
for (const assetName of AllAssets) {
for (const assetName of AssetId.All) {
expect(vSelected.assets?.[assetName] || 0n).toEqual(

@@ -62,4 +71,4 @@ (vRequested.assets?.[assetName] || 0n) + (vChange.assets?.[assetName] || 0n)

for (const utxoEntry of utxo) {
const isInInputSelectionInputsSet = containsUtxo(results.selection.inputs, utxoEntry);
const isInRemainingUtxoSet = containsUtxo(results.remainingUTxO, utxoEntry);
const isInInputSelectionInputsSet = results.selection.inputs.has(utxoEntry);
const isInRemainingUtxoSet = results.remainingUTxO.has(utxoEntry);
expect(isInInputSelectionInputsSet || isInRemainingUtxoSet).toBe(true);

@@ -84,8 +93,8 @@ expect(isInInputSelectionInputsSet).not.toEqual(isInRemainingUtxoSet);

error: InputSelectionError;
utxoAmounts: OgmiosValue[];
outputsAmounts: OgmiosValue[];
constraints: MockSelectionConstraints;
utxoAmounts: Ogmios.util.OgmiosValue[];
outputsAmounts: Ogmios.util.OgmiosValue[];
constraints: SelectionConstraints.MockSelectionConstraints;
}) => {
const utxoTotals = Ogmios.util.coalesceValueQuantities(...utxoAmounts);
const outputsTotals = Ogmios.util.coalesceValueQuantities(...outputsAmounts);
const utxoTotals = Ogmios.util.coalesceValueQuantities(utxoAmounts);
const outputsTotals = Ogmios.util.coalesceValueQuantities(outputsAmounts);
switch (error.failure) {

@@ -97,3 +106,3 @@ case InputSelectionFailure.UtxoBalanceInsufficient: {

Object.keys(outputsTotals.assets).some(
(assetId) => (utxoTotals.assets?.[assetId] || 0n) < outputsTotals.assets[assetId]
(assetId) => (utxoTotals.assets?.[assetId] || 0n) < outputsTotals.assets![assetId]
);

@@ -131,9 +140,9 @@ expect(insufficientCoin || insufficientAsset).toBe(true);

.array(
fc.record<OgmiosValue>({
coins: fc.bigUint(MAX_U64),
fc.record<Ogmios.util.OgmiosValue>({
coins: fc.bigUint(cslUtil.MAX_U64),
assets: fc.oneof(
fc
.set(fc.oneof(...AllAssets.map((asset) => fc.constant(asset))))
.set(fc.oneof(...AssetId.All.map((asset) => fc.constant(asset))))
.chain((assets) =>
fc.tuple(...assets.map((asset) => fc.bigUint(MAX_U64).map((amount) => ({ asset, amount }))))
fc.tuple(...assets.map((asset) => fc.bigUint(cslUtil.MAX_U64).map((amount) => ({ asset, amount }))))
)

@@ -144,3 +153,3 @@ .map((assets) =>

return quantities;
}, {} as TokenMap)
}, {} as Ogmios.util.TokenMap)
),

@@ -154,10 +163,13 @@ fc.constant(void 0)

// sum of coin or any asset can't exceed MAX_U64
const { coins, assets } = Ogmios.util.coalesceValueQuantities(...values);
return coins <= MAX_U64 && (!assets || Object.values(assets).every((quantity) => quantity <= MAX_U64));
const { coins, assets } = Ogmios.util.coalesceValueQuantities(values);
return (
coins <= cslUtil.MAX_U64 &&
(!assets || Object.values(assets).every((quantity) => quantity <= cslUtil.MAX_U64))
);
});
return (): Arbitrary<{
utxoAmounts: OgmiosValue[];
outputsAmounts: OgmiosValue[];
constraints: MockSelectionConstraints;
utxoAmounts: Ogmios.util.OgmiosValue[];
outputsAmounts: Ogmios.util.OgmiosValue[];
constraints: SelectionConstraints.MockSelectionConstraints;
}> =>

@@ -167,4 +179,4 @@ fc.record({

outputsAmounts: arrayOfCoinAndAssets(),
constraints: fc.record<MockSelectionConstraints>({
maxTokenBundleSize: fc.nat(AllAssets.length),
constraints: fc.record<SelectionConstraints.MockSelectionConstraints>({
maxTokenBundleSize: fc.nat(AssetId.All.length),
minimumCoinQuantity: fc.oneof(...[0n, 1n, 34_482n * 29n, 9_999_991n].map((n) => fc.constant(n))),

@@ -171,0 +183,0 @@ minimumCost: fc.oneof(...[0n, 1n, 200_000n, 2_000_003n].map((n) => fc.constant(n))),

import { CardanoSerializationLib, CSL, loadCardanoSerializationLib } from '@cardano-sdk/core';
import { createCslTestUtils, TestUtils } from './util';
import { InputSelector } from '../../src/types';
import { InputSelectionError, InputSelectionFailure } from '../../src/InputSelectionError';
import { MockSelectionConstraints, toConstraints } from './constraints';
import { SelectionConstraints } from '@cardano-sdk/util-dev';
import { assertInputSelectionProperties } from './properties';

@@ -12,15 +11,15 @@

*/
getAlgorithm: (SerializationLib: CardanoSerializationLib) => InputSelector;
getAlgorithm: (csl: CardanoSerializationLib) => InputSelector;
/**
* Available UTxO
*/
createUtxo: (utils: TestUtils) => CSL.TransactionUnspentOutput[];
createUtxo: (csl: CardanoSerializationLib) => CSL.TransactionUnspentOutput[];
/**
* Transaction outputs
*/
createOutputs: (utils: TestUtils) => CSL.TransactionOutput[];
createOutputs: (csl: CardanoSerializationLib) => CSL.TransactionOutput[];
/**
* Input selection constraints passed to the algorithm.
*/
mockConstraints: MockSelectionConstraints;
mockConstraints: SelectionConstraints.MockSelectionConstraints;
}

@@ -45,10 +44,9 @@

}: InputSelectionFailureModeTestParams) => {
const SerializationLib = await loadCardanoSerializationLib();
const utils = createCslTestUtils(SerializationLib);
const utxo = createUtxo(utils);
const outputs = createOutputs(utils);
const algorithm = getAlgorithm(SerializationLib);
await expect(algorithm.select({ utxo, outputs, constraints: toConstraints(mockConstraints) })).rejects.toThrowError(
new InputSelectionError(expectedError)
);
const csl = await loadCardanoSerializationLib();
const utxo = new Set(createUtxo(csl));
const outputs = new Set(createOutputs(csl));
const algorithm = getAlgorithm(csl);
await expect(
algorithm.select({ utxo, outputs, constraints: SelectionConstraints.mockConstraintsToConstraints(mockConstraints) })
).rejects.toThrowError(new InputSelectionError(expectedError));
};

@@ -65,9 +63,12 @@

}: InputSelectionPropertiesTestParams) => {
const SerializationLib = await loadCardanoSerializationLib();
const utils = createCslTestUtils(SerializationLib);
const utxo = createUtxo(utils);
const outputs = createOutputs(utils);
const algorithm = getAlgorithm(SerializationLib);
const results = await algorithm.select({ utxo, outputs, constraints: toConstraints(mockConstraints) });
assertInputSelectionProperties({ utils, results, outputs, constraints: mockConstraints, utxo });
const csl = await loadCardanoSerializationLib();
const utxo = new Set(createUtxo(csl));
const outputs = new Set(createOutputs(csl));
const algorithm = getAlgorithm(csl);
const results = await algorithm.select({
utxo,
outputs,
constraints: SelectionConstraints.mockConstraintsToConstraints(mockConstraints)
});
assertInputSelectionProperties({ results, outputs, constraints: mockConstraints, utxo });
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc