@koralabs/kora-labs-common 6.14.2 → 6.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -148,26 +148,41 @@ const acquireWithExecutor = async (executor, name, offline, topology, token = cr
148
148
  // The LWT timed out: it may have been applied WITH OUR TOKEN, in which case nobody (not even
149
149
  // us) could run until the lease expired. A read at the same serial consistency completes any
150
150
  // in-flight Paxos round, so it tells us the real owner.
151
- const owner = await readOwner(executor, key, serialConsistency);
152
- if (owner === token)
151
+ const read = await readOwner(executor, key, serialConsistency);
152
+ if (read.owner === token)
153
153
  return { status: 'acquired', token };
154
- if (owner)
154
+ if (read.owner)
155
155
  return { status: 'held' };
156
+ if (read.error) {
157
+ // Still unknown and we will not run: retract our possibly-applied proposal so it cannot
158
+ // block every box for the whole lease. Conditional on our token, so it never frees another's lock.
159
+ const retracted = await deleteIfOwner(executor, key, token, serialConsistency, consistency);
160
+ return { status: 'unavailable', message: `${message}; owner read failed (${read.error}); ${retracted ? 'retracted our proposal' : 'retract failed, the lease will reclaim it'}` };
161
+ }
156
162
  }
157
163
  return { status: 'unavailable', message };
158
164
  }
159
165
  };
160
166
  exports.acquireWithExecutor = acquireWithExecutor;
161
- /** The lock's owner as a linearizable (serial) read sees it; undefined when absent or unreadable. */
167
+ /** The lock's owner as a linearizable (serial) read sees it. */
162
168
  const readOwner = async (executor, key, serialConsistency) => {
163
169
  var _a;
164
170
  try {
165
171
  const result = await executor.execute(`SELECT owner FROM ${KEYSPACE}.${LOCK_TABLE} WHERE name = ?`, [key], { prepare: true, consistency: serialConsistency });
166
172
  const owner = (_a = result.rows[0]) === null || _a === void 0 ? void 0 : _a.owner;
167
- return typeof owner === 'string' ? owner : undefined;
173
+ return typeof owner === 'string' ? { owner } : {};
168
174
  }
169
- catch (_b) {
170
- return undefined;
175
+ catch (error) {
176
+ return { error: error instanceof Error ? error.message : String(error) };
177
+ }
178
+ };
179
+ const deleteIfOwner = async (executor, key, token, serialConsistency, consistency) => {
180
+ try {
181
+ await executor.execute(`DELETE FROM ${KEYSPACE}.${LOCK_TABLE} WHERE name = ? IF owner = ?`, [key, token], { prepare: true, serialConsistency, consistency });
182
+ return true;
183
+ }
184
+ catch (_a) {
185
+ return false;
171
186
  }
172
187
  };
173
188
  const acquire = async (name, offline, options = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koralabs/kora-labs-common",
3
- "version": "6.14.2",
3
+ "version": "6.15.0",
4
4
  "description": "Kora Labs Common Utilities",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -52,6 +52,7 @@
52
52
  "cassandra-driver": "^4.10.0",
53
53
  "eslint": "^8.57.1",
54
54
  "jest": "^30.4.2",
55
+ "scalus": "^0.15.0",
55
56
  "ts-jest": "^29.4.11",
56
57
  "typescript": "^5.9.3"
57
58
  },
@@ -77,7 +78,8 @@
77
78
  "@cardano-sdk/crypto": "^0.4.5",
78
79
  "@emurgo/cardano-message-signing-nodejs": "^1.1.0",
79
80
  "bip39": "^3.1.0",
80
- "cassandra-driver": "^4.9.0"
81
+ "cassandra-driver": "^4.9.0",
82
+ "scalus": "^0.15.0"
81
83
  },
82
84
  "peerDependenciesMeta": {
83
85
  "@aiken-lang/merkle-patricia-forestry": {
@@ -97,6 +99,9 @@
97
99
  },
98
100
  "cassandra-driver": {
99
101
  "optional": true
102
+ },
103
+ "scalus": {
104
+ "optional": true
100
105
  }
101
106
  }
102
107
  }
@@ -109,6 +109,7 @@ class BlockfrostTxClient {
109
109
  priceSteps: (0, fees_1.ratio)(p.price_step),
110
110
  minFeeRefScriptCostPerByte: (0, fees_1.ratio)((_a = p.min_fee_ref_script_cost_per_byte) !== null && _a !== void 0 ? _a : 0),
111
111
  coinsPerUtxoByte: BigInt(p.coins_per_utxo_size),
112
+ stakeKeyDeposit: BigInt(p.key_deposit),
112
113
  maxTxSize: Number(p.max_tx_size),
113
114
  maxTxExUnits: { memory: Number(p.max_tx_ex_mem), steps: Number(p.max_tx_ex_steps) },
114
115
  costModels
@@ -1,3 +1,4 @@
1
1
  export * from './fees';
2
2
  export * from './scriptTx';
3
3
  export * from './blockfrost';
4
+ export * from './scalus';
package/txBuild/index.js CHANGED
@@ -19,3 +19,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  __exportStar(require("./fees"), exports);
20
20
  __exportStar(require("./scriptTx"), exports);
21
21
  __exportStar(require("./blockfrost"), exports);
22
+ __exportStar(require("./scalus"), exports);
@@ -0,0 +1,26 @@
1
+ import { Cardano, Serialization } from '@cardano-sdk/core';
2
+ import { Evaluator } from './scriptTx';
3
+ /**
4
+ * Scripts come in two forms: "single CBOR" — a CBOR byte string of the flat program, as Aiken
5
+ * blueprints, Blockfrost `/scripts/{hash}/cbor` and cardano-sdk's `PlutusScript.bytes` hold it — and
6
+ * "double CBOR" — that wrapped once more, as scalus reads and writes it.
7
+ */
8
+ export declare const toDoubleCbor: (cborHex: string) => string;
9
+ export declare const toSingleCbor: (cborHex: string) => string;
10
+ /** Plutus data in the node's JSON form (what scalus.applyDataArgToScript reads). */
11
+ export declare const plutusDataToJson: (data: Serialization.PlutusData) => unknown;
12
+ /** Apply parameters (in order) to a compiled validator; returns the script in single-CBOR form. */
13
+ export declare const applyParamsToScript: (compiledCode: string, params: Serialization.PlutusData[]) => string;
14
+ /** A Plutus script (single- or double-CBOR) as a cardano-sdk Script. */
15
+ export declare const plutusScript: (cborHex: string, version: Cardano.PlutusLanguageVersion) => Cardano.PlutusScript;
16
+ /** The script hash of a (single- or double-CBOR) Plutus script of `version`. */
17
+ export declare const plutusScriptHash: (cborHex: string, version: Cardano.PlutusLanguageVersion) => string;
18
+ /**
19
+ * An Evaluator that runs the tx's scripts locally (scalus) against `utxos` — every spent AND
20
+ * referenced UTxO, reference scripts included. Offline stand-in for the node's evaluation.
21
+ */
22
+ export declare const localEvaluator: ({ utxos, costModels, network }: {
23
+ utxos: Cardano.Utxo[];
24
+ costModels: Map<Cardano.PlutusLanguageVersion, number[]>;
25
+ network: "mainnet" | "preview" | "preprod";
26
+ }) => Evaluator;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.localEvaluator = exports.plutusScriptHash = exports.plutusScript = exports.applyParamsToScript = exports.plutusDataToJson = exports.toSingleCbor = exports.toDoubleCbor = void 0;
4
+ // Local UPLC work on scalus (optional peer dependency, loaded on first use): applying parameters to a
5
+ // compiled validator (replaces Helios' UplcProgram.apply) and evaluating a tx's scripts offline.
6
+ // Production code evaluates with the node (BlockfrostTxClient.evaluateTx); the local evaluator is for
7
+ // tests and tooling that must run without a network.
8
+ const core_1 = require("@cardano-sdk/core");
9
+ let scalusModule;
10
+ // Lazy: scalus is an optional peer, needed only by callers of these helpers.
11
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
12
+ const scalus = () => (scalusModule !== null && scalusModule !== void 0 ? scalusModule : (scalusModule = require('scalus')));
13
+ const cborByteStringHeader = (length) => {
14
+ if (length <= 23)
15
+ return Buffer.from([0x40 | length]);
16
+ if (length <= 0xff)
17
+ return Buffer.from([0x58, length]);
18
+ if (length <= 0xffff)
19
+ return Buffer.from([0x59, length >> 8, length & 0xff]);
20
+ return Buffer.from([0x5a, (length >>> 24) & 0xff, (length >> 16) & 0xff, (length >> 8) & 0xff, length & 0xff]);
21
+ };
22
+ /** [header length, content length] of the CBOR byte string at the start of `bytes`. */
23
+ const byteStringHeader = (bytes) => {
24
+ if (bytes.length === 0 || bytes[0] >> 5 !== 2)
25
+ throw new Error('expected a CBOR byte string');
26
+ const info = bytes[0] & 0x1f;
27
+ if (info <= 23)
28
+ return [1, info];
29
+ if (info === 24)
30
+ return [2, bytes[1]];
31
+ if (info === 25)
32
+ return [3, bytes.readUInt16BE(1)];
33
+ if (info === 26)
34
+ return [5, bytes.readUInt32BE(1)];
35
+ throw new Error(`unsupported CBOR byte string header 0x${bytes[0].toString(16)}`);
36
+ };
37
+ const isDoubleCbor = (bytes) => {
38
+ const [headerLength] = byteStringHeader(bytes);
39
+ return bytes[headerLength] >> 5 === 2;
40
+ };
41
+ /**
42
+ * Scripts come in two forms: "single CBOR" — a CBOR byte string of the flat program, as Aiken
43
+ * blueprints, Blockfrost `/scripts/{hash}/cbor` and cardano-sdk's `PlutusScript.bytes` hold it — and
44
+ * "double CBOR" — that wrapped once more, as scalus reads and writes it.
45
+ */
46
+ const toDoubleCbor = (cborHex) => {
47
+ const bytes = Buffer.from(cborHex, 'hex');
48
+ return isDoubleCbor(bytes) ? cborHex : Buffer.concat([cborByteStringHeader(bytes.length), bytes]).toString('hex');
49
+ };
50
+ exports.toDoubleCbor = toDoubleCbor;
51
+ const toSingleCbor = (cborHex) => {
52
+ const bytes = Buffer.from(cborHex, 'hex');
53
+ if (!isDoubleCbor(bytes))
54
+ return cborHex;
55
+ const [headerLength, length] = byteStringHeader(bytes);
56
+ return bytes.subarray(headerLength, headerLength + length).toString('hex');
57
+ };
58
+ exports.toSingleCbor = toSingleCbor;
59
+ /** Plutus data in the node's JSON form (what scalus.applyDataArgToScript reads). */
60
+ const plutusDataToJson = (data) => {
61
+ const c = data.asConstrPlutusData();
62
+ if (c) {
63
+ const fields = c.getData();
64
+ return { constructor: Number(c.getAlternative()), fields: Array.from({ length: fields.getLength() }, (_, i) => (0, exports.plutusDataToJson)(fields.get(i))) };
65
+ }
66
+ const bytes = data.asBoundedBytes();
67
+ if (bytes)
68
+ return { bytes: Buffer.from(bytes).toString('hex') };
69
+ const integer = data.asInteger();
70
+ if (integer !== undefined) {
71
+ if (integer > BigInt(Number.MAX_SAFE_INTEGER) || integer < BigInt(Number.MIN_SAFE_INTEGER))
72
+ throw new Error(`integer parameter ${integer} is outside the safe range scalus accepts`);
73
+ return { int: Number(integer) };
74
+ }
75
+ const items = data.asList();
76
+ if (items)
77
+ return { list: Array.from({ length: items.getLength() }, (_, i) => (0, exports.plutusDataToJson)(items.get(i))) };
78
+ const map = data.asMap();
79
+ if (map) {
80
+ const keys = map.getKeys();
81
+ return { map: Array.from({ length: keys.getLength() }, (_, i) => ({ k: (0, exports.plutusDataToJson)(keys.get(i)), v: (0, exports.plutusDataToJson)(map.get(keys.get(i))) })) };
82
+ }
83
+ throw new Error('unsupported Plutus data');
84
+ };
85
+ exports.plutusDataToJson = plutusDataToJson;
86
+ /** Apply parameters (in order) to a compiled validator; returns the script in single-CBOR form. */
87
+ const applyParamsToScript = (compiledCode, params) => (0, exports.toSingleCbor)(params.reduce((script, param) => scalus().Scalus.applyDataArgToScript(script, JSON.stringify((0, exports.plutusDataToJson)(param))), (0, exports.toDoubleCbor)(compiledCode)));
88
+ exports.applyParamsToScript = applyParamsToScript;
89
+ /** A Plutus script (single- or double-CBOR) as a cardano-sdk Script. */
90
+ const plutusScript = (cborHex, version) => ({
91
+ __type: core_1.Cardano.ScriptType.Plutus,
92
+ version,
93
+ bytes: (0, exports.toSingleCbor)(cborHex)
94
+ });
95
+ exports.plutusScript = plutusScript;
96
+ /** The script hash of a (single- or double-CBOR) Plutus script of `version`. */
97
+ const plutusScriptHash = (cborHex, version) => core_1.Serialization.Script.fromCore((0, exports.plutusScript)(cborHex, version)).hash();
98
+ exports.plutusScriptHash = plutusScriptHash;
99
+ const PURPOSE = { spend: 'spend', mint: 'mint', cert: 'certificate', certificate: 'certificate', reward: 'withdrawal', withdraw: 'withdrawal', withdrawal: 'withdrawal' };
100
+ /**
101
+ * An Evaluator that runs the tx's scripts locally (scalus) against `utxos` — every spent AND
102
+ * referenced UTxO, reference scripts included. Offline stand-in for the node's evaluation.
103
+ */
104
+ const localEvaluator = ({ utxos, costModels, network }) => {
105
+ const writer = new core_1.Serialization.CborWriter();
106
+ writer.writeStartMap(utxos.length);
107
+ for (const [txIn, txOut] of utxos) {
108
+ writer.writeEncodedValue(Buffer.from(core_1.Serialization.TransactionInput.fromCore(txIn).toCbor(), 'hex'));
109
+ writer.writeEncodedValue(Buffer.from(core_1.Serialization.TransactionOutput.fromCore(txOut).toCbor(), 'hex'));
110
+ }
111
+ const utxoCbor = writer.encode();
112
+ const models = [core_1.Cardano.PlutusLanguageVersion.V1, core_1.Cardano.PlutusLanguageVersion.V2, core_1.Cardano.PlutusLanguageVersion.V3].map((v) => { var _a; return (_a = costModels.get(v)) !== null && _a !== void 0 ? _a : []; });
113
+ return async (txCbor) => {
114
+ const { Scalus, SlotConfig } = scalus();
115
+ const results = Scalus.evalPlutusScripts(Buffer.from(txCbor, 'hex'), utxoCbor, SlotConfig[network], models);
116
+ return new Map(results.map((r) => { var _a; return [`${(_a = PURPOSE[String(r.tag).toLowerCase()]) !== null && _a !== void 0 ? _a : String(r.tag).toLowerCase()}:${r.index}`, { memory: Number(r.budget.memory), steps: Number(r.budget.steps) }]; }));
117
+ };
118
+ };
119
+ exports.localEvaluator = localEvaluator;
@@ -3,6 +3,8 @@ import { ExUnits, FeeParameters } from './fees';
3
3
  export type PlutusData = Serialization.PlutusData;
4
4
  export interface ScriptTxProtocolParameters extends FeeParameters {
5
5
  coinsPerUtxoByte: bigint;
6
+ /** key_deposit: what a (pre-Conway-style) stake registration certificate locks. */
7
+ stakeKeyDeposit: bigint;
6
8
  maxTxSize: number;
7
9
  maxTxExUnits: {
8
10
  memory: number;
@@ -32,6 +34,8 @@ export interface ScriptTxPlan {
32
34
  quantity: bigint;
33
35
  redeemer?: PlutusData;
34
36
  }[];
37
+ /** Stake (de)registrations; their deposits/refunds are balanced into the change. No certificate redeemers. */
38
+ certificates?: Cardano.Certificate[];
35
39
  requiredSigners?: string[];
36
40
  nativeScripts?: Cardano.NativeScript[];
37
41
  validityInterval?: Cardano.ValidityInterval;
@@ -67,7 +71,15 @@ export declare const computeScriptDataHash: ({ redeemersCbor, datumsCbor, costMo
67
71
  costModels: Map<Cardano.PlutusLanguageVersion, number[]>;
68
72
  languages: Cardano.PlutusLanguageVersion[];
69
73
  }) => string | undefined;
74
+ /** Lovelace a certificate locks (positive) or releases (negative). */
75
+ export declare const certificateDeposit: (certificate: Cardano.Certificate, stakeKeyDeposit: bigint) => bigint;
70
76
  export declare const finalizeScriptTx: (plan: ScriptTxPlan, params: ScriptTxProtocolParameters, evaluate: Evaluator) => Promise<FinalizedScriptTx>;
77
+ /**
78
+ * Wallet inputs for a tx whose other inputs are fixed: ADA-only UTxOs first, largest first, until they
79
+ * hold `lovelace` (callers include a fee/min-UTxO allowance; the finalizer returns the excess as change).
80
+ * UTxOs carrying tokens are only used when ADA-only ones do not suffice (their tokens go to change).
81
+ */
82
+ export declare const selectWalletInputs: (utxos: Cardano.Utxo[], lovelace: bigint, exclude?: Set<string>) => Cardano.Utxo[];
71
83
  /**
72
84
  * Add vkey witnesses to an unsigned tx by splicing them into its witness set byte-for-byte
73
85
  * (`mergeWitnessSet`): the body (so the tx id and every signature) and the redeemer bytes the
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addVkeyWitnesses = exports.finalizeScriptTx = exports.computeScriptDataHash = exports.withMinAda = void 0;
3
+ exports.addVkeyWitnesses = exports.selectWalletInputs = exports.finalizeScriptTx = exports.certificateDeposit = exports.computeScriptDataHash = exports.withMinAda = void 0;
4
4
  // Deterministic Plutus transaction finalizer on @cardano-sdk/core (no input selection, no Helios).
5
5
  //
6
6
  // The caller fixes every input; this module derives redeemer pointers from the ledger's canonical
@@ -60,6 +60,22 @@ const placeholderSignatures = (count) => {
60
60
  signatures.set((i + 1).toString(16).padStart(64, '0'), '0'.repeat(128));
61
61
  return signatures;
62
62
  };
63
+ /** Lovelace a certificate locks (positive) or releases (negative). */
64
+ const certificateDeposit = (certificate, stakeKeyDeposit) => {
65
+ switch (certificate.__typename) {
66
+ case core_1.Cardano.CertificateType.StakeRegistration:
67
+ return stakeKeyDeposit;
68
+ case core_1.Cardano.CertificateType.StakeDeregistration:
69
+ return -stakeKeyDeposit;
70
+ case core_1.Cardano.CertificateType.Registration:
71
+ return certificate.deposit;
72
+ case core_1.Cardano.CertificateType.Unregistration:
73
+ return -certificate.deposit;
74
+ default:
75
+ throw new Error(`Certificate ${certificate.__typename} is not supported by finalizeScriptTx`);
76
+ }
77
+ };
78
+ exports.certificateDeposit = certificateDeposit;
63
79
  const sumValues = (values) => {
64
80
  var _a, _b;
65
81
  let coins = BigInt(0);
@@ -72,7 +88,7 @@ const sumValues = (values) => {
72
88
  return { coins, assets };
73
89
  };
74
90
  const finalizeScriptTx = async (plan, params, evaluate) => {
75
- var _a, _b;
91
+ var _a, _b, _c;
76
92
  const inputs = [...plan.inputs].sort((a, b) => byTxIn(a.utxo[0], b.utxo[0]));
77
93
  const mint = [...((_a = plan.mint) !== null && _a !== void 0 ? _a : [])].sort((a, b) => hexCompare(a.policyId, b.policyId));
78
94
  const withdrawals = [...((_b = plan.withdrawals) !== null && _b !== void 0 ? _b : [])].sort((a, b) => hexCompare(rewardAccountBytes(a.rewardAccount), rewardAccountBytes(b.rewardAccount)));
@@ -90,10 +106,11 @@ const finalizeScriptTx = async (plan, params, evaluate) => {
90
106
  }
91
107
  const inValue = sumValues(inputs.map(({ utxo }) => utxo[1].value));
92
108
  const withdrawn = withdrawals.reduce((sum, w) => sum + w.quantity, BigInt(0));
109
+ const deposited = ((_c = plan.certificates) !== null && _c !== void 0 ? _c : []).reduce((sum, c) => sum + (0, exports.certificateDeposit)(c, params.stakeKeyDeposit), BigInt(0));
93
110
  const produced = sumValues(outputs.map((o) => o.value));
94
111
  const changeFor = (fee) => {
95
112
  var _a, _b, _c;
96
- const coins = inValue.coins + withdrawn - produced.coins - fee;
113
+ const coins = inValue.coins + withdrawn - deposited - produced.coins - fee;
97
114
  if (coins < BigInt(0))
98
115
  throw new Error(`Inputs do not cover outputs + fee: ${-coins} lovelace short`);
99
116
  const assets = new Map();
@@ -108,7 +125,7 @@ const finalizeScriptTx = async (plan, params, evaluate) => {
108
125
  return { address: plan.changeAddress, value: { coins, ...(assets.size ? { assets } : {}) } };
109
126
  };
110
127
  const assemble = (fee, exUnits, signatures) => {
111
- var _a, _b, _c, _d;
128
+ var _a, _b, _c, _d, _e;
112
129
  const change = changeFor(fee);
113
130
  const redeemers = pointers.map(({ purpose, index, data }) => {
114
131
  const units = exUnits.get(key(purpose, index));
@@ -125,8 +142,9 @@ const finalizeScriptTx = async (plan, params, evaluate) => {
125
142
  ...(((_a = plan.referenceInputs) === null || _a === void 0 ? void 0 : _a.length) ? { referenceInputs: [...plan.referenceInputs].sort(byTxIn) } : {}),
126
143
  ...(((_b = plan.collateral) === null || _b === void 0 ? void 0 : _b.length) ? { collaterals: [...plan.collateral].sort(byTxIn) } : {}),
127
144
  ...(tokenMap.size ? { mint: tokenMap } : {}),
145
+ ...(((_c = plan.certificates) === null || _c === void 0 ? void 0 : _c.length) ? { certificates: plan.certificates } : {}),
128
146
  ...(withdrawals.length ? { withdrawals: withdrawals.map((w) => ({ stakeAddress: w.rewardAccount, quantity: w.quantity })) } : {}),
129
- ...(((_c = plan.requiredSigners) === null || _c === void 0 ? void 0 : _c.length) ? { requiredExtraSignatures: [...new Set(plan.requiredSigners)].sort(hexCompare) } : {}),
147
+ ...(((_d = plan.requiredSigners) === null || _d === void 0 ? void 0 : _d.length) ? { requiredExtraSignatures: [...new Set(plan.requiredSigners)].sort(hexCompare) } : {}),
130
148
  ...(plan.validityInterval ? { validityInterval: plan.validityInterval } : {}),
131
149
  ...(scriptIntegrityHash ? { scriptIntegrityHash: scriptIntegrityHash } : {})
132
150
  };
@@ -136,7 +154,7 @@ const finalizeScriptTx = async (plan, params, evaluate) => {
136
154
  witness: {
137
155
  signatures: signatures,
138
156
  ...(redeemers.length ? { redeemers } : {}),
139
- ...(((_d = plan.nativeScripts) === null || _d === void 0 ? void 0 : _d.length) ? { scripts: plan.nativeScripts } : {})
157
+ ...(((_e = plan.nativeScripts) === null || _e === void 0 ? void 0 : _e.length) ? { scripts: plan.nativeScripts } : {})
140
158
  },
141
159
  isValid: true
142
160
  });
@@ -183,6 +201,36 @@ const finalizeScriptTx = async (plan, params, evaluate) => {
183
201
  return { cbor: tx.toCbor(), txId: tx.getId(), fee, outputs: [...outputs, change], redeemers };
184
202
  };
185
203
  exports.finalizeScriptTx = finalizeScriptTx;
204
+ /**
205
+ * Wallet inputs for a tx whose other inputs are fixed: ADA-only UTxOs first, largest first, until they
206
+ * hold `lovelace` (callers include a fee/min-UTxO allowance; the finalizer returns the excess as change).
207
+ * UTxOs carrying tokens are only used when ADA-only ones do not suffice (their tokens go to change).
208
+ */
209
+ const selectWalletInputs = (utxos, lovelace, exclude = new Set()) => {
210
+ const key = ([txIn]) => `${txIn.txId}#${txIn.index}`;
211
+ const candidates = utxos
212
+ .filter((u) => !exclude.has(key(u)))
213
+ .sort((a, b) => {
214
+ var _a, _b;
215
+ const tokensA = ((_a = a[1].value.assets) === null || _a === void 0 ? void 0 : _a.size) ? 1 : 0;
216
+ const tokensB = ((_b = b[1].value.assets) === null || _b === void 0 ? void 0 : _b.size) ? 1 : 0;
217
+ if (tokensA !== tokensB)
218
+ return tokensA - tokensB;
219
+ return a[1].value.coins === b[1].value.coins ? 0 : a[1].value.coins > b[1].value.coins ? -1 : 1;
220
+ });
221
+ const selected = [];
222
+ let total = BigInt(0);
223
+ for (const utxo of candidates) {
224
+ if (total >= lovelace)
225
+ break;
226
+ selected.push(utxo);
227
+ total += utxo[1].value.coins;
228
+ }
229
+ if (total < lovelace)
230
+ throw new Error(`Wallet holds ${total} spendable lovelace; ${lovelace} needed`);
231
+ return selected;
232
+ };
233
+ exports.selectWalletInputs = selectWalletInputs;
186
234
  /**
187
235
  * Add vkey witnesses to an unsigned tx by splicing them into its witness set byte-for-byte
188
236
  * (`mergeWitnessSet`): the body (so the tx id and every signature) and the redeemer bytes the