@odatano/core 0.3.17 → 0.3.18
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.
- package/@cds-models/CardanoODataService/index.d.ts +434 -427
- package/@cds-models/CardanoODataService/index.d.ts.map +1 -1
- package/@cds-models/CardanoODataService/index.js +7 -0
- package/@cds-models/CardanoODataService/index.js.map +1 -1
- package/@cds-models/CardanoODataService/index.ts +99 -92
- package/@cds-models/CardanoSignService/index.d.ts +1549 -0
- package/@cds-models/CardanoSignService/index.d.ts.map +1 -0
- package/@cds-models/CardanoSignService/index.js +291 -0
- package/@cds-models/CardanoSignService/index.js.map +1 -0
- package/@cds-models/CardanoSignService/index.ts +519 -0
- package/@cds-models/CardanoTransactionService/index.d.ts +338 -815
- package/@cds-models/CardanoTransactionService/index.d.ts.map +1 -1
- package/@cds-models/CardanoTransactionService/index.js +3 -63
- package/@cds-models/CardanoTransactionService/index.js.map +1 -1
- package/@cds-models/CardanoTransactionService/index.ts +71 -210
- package/@cds-models/index.d.ts +150 -1
- package/@cds-models/index.d.ts.map +1 -1
- package/@cds-models/index.js +51 -1
- package/@cds-models/index.js.map +1 -1
- package/@cds-models/index.ts +74 -2
- package/@cds-models/odatano/cardano/index.d.ts +553 -696
- package/@cds-models/odatano/cardano/index.d.ts.map +1 -1
- package/@cds-models/odatano/cardano/index.js +3 -52
- package/@cds-models/odatano/cardano/index.js.map +1 -1
- package/@cds-models/odatano/cardano/index.ts +93 -165
- package/README.md +78 -19
- package/db/schema.cds +6 -108
- package/db/types.cds +111 -0
- package/package.json +19 -4
- package/src/index.d.ts.map +1 -1
- package/src/index.js +8 -3
- package/src/index.js.map +1 -1
- package/src/plugin.d.ts.map +1 -1
- package/src/plugin.js +7 -2
- package/src/plugin.js.map +1 -1
- package/srv/blockchain/backends/koios-backend.d.ts.map +1 -1
- package/srv/blockchain/backends/koios-backend.js +6 -16
- package/srv/blockchain/backends/koios-backend.js.map +1 -1
- package/srv/blockchain/cardano-client.d.ts.map +1 -1
- package/srv/blockchain/cardano-client.js +13 -10
- package/srv/blockchain/cardano-client.js.map +1 -1
- package/srv/blockchain/cardano-indexer.d.ts.map +1 -1
- package/srv/blockchain/cardano-indexer.js +10 -8
- package/srv/blockchain/cardano-indexer.js.map +1 -1
- package/srv/blockchain/cardano-tx-builder.d.ts.map +1 -1
- package/srv/blockchain/cardano-tx-builder.js +16 -16
- package/srv/blockchain/cardano-tx-builder.js.map +1 -1
- package/srv/blockchain/signing/hsm-signer.d.ts.map +1 -0
- package/srv/blockchain/signing/hsm-signer.js +290 -0
- package/srv/blockchain/signing/hsm-signer.js.map +1 -0
- package/srv/blockchain/signing/signature-verifier.d.ts.map +1 -1
- package/srv/blockchain/signing/signature-verifier.js +7 -25
- package/srv/blockchain/signing/signature-verifier.js.map +1 -1
- package/srv/blockchain/transaction-building/buildooor-tx.d.ts.map +1 -1
- package/srv/blockchain/transaction-building/buildooor-tx.js +171 -437
- package/srv/blockchain/transaction-building/buildooor-tx.js.map +1 -1
- package/srv/blockchain/transaction-building/cardano-tx.d.ts.map +1 -1
- package/srv/blockchain/transaction-building/csl-tx.d.ts.map +1 -1
- package/srv/blockchain/transaction-building/csl-tx.js +230 -611
- package/srv/blockchain/transaction-building/csl-tx.js.map +1 -1
- package/srv/cardano-service.cds +17 -9
- package/srv/cardano-service.js +2 -14
- package/srv/cardano-service.js.map +1 -1
- package/srv/cardano-sign-service.cds +128 -0
- package/srv/cardano-sign-service.d.ts.map +1 -0
- package/srv/cardano-sign-service.js +401 -0
- package/srv/cardano-sign-service.js.map +1 -0
- package/srv/cardano-tx-service.cds +116 -196
- package/srv/cardano-tx-service.js +5 -308
- package/srv/cardano-tx-service.js.map +1 -1
- package/srv/server.d.ts.map +1 -1
- package/srv/server.js +60 -5
- package/srv/server.js.map +1 -1
- package/srv/utils/const.d.ts.map +1 -1
- package/srv/utils/const.js +5 -1
- package/srv/utils/const.js.map +1 -1
- package/srv/utils/error-codes.d.ts.map +1 -1
- package/srv/utils/error-codes.js +15 -0
- package/srv/utils/error-codes.js.map +1 -1
- package/srv/utils/errors.d.ts.map +1 -1
- package/srv/utils/errors.js +12 -1
- package/srv/utils/errors.js.map +1 -1
- package/srv/utils/mappers.d.ts.map +1 -1
- package/srv/utils/mappers.js +9 -29
- package/srv/utils/mappers.js.map +1 -1
- package/srv/utils/signing-helper.d.ts.map +1 -1
- package/srv/utils/signing-helper.js +3 -18
- package/srv/utils/signing-helper.js.map +1 -1
- package/srv/utils/tx-build-helper.d.ts.map +1 -1
- package/srv/utils/tx-build-helper.js +10 -4
- package/srv/utils/tx-build-helper.js.map +1 -1
- package/srv/utils/types.d.ts.map +1 -1
- package/srv/utils/types.js +2 -0
- package/srv/utils/types.js.map +1 -1
|
@@ -64,373 +64,94 @@ class CSLTxBuilder {
|
|
|
64
64
|
logger.info(`Initialized with protocol parameters.`);
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
|
-
* Build unsigned ADA
|
|
68
|
-
* @param req transaction build request
|
|
69
|
-
* @param ctx transaction build context
|
|
70
|
-
* @returns {Promise<TxBuildResult>} transaction build result
|
|
67
|
+
* Build unsigned transfer transaction (ADA-only or with native assets)
|
|
71
68
|
*/
|
|
72
|
-
async
|
|
69
|
+
async buildUnsignedTransfer(req, ctx) {
|
|
73
70
|
try {
|
|
74
|
-
// prepare addresses
|
|
75
71
|
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
76
72
|
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
77
|
-
// map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support)
|
|
78
|
-
// This allows spending UTxOs that contain native assets - they will be returned in the change output
|
|
79
73
|
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
80
|
-
// create Transaction Builder from stored config
|
|
81
74
|
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
const outValue = CSL.Value.new(amount);
|
|
85
|
-
// Optional: add native assets to the output (for locking tokens at script address)
|
|
75
|
+
// Build output value (lovelace + optional native assets)
|
|
76
|
+
const outValue = CSL.Value.new(CSL.BigNum.from_str(String(req.lovelaceAmount)));
|
|
86
77
|
if (req.assets && req.assets.length > 0) {
|
|
87
|
-
|
|
88
|
-
for (const asset of req.assets) {
|
|
89
|
-
if (asset.unit === 'lovelace')
|
|
90
|
-
continue;
|
|
91
|
-
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
92
|
-
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
93
|
-
let assets = multiAsset.get(policyHash);
|
|
94
|
-
if (!assets)
|
|
95
|
-
assets = CSL.Assets.new();
|
|
96
|
-
assets.insert(CSL.AssetName.new(Buffer.from(assetName, 'hex')), CSL.BigNum.from_str(asset.quantity));
|
|
97
|
-
multiAsset.insert(policyHash, assets);
|
|
98
|
-
}
|
|
99
|
-
outValue.set_multiasset(multiAsset);
|
|
78
|
+
outValue.set_multiasset(this._buildCslMultiAsset(req.assets));
|
|
100
79
|
}
|
|
101
80
|
const out = CSL.TransactionOutput.new(recipientAddress, outValue);
|
|
102
|
-
|
|
103
|
-
const plutusData = CSL.PlutusData.from_json(JSON.stringify(req.outputDatum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
104
|
-
out.set_plutus_data(plutusData);
|
|
105
|
-
}
|
|
81
|
+
this._attachInlineDatum(out, req.outputDatum);
|
|
106
82
|
txb.add_output(out);
|
|
107
|
-
// add inputs via coin selection + add change
|
|
108
83
|
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
109
84
|
txb.add_change_if_needed(changeAddress);
|
|
110
|
-
// build unsigned tx
|
|
111
85
|
const unsignedTx = txb.build_tx();
|
|
112
|
-
|
|
113
|
-
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
114
|
-
// hash + fee + outputs
|
|
115
|
-
const body = unsignedTx.body();
|
|
116
|
-
const bodyBytes = body.to_bytes();
|
|
117
|
-
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
118
|
-
const txBodyHash = hash;
|
|
119
|
-
const feeLovelace = body.fee().to_str();
|
|
120
|
-
const outputs = [];
|
|
121
|
-
const txOuts = body.outputs();
|
|
122
|
-
for (let i = 0; i < txOuts.len(); i++) {
|
|
123
|
-
const o = txOuts.get(i);
|
|
124
|
-
outputs.push({
|
|
125
|
-
address: o.address().to_bech32(),
|
|
126
|
-
lovelace: o.amount().coin().to_str(),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
86
|
+
const txDetails = this._extractTxDetails(unsignedTx);
|
|
129
87
|
logger.info(`Built unsigned transaction successfully.`);
|
|
130
|
-
return
|
|
131
|
-
unsignedTxCbor,
|
|
132
|
-
txBodyHash,
|
|
133
|
-
senderAddress: req.senderAddress,
|
|
134
|
-
network: this.cardanoClient.network,
|
|
135
|
-
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
136
|
-
builderEngine: this.name,
|
|
137
|
-
feeLovelace: feeLovelace,
|
|
138
|
-
inputs: ctx.utxos.map(u => ({
|
|
139
|
-
txHash: u.txHash,
|
|
140
|
-
index: u.outputIndex,
|
|
141
|
-
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
142
|
-
})),
|
|
143
|
-
outputs,
|
|
144
|
-
warnings: [],
|
|
145
|
-
};
|
|
88
|
+
return this._buildResult(req, ctx, txDetails);
|
|
146
89
|
}
|
|
147
90
|
catch (err) {
|
|
148
|
-
(0, tx_build_helper_1.mapBuilderError)(err
|
|
91
|
+
(0, tx_build_helper_1.mapBuilderError)(err);
|
|
149
92
|
}
|
|
150
93
|
}
|
|
151
94
|
async buildUnsignedTransactionWithMetadata(req, ctx) {
|
|
152
95
|
try {
|
|
153
|
-
// prepare addresses
|
|
154
96
|
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
155
97
|
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
156
|
-
// map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support)
|
|
157
|
-
// This allows spending UTxOs that contain native assets - they will be returned in the change output
|
|
158
98
|
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
159
|
-
// create Transaction Builder from stored config
|
|
160
99
|
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
161
|
-
// add recipient & output (lovelace)
|
|
162
100
|
const amount = CSL.BigNum.from_str(String(req.lovelaceAmount));
|
|
163
101
|
const outValue = CSL.Value.new(amount);
|
|
164
102
|
const out = CSL.TransactionOutput.new(recipientAddress, outValue);
|
|
165
103
|
txb.add_output(out);
|
|
166
|
-
// add metadata if provided
|
|
167
104
|
if (req.metadataJson) {
|
|
168
105
|
const metadata = this._mapOdatanoMetadataToCSLMetadata(req.metadataJson);
|
|
169
106
|
txb.set_metadata(metadata);
|
|
170
107
|
}
|
|
171
|
-
// add inputs via coin selection + add change
|
|
172
108
|
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
173
109
|
txb.add_change_if_needed(changeAddress);
|
|
174
|
-
// build unsigned tx
|
|
175
110
|
const unsignedTx = txb.build_tx();
|
|
176
|
-
|
|
177
|
-
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
178
|
-
// hash + fee + outputs
|
|
179
|
-
const body = unsignedTx.body();
|
|
180
|
-
const bodyBytes = body.to_bytes();
|
|
181
|
-
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
182
|
-
const txBodyHash = hash;
|
|
183
|
-
const feeLovelace = body.fee().to_str();
|
|
184
|
-
const outputs = [];
|
|
185
|
-
const txOuts = body.outputs();
|
|
186
|
-
for (let i = 0; i < txOuts.len(); i++) {
|
|
187
|
-
const o = txOuts.get(i);
|
|
188
|
-
outputs.push({
|
|
189
|
-
address: o.address().to_bech32(),
|
|
190
|
-
lovelace: o.amount().coin().to_str(),
|
|
191
|
-
});
|
|
192
|
-
}
|
|
111
|
+
const txDetails = this._extractTxDetails(unsignedTx);
|
|
193
112
|
logger.info(`Built unsigned transaction with metadata successfully.`);
|
|
194
|
-
return
|
|
195
|
-
unsignedTxCbor,
|
|
196
|
-
txBodyHash,
|
|
197
|
-
senderAddress: req.senderAddress,
|
|
198
|
-
network: this.cardanoClient.network,
|
|
199
|
-
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
200
|
-
builderEngine: this.name,
|
|
201
|
-
feeLovelace,
|
|
202
|
-
inputs: ctx.utxos.map(u => ({
|
|
203
|
-
txHash: u.txHash,
|
|
204
|
-
index: u.outputIndex,
|
|
205
|
-
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
206
|
-
})),
|
|
207
|
-
outputs,
|
|
208
|
-
warnings: [],
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
catch (err) {
|
|
212
|
-
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async buildUnsignedMultiAssetTransaction(req, ctx) {
|
|
216
|
-
if (!req.assets || req.assets.length === 0) {
|
|
217
|
-
throw new Error('[CSLTxBuilder] buildUnsignedMultiAssetTransaction requires assets to be specified');
|
|
218
|
-
}
|
|
219
|
-
try {
|
|
220
|
-
// prepare addresses
|
|
221
|
-
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
222
|
-
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
223
|
-
// map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support)
|
|
224
|
-
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
225
|
-
// create Transaction Builder from stored config
|
|
226
|
-
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
227
|
-
// Build output value with ADA + multi-assets
|
|
228
|
-
const lovelace = CSL.BigNum.from_str(String(req.lovelaceAmount));
|
|
229
|
-
const outputValue = CSL.Value.new(lovelace);
|
|
230
|
-
// Create MultiAsset structure for native tokens
|
|
231
|
-
const multiAsset = CSL.MultiAsset.new();
|
|
232
|
-
for (const asset of req.assets) {
|
|
233
|
-
// Skip 'lovelace' unit (already handled above)
|
|
234
|
-
if (asset.unit === 'lovelace')
|
|
235
|
-
continue;
|
|
236
|
-
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
237
|
-
// Get or create Assets for this policy
|
|
238
|
-
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
239
|
-
let assets = multiAsset.get(policyHash);
|
|
240
|
-
if (!assets) {
|
|
241
|
-
assets = CSL.Assets.new();
|
|
242
|
-
}
|
|
243
|
-
// Add asset with quantity
|
|
244
|
-
const assetNameBytes = Buffer.from(assetName, 'hex');
|
|
245
|
-
const cslAssetName = CSL.AssetName.new(assetNameBytes);
|
|
246
|
-
const quantity = CSL.BigNum.from_str(asset.quantity);
|
|
247
|
-
assets.insert(cslAssetName, quantity);
|
|
248
|
-
multiAsset.insert(policyHash, assets);
|
|
249
|
-
}
|
|
250
|
-
// Set multi-asset on output value
|
|
251
|
-
outputValue.set_multiasset(multiAsset);
|
|
252
|
-
// Create output with all assets + optional inline datum
|
|
253
|
-
const recipientOutput = CSL.TransactionOutput.new(recipientAddress, outputValue);
|
|
254
|
-
if (req.outputDatum) {
|
|
255
|
-
const plutusData = CSL.PlutusData.from_json(JSON.stringify(req.outputDatum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
256
|
-
recipientOutput.set_plutus_data(plutusData);
|
|
257
|
-
}
|
|
258
|
-
txb.add_output(recipientOutput);
|
|
259
|
-
// add inputs via coin selection + add change
|
|
260
|
-
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
261
|
-
txb.add_change_if_needed(changeAddress);
|
|
262
|
-
// build unsigned tx
|
|
263
|
-
const unsignedTx = txb.build_tx();
|
|
264
|
-
// Export the complete transaction (with empty witness set) for cardano-cli
|
|
265
|
-
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
266
|
-
// hash + fee + outputs
|
|
267
|
-
const body = unsignedTx.body();
|
|
268
|
-
const bodyBytes = body.to_bytes();
|
|
269
|
-
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
270
|
-
const txBodyHash = hash;
|
|
271
|
-
const feeLovelace = body.fee().to_str();
|
|
272
|
-
const outputs = [];
|
|
273
|
-
const txOuts = body.outputs();
|
|
274
|
-
for (let i = 0; i < txOuts.len(); i++) {
|
|
275
|
-
const o = txOuts.get(i);
|
|
276
|
-
outputs.push({
|
|
277
|
-
address: o.address().to_bech32(),
|
|
278
|
-
lovelace: o.amount().coin().to_str(),
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
logger.info(`Built unsigned multi-asset transaction successfully.`);
|
|
282
|
-
return {
|
|
283
|
-
unsignedTxCbor,
|
|
284
|
-
txBodyHash,
|
|
285
|
-
senderAddress: req.senderAddress,
|
|
286
|
-
network: this.cardanoClient.network,
|
|
287
|
-
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
288
|
-
builderEngine: this.name,
|
|
289
|
-
feeLovelace,
|
|
290
|
-
inputs: ctx.utxos.map(u => ({
|
|
291
|
-
txHash: u.txHash,
|
|
292
|
-
index: u.outputIndex,
|
|
293
|
-
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
294
|
-
})),
|
|
295
|
-
outputs,
|
|
296
|
-
warnings: [],
|
|
297
|
-
};
|
|
113
|
+
return this._buildResult(req, ctx, txDetails);
|
|
298
114
|
}
|
|
299
115
|
catch (err) {
|
|
300
|
-
|
|
301
|
-
const assetMatch = err?.message?.match(/not enough\s+([a-f0-9.]+)/i);
|
|
302
|
-
const assetUnit = assetMatch?.[1] || 'assets';
|
|
303
|
-
(0, tx_build_helper_1.mapBuilderError)(err, assetUnit);
|
|
116
|
+
(0, tx_build_helper_1.mapBuilderError)(err);
|
|
304
117
|
}
|
|
305
118
|
}
|
|
306
119
|
async buildUnsignedMintTransaction(req, ctx) {
|
|
307
120
|
try {
|
|
308
|
-
|
|
309
|
-
let finalExUnits = {
|
|
310
|
-
mem: String(const_1.DEFAULT_EXECUTION_UNITS.mem),
|
|
311
|
-
cpu: String(const_1.DEFAULT_EXECUTION_UNITS.cpu)
|
|
312
|
-
};
|
|
313
|
-
if (ctx.evaluateTransaction) {
|
|
314
|
-
// Build first pass with high execution units for evaluation
|
|
315
|
-
logger.debug(`[CSLTxBuilder] Building evaluation pass with high execution units`);
|
|
316
|
-
const evalTx = this._buildMintTx(req, ctx, {
|
|
317
|
-
mem: String(const_1.HIGH_EXECUTION_UNITS.mem),
|
|
318
|
-
cpu: String(const_1.HIGH_EXECUTION_UNITS.cpu)
|
|
319
|
-
});
|
|
320
|
-
const evalTxCbor = Buffer.from(evalTx.to_bytes()).toString('hex');
|
|
321
|
-
try {
|
|
322
|
-
// Evaluate to get exact execution units
|
|
323
|
-
const evalResults = await ctx.evaluateTransaction(evalTxCbor);
|
|
324
|
-
logger.debug(`[CSLTxBuilder] Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
325
|
-
if (evalResults && evalResults.length > 0) {
|
|
326
|
-
// Use the evaluated budget (take first result for single script)
|
|
327
|
-
const budget = evalResults[0].budget;
|
|
328
|
-
// Add safety margin to evaluated units
|
|
329
|
-
finalExUnits = {
|
|
330
|
-
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER).toString(),
|
|
331
|
-
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER).toString()
|
|
332
|
-
};
|
|
333
|
-
logger.info(`[CSLTxBuilder] Using evaluated execution units: mem=${finalExUnits.mem}, cpu=${finalExUnits.cpu}`);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
catch (evalError) {
|
|
337
|
-
logger.warn(`[CSLTxBuilder] Evaluation failed, using default units: ${evalError.message}`);
|
|
338
|
-
// Fall back to defaults on evaluation failure
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
else {
|
|
342
|
-
logger.debug(`[CSLTxBuilder] No evaluator available, using default execution units`);
|
|
343
|
-
}
|
|
344
|
-
// Build final transaction with determined execution units
|
|
121
|
+
const finalExUnits = await this._evaluateExUnits((exUnits) => this._buildMintTx(req, ctx, exUnits), ctx.evaluateTransaction);
|
|
345
122
|
const unsignedTx = this._buildMintTx(req, ctx, finalExUnits);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const body = unsignedTx.body();
|
|
351
|
-
const bodyBytes = body.to_bytes();
|
|
352
|
-
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
353
|
-
const txBodyHash = hash;
|
|
354
|
-
const feeLovelace = body.fee().to_str();
|
|
355
|
-
// Extract outputs
|
|
356
|
-
const outputs = [];
|
|
357
|
-
const txOuts = body.outputs();
|
|
358
|
-
for (let i = 0; i < txOuts.len(); i++) {
|
|
359
|
-
const o = txOuts.get(i);
|
|
360
|
-
outputs.push({
|
|
361
|
-
address: o.address().to_bech32(),
|
|
362
|
-
lovelace: o.amount().coin().to_str(),
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
// Compute script hash from the minting policy script (= policy ID)
|
|
366
|
-
const mintScriptBytes = Buffer.from(req.mintingPolicyScript, 'hex');
|
|
367
|
-
const mintPlutusScript = CSL.PlutusScript.new_v3(mintScriptBytes);
|
|
368
|
-
const mintScriptHash = Buffer.from(mintPlutusScript.hash().to_bytes()).toString('hex');
|
|
369
|
-
logger.info(`[CSLTxBuilder] Built unsigned minting transaction successfully. Fee: ${feeLovelace}`);
|
|
370
|
-
return {
|
|
371
|
-
unsignedTxCbor,
|
|
372
|
-
txBodyHash,
|
|
373
|
-
senderAddress: req.senderAddress,
|
|
374
|
-
network: this.cardanoClient.network,
|
|
375
|
-
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
376
|
-
builderEngine: this.name,
|
|
377
|
-
feeLovelace,
|
|
378
|
-
inputs: ctx.utxos.map(u => ({
|
|
379
|
-
txHash: u.txHash,
|
|
380
|
-
index: u.outputIndex,
|
|
381
|
-
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
382
|
-
})),
|
|
383
|
-
outputs,
|
|
384
|
-
scriptHash: mintScriptHash,
|
|
385
|
-
warnings: [],
|
|
386
|
-
};
|
|
123
|
+
const txDetails = this._extractTxDetails(unsignedTx);
|
|
124
|
+
const { hashHex: scriptHash } = this._computeScriptHash(req.mintingPolicyScript);
|
|
125
|
+
logger.info(`Built unsigned minting transaction successfully. Fee: ${txDetails.feeLovelace}`);
|
|
126
|
+
return this._buildResult(req, ctx, txDetails, { scriptHash });
|
|
387
127
|
}
|
|
388
128
|
catch (error) {
|
|
389
|
-
logger.error(`
|
|
390
|
-
(0, tx_build_helper_1.mapBuilderError)(error
|
|
129
|
+
logger.error(`BuildUnsignedMintTransaction error: ${error?.message || error}`);
|
|
130
|
+
(0, tx_build_helper_1.mapBuilderError)(error);
|
|
391
131
|
}
|
|
392
132
|
}
|
|
393
133
|
/**
|
|
394
134
|
* Helper to build mint transaction with specified execution units
|
|
395
135
|
*/
|
|
396
136
|
_buildMintTx(req, ctx, exUnits) {
|
|
397
|
-
// Prepare addresses
|
|
398
137
|
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
399
138
|
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
400
|
-
// Create Transaction Builder
|
|
401
139
|
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
402
|
-
|
|
403
|
-
const scriptBytes = Buffer.from(req.mintingPolicyScript, 'hex');
|
|
404
|
-
const plutusScript = CSL.PlutusScript.new_v3(scriptBytes);
|
|
405
|
-
const scriptHash = plutusScript.hash();
|
|
406
|
-
// Create PlutusScriptSource - wrapper for the script
|
|
140
|
+
const { plutusScript, cslHash: scriptHash } = this._computeScriptHash(req.mintingPolicyScript);
|
|
407
141
|
const scriptSource = CSL.PlutusScriptSource.new(plutusScript);
|
|
408
|
-
// Create MintBuilder
|
|
142
|
+
// Create MintBuilder and track total minted value for output
|
|
409
143
|
const mintBuilder = CSL.MintBuilder.new();
|
|
410
|
-
// Track total minted value for output
|
|
411
144
|
let totalMintedValue = CSL.Value.new(CSL.BigNum.from_str('0'));
|
|
412
|
-
// Process each mint action
|
|
413
145
|
for (const mintAction of req.mintActions) {
|
|
414
146
|
const { assetName } = (0, tx_build_helper_1.parseAssetUnit)(mintAction.assetUnit);
|
|
415
|
-
|
|
416
|
-
const assetNameBytes = Buffer.from(assetName, 'hex');
|
|
417
|
-
const cslAssetName = CSL.AssetName.new(assetNameBytes);
|
|
418
|
-
// Create mint quantity (can be negative for burning)
|
|
147
|
+
const cslAssetName = CSL.AssetName.new(Buffer.from(assetName, 'hex'));
|
|
419
148
|
const mintQuantity = CSL.Int.new_i32(Number(mintAction.quantity));
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
redeemerData = CSL.PlutusData.from_json(JSON.stringify(req.mintRedeemer), CSL.PlutusDatumSchema.DetailedSchema);
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
redeemerData = CSL.PlutusData.new_integer(CSL.BigInt.from_str(String(mintAction.redeemer ?? 0)));
|
|
427
|
-
}
|
|
149
|
+
const redeemerData = req.mintRedeemer
|
|
150
|
+
? this._toPlutusData(req.mintRedeemer)
|
|
151
|
+
: CSL.PlutusData.new_integer(CSL.BigInt.from_str(String(mintAction.redeemer ?? 0)));
|
|
428
152
|
const redeemer = CSL.Redeemer.new(CSL.RedeemerTag.new_mint(), CSL.BigNum.from_str('0'), redeemerData, CSL.ExUnits.new(CSL.BigNum.from_str(exUnits.mem), CSL.BigNum.from_str(exUnits.cpu)));
|
|
429
|
-
// Create MintWitness directly from PlutusScriptSource + Redeemer
|
|
430
153
|
const mintWitness = CSL.MintWitness.new_plutus_script(scriptSource, redeemer);
|
|
431
|
-
// Add to mint builder
|
|
432
154
|
mintBuilder.add_asset(mintWitness, cslAssetName, mintQuantity);
|
|
433
|
-
// Add to total minted value for output construction (only if quantity > 0)
|
|
434
155
|
if (Number(mintAction.quantity) > 0) {
|
|
435
156
|
const assetValue = CSL.Value.new(CSL.BigNum.from_str('0'));
|
|
436
157
|
const multiAsset = CSL.MultiAsset.new();
|
|
@@ -441,43 +162,16 @@ class CSLTxBuilder {
|
|
|
441
162
|
totalMintedValue = totalMintedValue.checked_add(assetValue);
|
|
442
163
|
}
|
|
443
164
|
}
|
|
444
|
-
// Set the mint builder on transaction
|
|
445
165
|
txb.set_mint_builder(mintBuilder);
|
|
446
|
-
|
|
447
|
-
if (req.requiredSigners?.length) {
|
|
448
|
-
for (const signerHex of req.requiredSigners) {
|
|
449
|
-
txb.add_required_signer(CSL.Ed25519KeyHash.from_bytes(Buffer.from(signerHex, 'hex')));
|
|
450
|
-
}
|
|
451
|
-
}
|
|
166
|
+
this._addRequiredSigners(txb, req.requiredSigners);
|
|
452
167
|
// Create output with minted assets + minimum lovelace
|
|
453
|
-
const
|
|
454
|
-
const outputValue = CSL.Value.new(minLovelace);
|
|
168
|
+
const outputValue = CSL.Value.new(CSL.BigNum.from_str(String(req.lovelaceAmount)));
|
|
455
169
|
const finalOutputValue = outputValue.checked_add(totalMintedValue);
|
|
456
170
|
const recipientOutput = CSL.TransactionOutput.new(recipientAddress, finalOutputValue);
|
|
457
|
-
|
|
458
|
-
const datumData = CSL.PlutusData.from_json(JSON.stringify(req.inlineDatum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
459
|
-
recipientOutput.set_plutus_data(datumData);
|
|
460
|
-
}
|
|
171
|
+
this._attachInlineDatum(recipientOutput, req.inlineDatum);
|
|
461
172
|
txb.add_output(recipientOutput);
|
|
462
|
-
//
|
|
463
|
-
const
|
|
464
|
-
if (collateralOdatanoUtxo) {
|
|
465
|
-
const collateralBuilder = CSL.TxInputsBuilder.new();
|
|
466
|
-
const txHash = CSL.TransactionHash.from_bytes(Buffer.from(collateralOdatanoUtxo.txHash, 'hex'));
|
|
467
|
-
const input = CSL.TransactionInput.new(txHash, collateralOdatanoUtxo.outputIndex);
|
|
468
|
-
const address = CSL.Address.from_bech32(collateralOdatanoUtxo.address);
|
|
469
|
-
const value = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(collateralOdatanoUtxo).toString()));
|
|
470
|
-
collateralBuilder.add_regular_input(address, input, value);
|
|
471
|
-
txb.set_collateral(collateralBuilder);
|
|
472
|
-
}
|
|
473
|
-
// Exclude collateral UTxO so it is not consumed on successful tx.
|
|
474
|
-
let fundingUtxos = collateralOdatanoUtxo
|
|
475
|
-
? ctx.utxos.filter(u => !(u.txHash === collateralOdatanoUtxo.txHash && u.outputIndex === collateralOdatanoUtxo.outputIndex))
|
|
476
|
-
: ctx.utxos;
|
|
477
|
-
if (fundingUtxos.length === 0 && ctx.utxos.length > 0) {
|
|
478
|
-
logger.debug('[CSLTxBuilder] No funding UTxOs after collateral exclusion, including collateral in funding pool');
|
|
479
|
-
fundingUtxos = ctx.utxos;
|
|
480
|
-
}
|
|
173
|
+
// Collateral + funding UTxO separation
|
|
174
|
+
const { fundingUtxos } = this._setupCollateral(txb, ctx.utxos);
|
|
481
175
|
// Add all funding UTxOs as explicit inputs instead of using coin selection.
|
|
482
176
|
// CSL's add_inputs_from does not properly forward native tokens from selected
|
|
483
177
|
// inputs to the change output during Plutus minting transactions, causing
|
|
@@ -487,22 +181,7 @@ class CSLTxBuilder {
|
|
|
487
181
|
const fTxHash = CSL.TransactionHash.from_bytes(Buffer.from(u.txHash, 'hex'));
|
|
488
182
|
const fInput = CSL.TransactionInput.new(fTxHash, u.outputIndex);
|
|
489
183
|
const fAddress = CSL.Address.from_bech32(u.address);
|
|
490
|
-
const
|
|
491
|
-
const fValue = CSL.Value.new(fLovelace);
|
|
492
|
-
const fNonAdaAssets = u.amount.filter(a => a.unit.toLowerCase() !== 'lovelace' && BigInt(a.quantity) > 0n);
|
|
493
|
-
if (fNonAdaAssets.length > 0) {
|
|
494
|
-
const fMultiAsset = CSL.MultiAsset.new();
|
|
495
|
-
for (const asset of fNonAdaAssets) {
|
|
496
|
-
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
497
|
-
const pH = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
498
|
-
let assets = fMultiAsset.get(pH);
|
|
499
|
-
if (!assets)
|
|
500
|
-
assets = CSL.Assets.new();
|
|
501
|
-
assets.insert(CSL.AssetName.new(Buffer.from(assetName, 'hex')), CSL.BigNum.from_str(asset.quantity));
|
|
502
|
-
fMultiAsset.insert(pH, assets);
|
|
503
|
-
}
|
|
504
|
-
fValue.set_multiasset(fMultiAsset);
|
|
505
|
-
}
|
|
184
|
+
const fValue = this._buildCslValue((0, tx_build_helper_1.getLovelace)(u), u.amount);
|
|
506
185
|
fundingInputsBuilder.add_regular_input(fAddress, fInput, fValue);
|
|
507
186
|
}
|
|
508
187
|
txb.set_inputs(fundingInputsBuilder);
|
|
@@ -523,8 +202,7 @@ class CSLTxBuilder {
|
|
|
523
202
|
policy.set(an, (policy.get(an) || 0n) + BigInt(a.quantity));
|
|
524
203
|
}
|
|
525
204
|
}
|
|
526
|
-
// Adjust for burned tokens (negative mint quantities)
|
|
527
|
-
// destroyed, so the change output must not include them.
|
|
205
|
+
// Adjust for burned tokens (negative mint quantities)
|
|
528
206
|
const scriptHashHex = Buffer.from(scriptHash.to_bytes()).toString('hex');
|
|
529
207
|
for (const mintAction of req.mintActions) {
|
|
530
208
|
const qty = BigInt(mintAction.quantity);
|
|
@@ -559,99 +237,26 @@ class CSLTxBuilder {
|
|
|
559
237
|
const nativeChangeValue = CSL.Value.new(CSL.BigNum.from_str('2000000'));
|
|
560
238
|
nativeChangeValue.set_multiasset(nativeChangeMA);
|
|
561
239
|
txb.add_output(CSL.TransactionOutput.new(changeAddress, nativeChangeValue));
|
|
562
|
-
logger.debug(
|
|
240
|
+
logger.debug(`Added explicit change output for existing native tokens from funding UTxOs`);
|
|
563
241
|
}
|
|
564
|
-
// Calculate script data hash BEFORE add_change_if_needed
|
|
565
|
-
// Always call calc_script_data_hash for Plutus transactions - CSL requires it
|
|
566
242
|
const costModels = this._createCostModels('v3');
|
|
567
243
|
txb.calc_script_data_hash(costModels);
|
|
568
|
-
// Now add change
|
|
569
244
|
txb.add_change_if_needed(changeAddress);
|
|
570
|
-
// Build transaction and patch scriptDataHash (CSL's is incorrect for Conway PlutusV3)
|
|
571
245
|
const tx = txb.build_tx();
|
|
572
246
|
return this._patchScriptDataHash(tx);
|
|
573
247
|
}
|
|
574
248
|
async buildUnsignedPlutusSpendTransaction(req, ctx) {
|
|
575
249
|
try {
|
|
576
|
-
|
|
577
|
-
let finalExUnits = {
|
|
578
|
-
mem: const_1.DEFAULT_EXECUTION_UNITS.mem.toString(),
|
|
579
|
-
cpu: const_1.DEFAULT_EXECUTION_UNITS.cpu.toString()
|
|
580
|
-
};
|
|
581
|
-
if (ctx.evaluateTransaction) {
|
|
582
|
-
// Build evaluation pass with high execution units
|
|
583
|
-
logger.debug(`[CSLTxBuilder] Building evaluation pass for Plutus spending`);
|
|
584
|
-
const highExUnits = {
|
|
585
|
-
mem: const_1.HIGH_EXECUTION_UNITS.mem.toString(),
|
|
586
|
-
cpu: const_1.HIGH_EXECUTION_UNITS.cpu.toString()
|
|
587
|
-
};
|
|
588
|
-
const evalTx = this._buildPlutusSpendTx(req, ctx, highExUnits);
|
|
589
|
-
const evalTxCbor = Buffer.from(evalTx.to_bytes()).toString('hex');
|
|
590
|
-
try {
|
|
591
|
-
const evalResults = await ctx.evaluateTransaction(evalTxCbor);
|
|
592
|
-
logger.debug(`[CSLTxBuilder] Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
593
|
-
if (evalResults && evalResults.length > 0) {
|
|
594
|
-
const budget = evalResults[0].budget;
|
|
595
|
-
finalExUnits = {
|
|
596
|
-
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER).toString(),
|
|
597
|
-
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER).toString()
|
|
598
|
-
};
|
|
599
|
-
logger.info(`[CSLTxBuilder] Using evaluated execution units: mem=${finalExUnits.mem}, cpu=${finalExUnits.cpu}`);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
catch (evalError) {
|
|
603
|
-
logger.warn(`[CSLTxBuilder] Evaluation failed, using default units: ${evalError.message}`);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
logger.debug(`[CSLTxBuilder] No evaluator available, using default execution units`);
|
|
608
|
-
}
|
|
609
|
-
// Build final transaction with determined execution units
|
|
250
|
+
const finalExUnits = await this._evaluateExUnits((exUnits) => this._buildPlutusSpendTx(req, ctx, exUnits), ctx.evaluateTransaction);
|
|
610
251
|
const unsignedTx = this._buildPlutusSpendTx(req, ctx, finalExUnits);
|
|
611
|
-
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const bodyBytes = body.to_bytes();
|
|
616
|
-
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
617
|
-
const txBodyHash = hash;
|
|
618
|
-
const feeLovelace = body.fee().to_str();
|
|
619
|
-
// Extract outputs
|
|
620
|
-
const outputs = [];
|
|
621
|
-
const txOuts = body.outputs();
|
|
622
|
-
for (let i = 0; i < txOuts.len(); i++) {
|
|
623
|
-
const o = txOuts.get(i);
|
|
624
|
-
outputs.push({
|
|
625
|
-
address: o.address().to_bech32(),
|
|
626
|
-
lovelace: o.amount().coin().to_str(),
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
// Compute script hash from the validator script
|
|
630
|
-
const spendScriptBytes = Buffer.from(req.plutusScriptExecution.validatorScript, 'hex');
|
|
631
|
-
const spendPlutusScript = CSL.PlutusScript.new_v3(spendScriptBytes);
|
|
632
|
-
const spendScriptHash = Buffer.from(spendPlutusScript.hash().to_bytes()).toString('hex');
|
|
633
|
-
logger.info(`[CSLTxBuilder] Built unsigned Plutus spending transaction. Fee: ${feeLovelace}`);
|
|
634
|
-
return {
|
|
635
|
-
unsignedTxCbor,
|
|
636
|
-
txBodyHash,
|
|
637
|
-
senderAddress: req.senderAddress,
|
|
638
|
-
network: this.cardanoClient.network,
|
|
639
|
-
sizeBytes: unsignedTxCbor.length / 2,
|
|
640
|
-
builderEngine: this.name,
|
|
641
|
-
feeLovelace,
|
|
642
|
-
inputs: ctx.utxos.map(u => ({
|
|
643
|
-
txHash: u.txHash,
|
|
644
|
-
index: u.outputIndex,
|
|
645
|
-
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
646
|
-
})),
|
|
647
|
-
outputs,
|
|
648
|
-
scriptHash: spendScriptHash,
|
|
649
|
-
warnings: [],
|
|
650
|
-
};
|
|
252
|
+
const txDetails = this._extractTxDetails(unsignedTx);
|
|
253
|
+
const { hashHex: scriptHash } = this._computeScriptHash(req.plutusScriptExecution.validatorScript);
|
|
254
|
+
logger.info(`Built unsigned Plutus spending transaction. Fee: ${txDetails.feeLovelace}`);
|
|
255
|
+
return this._buildResult(req, ctx, txDetails, { scriptHash });
|
|
651
256
|
}
|
|
652
257
|
catch (error) {
|
|
653
|
-
logger.error(`
|
|
654
|
-
(0, tx_build_helper_1.mapBuilderError)(error
|
|
258
|
+
logger.error(`buildUnsignedPlutusSpendTransaction error: ${error?.message || error}`);
|
|
259
|
+
(0, tx_build_helper_1.mapBuilderError)(error);
|
|
655
260
|
}
|
|
656
261
|
}
|
|
657
262
|
/**
|
|
@@ -660,17 +265,13 @@ class CSLTxBuilder {
|
|
|
660
265
|
_buildPlutusSpendTx(req, ctx, exUnits) {
|
|
661
266
|
const { plutusScriptExecution } = req;
|
|
662
267
|
const scriptUtxoRef = plutusScriptExecution.scriptUtxo;
|
|
663
|
-
// Prepare addresses
|
|
664
268
|
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
665
269
|
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
666
|
-
// Create Transaction Builder
|
|
667
270
|
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
668
|
-
|
|
669
|
-
const scriptBytes = Buffer.from(plutusScriptExecution.validatorScript, 'hex');
|
|
670
|
-
const plutusScript = CSL.PlutusScript.new_v3(scriptBytes);
|
|
271
|
+
const { plutusScript } = this._computeScriptHash(plutusScriptExecution.validatorScript);
|
|
671
272
|
const scriptSource = CSL.PlutusScriptSource.new(plutusScript);
|
|
672
|
-
// Build redeemer
|
|
673
|
-
const redeemerPlutusData =
|
|
273
|
+
// Build redeemer
|
|
274
|
+
const redeemerPlutusData = this._toPlutusData(plutusScriptExecution.redeemer);
|
|
674
275
|
const redeemer = CSL.Redeemer.new(CSL.RedeemerTag.new_spend(), CSL.BigNum.from_str('0'), // index will be corrected by CSL
|
|
675
276
|
redeemerPlutusData, CSL.ExUnits.new(CSL.BigNum.from_str(exUnits.mem), CSL.BigNum.from_str(exUnits.cpu)));
|
|
676
277
|
// Add the script input using TxInputsBuilder
|
|
@@ -680,100 +281,40 @@ class CSLTxBuilder {
|
|
|
680
281
|
// Find the script UTxO to get its value
|
|
681
282
|
const scriptOdatanoUtxo = ctx.utxos.find(u => u.txHash === scriptUtxoRef.txHash && u.outputIndex === scriptUtxoRef.outputIndex);
|
|
682
283
|
if (!scriptOdatanoUtxo) {
|
|
683
|
-
throw new Error(`
|
|
284
|
+
throw new Error(`Script UTxO ${scriptUtxoRef.txHash}#${scriptUtxoRef.outputIndex} not found in provided UTxOs`);
|
|
684
285
|
}
|
|
685
|
-
// Build full multi-asset value for the script UTxO (lovelace + native assets)
|
|
686
|
-
const scriptUtxoValue = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(scriptOdatanoUtxo).toString()));
|
|
687
286
|
const scriptNonAdaAssets = scriptOdatanoUtxo.amount.filter(a => a.unit.toLowerCase() !== 'lovelace' && BigInt(a.quantity) > 0n);
|
|
688
|
-
|
|
689
|
-
const scriptMultiAsset = CSL.MultiAsset.new();
|
|
690
|
-
for (const asset of scriptNonAdaAssets) {
|
|
691
|
-
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
692
|
-
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
693
|
-
let assets = scriptMultiAsset.get(policyHash);
|
|
694
|
-
if (!assets)
|
|
695
|
-
assets = CSL.Assets.new();
|
|
696
|
-
assets.insert(CSL.AssetName.new(Buffer.from(assetName, 'hex')), CSL.BigNum.from_str(asset.quantity));
|
|
697
|
-
scriptMultiAsset.insert(policyHash, assets);
|
|
698
|
-
}
|
|
699
|
-
scriptUtxoValue.set_multiasset(scriptMultiAsset);
|
|
700
|
-
}
|
|
287
|
+
const scriptUtxoValue = this._buildCslValue((0, tx_build_helper_1.getLovelace)(scriptOdatanoUtxo), scriptOdatanoUtxo.amount);
|
|
701
288
|
// Build PlutusWitness: datum handling depends on whether UTxO has inline datum
|
|
702
289
|
let plutusWitness;
|
|
703
290
|
if (plutusScriptExecution.datum) {
|
|
704
|
-
|
|
705
|
-
const datumPlutusData = CSL.PlutusData.from_json(JSON.stringify(plutusScriptExecution.datum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
706
|
-
const datumSource = CSL.DatumSource.new(datumPlutusData);
|
|
291
|
+
const datumSource = CSL.DatumSource.new(this._toPlutusData(plutusScriptExecution.datum));
|
|
707
292
|
plutusWitness = CSL.PlutusWitness.new_with_ref(scriptSource, datumSource, redeemer);
|
|
708
293
|
}
|
|
709
294
|
else {
|
|
710
|
-
// Inline datum on UTxO — no datum witness needed
|
|
711
295
|
plutusWitness = CSL.PlutusWitness.new_with_ref_without_datum(scriptSource, redeemer);
|
|
712
296
|
}
|
|
713
|
-
// Add Plutus script input
|
|
714
297
|
scriptInputsBuilder.add_plutus_script_input(plutusWitness, scriptInput, scriptUtxoValue);
|
|
715
298
|
txb.set_inputs(scriptInputsBuilder);
|
|
716
|
-
|
|
717
|
-
if (req.requiredSigners?.length) {
|
|
718
|
-
for (const signerHex of req.requiredSigners) {
|
|
719
|
-
txb.add_required_signer(CSL.Ed25519KeyHash.from_bytes(Buffer.from(signerHex, 'hex')));
|
|
720
|
-
}
|
|
721
|
-
}
|
|
299
|
+
this._addRequiredSigners(txb, req.requiredSigners);
|
|
722
300
|
// Separate sender UTxOs (excluding script UTxO)
|
|
723
301
|
const senderUtxos = ctx.utxos.filter(u => !(u.txHash === scriptUtxoRef.txHash && u.outputIndex === scriptUtxoRef.outputIndex));
|
|
724
|
-
//
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
const collateralBuilder = CSL.TxInputsBuilder.new();
|
|
728
|
-
const colTxHash = CSL.TransactionHash.from_bytes(Buffer.from(collateralOdatanoUtxo.txHash, 'hex'));
|
|
729
|
-
const colInput = CSL.TransactionInput.new(colTxHash, collateralOdatanoUtxo.outputIndex);
|
|
730
|
-
const colAddress = CSL.Address.from_bech32(collateralOdatanoUtxo.address);
|
|
731
|
-
const colValue = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(collateralOdatanoUtxo).toString()));
|
|
732
|
-
collateralBuilder.add_regular_input(colAddress, colInput, colValue);
|
|
733
|
-
txb.set_collateral(collateralBuilder);
|
|
734
|
-
}
|
|
735
|
-
// Create output for recipient BEFORE coin selection so CSL knows the full
|
|
736
|
-
// output requirements (lovelace + assets + fee) when selecting funding UTxOs.
|
|
737
|
-
// Include multi-assets from the script UTxO in the continuing output.
|
|
302
|
+
// Collateral + funding UTxO separation (from sender UTxOs only)
|
|
303
|
+
const { fundingUtxos } = this._setupCollateral(txb, senderUtxos);
|
|
304
|
+
// Create output for recipient with multi-assets from the script UTxO
|
|
738
305
|
const outputValue = CSL.Value.new(CSL.BigNum.from_str(String(req.lovelaceAmount || 2_000_000)));
|
|
739
306
|
if (scriptNonAdaAssets.length > 0) {
|
|
740
|
-
|
|
741
|
-
for (const asset of scriptNonAdaAssets) {
|
|
742
|
-
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
743
|
-
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
744
|
-
let assets = outputMultiAsset.get(policyHash);
|
|
745
|
-
if (!assets)
|
|
746
|
-
assets = CSL.Assets.new();
|
|
747
|
-
assets.insert(CSL.AssetName.new(Buffer.from(assetName, 'hex')), CSL.BigNum.from_str(asset.quantity));
|
|
748
|
-
outputMultiAsset.insert(policyHash, assets);
|
|
749
|
-
}
|
|
750
|
-
outputValue.set_multiasset(outputMultiAsset);
|
|
307
|
+
outputValue.set_multiasset(this._buildCslMultiAsset(scriptNonAdaAssets));
|
|
751
308
|
}
|
|
752
309
|
const recipientOutput = CSL.TransactionOutput.new(recipientAddress, outputValue);
|
|
753
|
-
|
|
754
|
-
const datumData = CSL.PlutusData.from_json(JSON.stringify(req.inlineDatum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
755
|
-
recipientOutput.set_plutus_data(datumData);
|
|
756
|
-
}
|
|
310
|
+
this._attachInlineDatum(recipientOutput, req.inlineDatum);
|
|
757
311
|
txb.add_output(recipientOutput);
|
|
758
|
-
//
|
|
759
|
-
// This preserves the dedicated collateral UTxO for future Plutus transactions.
|
|
760
|
-
// If no funding UTxOs remain after excluding collateral, fall back to including it —
|
|
761
|
-
// the same UTxO can appear in both inputs and collateral_inputs (consumed either way).
|
|
762
|
-
let fundingUtxos = collateralOdatanoUtxo
|
|
763
|
-
? senderUtxos.filter(u => !(u.txHash === collateralOdatanoUtxo.txHash && u.outputIndex === collateralOdatanoUtxo.outputIndex))
|
|
764
|
-
: senderUtxos;
|
|
765
|
-
if (fundingUtxos.length === 0 && senderUtxos.length > 0) {
|
|
766
|
-
logger.debug('[CSLTxBuilder] No funding UTxOs after collateral exclusion, including collateral in funding pool');
|
|
767
|
-
fundingUtxos = senderUtxos;
|
|
768
|
-
}
|
|
312
|
+
// Coin selection from funding UTxOs
|
|
769
313
|
const cslFundingUtxos = this._mapMultiAssetUtxosToCslUtxos(fundingUtxos);
|
|
770
314
|
txb.add_inputs_from(cslFundingUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
771
|
-
// Calculate script data hash
|
|
772
315
|
const costModels = this._createCostModels('v3');
|
|
773
316
|
txb.calc_script_data_hash(costModels);
|
|
774
|
-
// Add change
|
|
775
317
|
txb.add_change_if_needed(changeAddress);
|
|
776
|
-
// Build transaction and patch scriptDataHash (CSL's is incorrect for Conway PlutusV3)
|
|
777
318
|
const tx = txb.build_tx();
|
|
778
319
|
return this._patchScriptDataHash(tx);
|
|
779
320
|
}
|
|
@@ -788,15 +329,11 @@ class CSLTxBuilder {
|
|
|
788
329
|
const redeemers = witnesses.redeemers();
|
|
789
330
|
if (!redeemers || redeemers.len() === 0)
|
|
790
331
|
return tx;
|
|
791
|
-
// 1. Get redeemers CBOR from the built transaction
|
|
792
332
|
const redeemersBytes = Buffer.from(redeemers.to_bytes());
|
|
793
|
-
// 2. Get datums CBOR if any (hash-based datums in witness set)
|
|
794
333
|
const plutusData = witnesses.plutus_data();
|
|
795
334
|
const datumsBytes = plutusData && plutusData.len() > 0
|
|
796
335
|
? Buffer.from(plutusData.to_bytes())
|
|
797
336
|
: Buffer.alloc(0);
|
|
798
|
-
// 3. Compute correct language views using costModelsToLanguageViewCbor
|
|
799
|
-
// Cost models are already correctly ordered by normalizeCostModels (uses toCostModelArrV3)
|
|
800
337
|
const costModelsJson = JSON.parse(this.protocolParameters.costModels || '{}');
|
|
801
338
|
const costModelsObj = {};
|
|
802
339
|
const v3 = costModelsJson['plutus:v3'] || costModelsJson['PlutusV3'];
|
|
@@ -804,12 +341,10 @@ class CSLTxBuilder {
|
|
|
804
341
|
costModelsObj.PlutusScriptV3 = Array.from((0, cardano_costmodels_ts_1.toCostModelArrV3)(v3)).map(Number);
|
|
805
342
|
}
|
|
806
343
|
const languageViews = Buffer.from((0, cardano_costmodels_ts_1.costModelsToLanguageViewCbor)(costModelsObj, { mustHaveV3: !!costModelsObj.PlutusScriptV3 }).toBuffer());
|
|
807
|
-
//
|
|
344
|
+
// blake2b256(redeemers || datums || languageViews)
|
|
808
345
|
const hashInput = Buffer.concat([redeemersBytes, datumsBytes, languageViews]);
|
|
809
346
|
const hashOutput = Buffer.alloc(32);
|
|
810
347
|
(0, blake2b_1.default)(32).update(hashInput).digest(hashOutput);
|
|
811
|
-
// 5. Patch at raw byte level (CSL caches serialized bytes internally,
|
|
812
|
-
// so set_script_data_hash + Transaction.new doesn't change the output)
|
|
813
348
|
const wrongHash = tx.body().script_data_hash();
|
|
814
349
|
if (!wrongHash)
|
|
815
350
|
return tx;
|
|
@@ -825,47 +360,170 @@ class CSLTxBuilder {
|
|
|
825
360
|
}
|
|
826
361
|
hashOutput.copy(txBytes, idx);
|
|
827
362
|
logger.info(`_patchScriptDataHash: patched ${wrongHashBytes.toString('hex').slice(0, 16)}... → ${hashOutput.toString('hex').slice(0, 16)}...`);
|
|
828
|
-
// 6. Reconstruct transaction from patched bytes
|
|
829
363
|
return CSL.Transaction.from_bytes(txBytes);
|
|
830
364
|
}
|
|
831
365
|
//---------------------------------------------------------------------------
|
|
832
|
-
//
|
|
366
|
+
// Shared Helper Methods
|
|
367
|
+
//---------------------------------------------------------------------------
|
|
368
|
+
/** Convert JSON PlutusData to CSL PlutusData using DetailedSchema */
|
|
369
|
+
_toPlutusData(json) {
|
|
370
|
+
return CSL.PlutusData.from_json(JSON.stringify(json), CSL.PlutusDatumSchema.DetailedSchema);
|
|
371
|
+
}
|
|
372
|
+
/** Build CSL MultiAsset from an array of { unit, quantity } (skips lovelace entries) */
|
|
373
|
+
_buildCslMultiAsset(assets) {
|
|
374
|
+
const multiAsset = CSL.MultiAsset.new();
|
|
375
|
+
for (const asset of assets) {
|
|
376
|
+
if (asset.unit.toLowerCase() === 'lovelace')
|
|
377
|
+
continue;
|
|
378
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
379
|
+
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
380
|
+
let existing = multiAsset.get(policyHash);
|
|
381
|
+
if (!existing)
|
|
382
|
+
existing = CSL.Assets.new();
|
|
383
|
+
existing.insert(CSL.AssetName.new(Buffer.from(assetName, 'hex')), CSL.BigNum.from_str(asset.quantity));
|
|
384
|
+
multiAsset.insert(policyHash, existing);
|
|
385
|
+
}
|
|
386
|
+
return multiAsset;
|
|
387
|
+
}
|
|
388
|
+
/** Build CSL Value from lovelace + optional assets array */
|
|
389
|
+
_buildCslValue(lovelace, assets) {
|
|
390
|
+
const value = CSL.Value.new(CSL.BigNum.from_str(String(lovelace)));
|
|
391
|
+
const nonAda = assets?.filter(a => a.unit.toLowerCase() !== 'lovelace' && BigInt(a.quantity) > 0n);
|
|
392
|
+
if (nonAda && nonAda.length > 0) {
|
|
393
|
+
value.set_multiasset(this._buildCslMultiAsset(nonAda));
|
|
394
|
+
}
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
/** Extract CBOR, hash, fee, and outputs from a built CSL Transaction */
|
|
398
|
+
_extractTxDetails(unsignedTx) {
|
|
399
|
+
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
400
|
+
const body = unsignedTx.body();
|
|
401
|
+
const txBodyHash = (0, blake2b_1.default)(32).update(body.to_bytes()).digest('hex');
|
|
402
|
+
const feeLovelace = body.fee().to_str();
|
|
403
|
+
const outputs = [];
|
|
404
|
+
const txOuts = body.outputs();
|
|
405
|
+
for (let i = 0; i < txOuts.len(); i++) {
|
|
406
|
+
const o = txOuts.get(i);
|
|
407
|
+
outputs.push({ address: o.address().to_bech32(), lovelace: o.amount().coin().to_str() });
|
|
408
|
+
}
|
|
409
|
+
return { unsignedTxCbor, txBodyHash, feeLovelace, outputs, sizeBytes: unsignedTxCbor.length / 2 };
|
|
410
|
+
}
|
|
411
|
+
/** Build the standard TxBuildResult object */
|
|
412
|
+
_buildResult(req, ctx, txDetails, extra) {
|
|
413
|
+
return {
|
|
414
|
+
unsignedTxCbor: txDetails.unsignedTxCbor,
|
|
415
|
+
txBodyHash: txDetails.txBodyHash,
|
|
416
|
+
senderAddress: req.senderAddress,
|
|
417
|
+
network: this.cardanoClient.network,
|
|
418
|
+
sizeBytes: txDetails.sizeBytes,
|
|
419
|
+
builderEngine: this.name,
|
|
420
|
+
feeLovelace: txDetails.feeLovelace,
|
|
421
|
+
inputs: ctx.utxos.map(u => ({
|
|
422
|
+
txHash: u.txHash,
|
|
423
|
+
index: u.outputIndex,
|
|
424
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
425
|
+
})),
|
|
426
|
+
outputs: txDetails.outputs,
|
|
427
|
+
...extra,
|
|
428
|
+
warnings: [],
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/** Add required signers (Ed25519 key hashes) to the transaction builder */
|
|
432
|
+
_addRequiredSigners(txb, signers) {
|
|
433
|
+
if (signers?.length) {
|
|
434
|
+
for (const signerHex of signers) {
|
|
435
|
+
txb.add_required_signer(CSL.Ed25519KeyHash.from_bytes(Buffer.from(signerHex, 'hex')));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/** Attach inline PlutusData datum to a transaction output */
|
|
440
|
+
_attachInlineDatum(output, datum) {
|
|
441
|
+
if (datum) {
|
|
442
|
+
output.set_plutus_data(this._toPlutusData(datum));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/** Parse PlutusV3 script hex and compute its hash */
|
|
446
|
+
_computeScriptHash(scriptHex) {
|
|
447
|
+
const plutusScript = CSL.PlutusScript.new_v3(Buffer.from(scriptHex, 'hex'));
|
|
448
|
+
const cslHash = plutusScript.hash();
|
|
449
|
+
return { plutusScript, hashHex: Buffer.from(cslHash.to_bytes()).toString('hex'), cslHash };
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Evaluate execution units via a two-pass build: first with high units for evaluation,
|
|
453
|
+
* then apply the evaluated budget with a safety buffer. Falls back to defaults on failure.
|
|
454
|
+
*/
|
|
455
|
+
async _evaluateExUnits(buildFn, evaluator) {
|
|
456
|
+
const defaultUnits = {
|
|
457
|
+
mem: String(const_1.DEFAULT_EXECUTION_UNITS.mem),
|
|
458
|
+
cpu: String(const_1.DEFAULT_EXECUTION_UNITS.cpu)
|
|
459
|
+
};
|
|
460
|
+
if (!evaluator) {
|
|
461
|
+
logger.debug('No evaluator available, using default execution units');
|
|
462
|
+
return defaultUnits;
|
|
463
|
+
}
|
|
464
|
+
logger.debug('Building evaluation pass with high execution units');
|
|
465
|
+
const evalTx = buildFn({
|
|
466
|
+
mem: String(const_1.HIGH_EXECUTION_UNITS.mem),
|
|
467
|
+
cpu: String(const_1.HIGH_EXECUTION_UNITS.cpu)
|
|
468
|
+
});
|
|
469
|
+
const evalTxCbor = Buffer.from(evalTx.to_bytes()).toString('hex');
|
|
470
|
+
try {
|
|
471
|
+
const evalResults = await evaluator(evalTxCbor);
|
|
472
|
+
logger.debug(`Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
473
|
+
if (evalResults && evalResults.length > 0) {
|
|
474
|
+
const budget = evalResults[0].budget;
|
|
475
|
+
const result = {
|
|
476
|
+
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER).toString(),
|
|
477
|
+
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER).toString()
|
|
478
|
+
};
|
|
479
|
+
logger.info(`Using evaluated execution units: mem=${result.mem}, cpu=${result.cpu}`);
|
|
480
|
+
return result;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
catch (evalError) {
|
|
484
|
+
logger.warn(`Evaluation failed, using default units: ${evalError.message}`);
|
|
485
|
+
}
|
|
486
|
+
return defaultUnits;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Find an ADA-only UTxO for collateral, set it on the builder,
|
|
490
|
+
* and return the remaining funding UTxOs with collateral excluded.
|
|
491
|
+
*/
|
|
492
|
+
_setupCollateral(txb, utxos) {
|
|
493
|
+
const collateralUtxo = utxos.find(u => u.amount.every(a => a.unit.toLowerCase() === 'lovelace'));
|
|
494
|
+
if (collateralUtxo) {
|
|
495
|
+
const collateralBuilder = CSL.TxInputsBuilder.new();
|
|
496
|
+
const txHash = CSL.TransactionHash.from_bytes(Buffer.from(collateralUtxo.txHash, 'hex'));
|
|
497
|
+
const input = CSL.TransactionInput.new(txHash, collateralUtxo.outputIndex);
|
|
498
|
+
const address = CSL.Address.from_bech32(collateralUtxo.address);
|
|
499
|
+
const value = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(collateralUtxo).toString()));
|
|
500
|
+
collateralBuilder.add_regular_input(address, input, value);
|
|
501
|
+
txb.set_collateral(collateralBuilder);
|
|
502
|
+
}
|
|
503
|
+
let fundingUtxos = collateralUtxo
|
|
504
|
+
? utxos.filter(u => !(u.txHash === collateralUtxo.txHash && u.outputIndex === collateralUtxo.outputIndex))
|
|
505
|
+
: utxos;
|
|
506
|
+
if (fundingUtxos.length === 0 && utxos.length > 0) {
|
|
507
|
+
logger.debug('No funding UTxOs after collateral exclusion, including collateral in funding pool');
|
|
508
|
+
fundingUtxos = utxos;
|
|
509
|
+
}
|
|
510
|
+
return { collateralUtxo, fundingUtxos };
|
|
511
|
+
}
|
|
512
|
+
//---------------------------------------------------------------------------
|
|
513
|
+
// CSL Mapping & Configuration Helpers
|
|
833
514
|
//---------------------------------------------------------------------------
|
|
834
515
|
/**
|
|
835
516
|
* Map ODATANO UTxOs to CSL TransactionUnspentOutputs (with multi-asset support)
|
|
836
|
-
* @param utxos ODATANO UTxO array
|
|
837
|
-
* @returns CSL TransactionUnspentOutputs
|
|
838
517
|
*/
|
|
839
518
|
_mapMultiAssetUtxosToCslUtxos(utxos) {
|
|
840
519
|
const outs = CSL.TransactionUnspentOutputs.new();
|
|
841
520
|
logger.debug(`Mapping ${utxos.length} UTxOs to CSL format`);
|
|
842
521
|
for (const u of utxos) {
|
|
843
522
|
logger.debug(`UTxO ${u.txHash}:${u.outputIndex} has ${u.amount.length} amounts: ${JSON.stringify(u.amount)}`);
|
|
844
|
-
const
|
|
845
|
-
const txHash = CSL.TransactionHash.from_bytes(txHashBytes);
|
|
523
|
+
const txHash = CSL.TransactionHash.from_bytes(Buffer.from(u.txHash, "hex"));
|
|
846
524
|
const input = CSL.TransactionInput.new(txHash, u.outputIndex);
|
|
847
525
|
const addr = CSL.Address.from_bech32(u.address);
|
|
848
|
-
|
|
849
|
-
const lovelace = CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(u).toString());
|
|
850
|
-
const value = CSL.Value.new(lovelace);
|
|
851
|
-
// Add native assets if any
|
|
852
|
-
const nonAdaAssets = u.amount.filter(a => a.unit.toLowerCase() !== 'lovelace' && BigInt(a.quantity) > 0n);
|
|
853
|
-
if (nonAdaAssets.length > 0) {
|
|
854
|
-
const multiAsset = CSL.MultiAsset.new();
|
|
855
|
-
for (const asset of nonAdaAssets) {
|
|
856
|
-
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
857
|
-
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
858
|
-
let assets = multiAsset.get(policyHash);
|
|
859
|
-
if (!assets) {
|
|
860
|
-
assets = CSL.Assets.new();
|
|
861
|
-
}
|
|
862
|
-
const assetNameBytes = Buffer.from(assetName, 'hex');
|
|
863
|
-
const assetNameObj = CSL.AssetName.new(assetNameBytes);
|
|
864
|
-
assets.insert(assetNameObj, CSL.BigNum.from_str(asset.quantity));
|
|
865
|
-
multiAsset.insert(policyHash, assets);
|
|
866
|
-
}
|
|
867
|
-
value.set_multiasset(multiAsset);
|
|
868
|
-
}
|
|
526
|
+
const value = this._buildCslValue((0, tx_build_helper_1.getLovelace)(u), u.amount);
|
|
869
527
|
const output = CSL.TransactionOutput.new(addr, value);
|
|
870
528
|
outs.add(CSL.TransactionUnspentOutput.new(input, output));
|
|
871
529
|
}
|
|
@@ -873,12 +531,8 @@ class CSLTxBuilder {
|
|
|
873
531
|
}
|
|
874
532
|
/**
|
|
875
533
|
* Create a CSL TransactionBuilderConfig from protocol parameters
|
|
876
|
-
* This config is created once and reused for all transactions
|
|
877
|
-
* @param protocolParams LedgerProtocolParameter
|
|
878
|
-
* @returns CSL.TransactionBuilderConfig
|
|
879
534
|
*/
|
|
880
535
|
_createTxBuilderConfig(protocolParams) {
|
|
881
|
-
// required values for CSL config
|
|
882
536
|
const minFeeA = protocolParams.minFeeA;
|
|
883
537
|
const minFeeB = protocolParams.minFeeB;
|
|
884
538
|
const poolDeposit = protocolParams.poolDeposit;
|
|
@@ -888,21 +542,15 @@ class CSLTxBuilder {
|
|
|
888
542
|
const coinsPerUtxoByte = protocolParams.coinsPerUtxoSize;
|
|
889
543
|
const feeAlgo = CSL.LinearFee.new(CSL.BigNum.from_str(String(minFeeA)), CSL.BigNum.from_str(String(minFeeB)));
|
|
890
544
|
// Plutus execution unit prices for script fee calculation
|
|
891
|
-
// Protocol params may contain decimal prices (e.g., 0.0577) - convert to integer numerators
|
|
892
545
|
const priceMemValue = protocolParams.priceMem;
|
|
893
546
|
const priceStepValue = protocolParams.priceStep;
|
|
894
|
-
// Convert decimal prices to integer numerators (or use defaults if already integers or missing)
|
|
895
547
|
const priceMemNumerator = (typeof priceMemValue === 'number' && priceMemValue < 1)
|
|
896
|
-
? Math.round(priceMemValue * 10000)
|
|
548
|
+
? Math.round(priceMemValue * 10000)
|
|
897
549
|
: (priceMemValue || 577);
|
|
898
550
|
const priceStepNumerator = (typeof priceStepValue === 'number' && priceStepValue < 1)
|
|
899
|
-
? Math.round(priceStepValue * 10000000)
|
|
551
|
+
? Math.round(priceStepValue * 10000000)
|
|
900
552
|
: (priceStepValue || 721);
|
|
901
|
-
const exUnitPrices = CSL.ExUnitPrices.new(CSL.UnitInterval.new(CSL.BigNum.from_str(String(priceMemNumerator)),
|
|
902
|
-
CSL.BigNum.from_str('10000') // denominator (0.0577 per memory unit)
|
|
903
|
-
), CSL.UnitInterval.new(CSL.BigNum.from_str(String(priceStepNumerator)), // numerator
|
|
904
|
-
CSL.BigNum.from_str('10000000') // denominator (0.0000721 per CPU step)
|
|
905
|
-
));
|
|
553
|
+
const exUnitPrices = CSL.ExUnitPrices.new(CSL.UnitInterval.new(CSL.BigNum.from_str(String(priceMemNumerator)), CSL.BigNum.from_str('10000')), CSL.UnitInterval.new(CSL.BigNum.from_str(String(priceStepNumerator)), CSL.BigNum.from_str('10000000')));
|
|
906
554
|
const cfg = CSL.TransactionBuilderConfigBuilder.new()
|
|
907
555
|
.fee_algo(feeAlgo)
|
|
908
556
|
.pool_deposit(CSL.BigNum.from_str(String(poolDeposit)))
|
|
@@ -910,26 +558,21 @@ class CSLTxBuilder {
|
|
|
910
558
|
.max_tx_size(Number(maxTxSize))
|
|
911
559
|
.max_value_size(Number(maxValueSize))
|
|
912
560
|
.coins_per_utxo_byte(CSL.BigNum.from_str(String(coinsPerUtxoByte)))
|
|
913
|
-
.ex_unit_prices(exUnitPrices)
|
|
561
|
+
.ex_unit_prices(exUnitPrices)
|
|
914
562
|
.build();
|
|
915
563
|
logger.info(`TransactionBuilderConfig created with Plutus execution unit prices.`);
|
|
916
564
|
return cfg;
|
|
917
565
|
}
|
|
918
566
|
/**
|
|
919
567
|
* Map ODATANO metadata JSON to CSL GeneralTransactionMetadata
|
|
920
|
-
* @param metadataJson JSON metadata object
|
|
921
|
-
* @returns CSL GeneralTransactionMetadata
|
|
922
568
|
*/
|
|
923
569
|
_mapOdatanoMetadataToCSLMetadata(metadataJson) {
|
|
924
|
-
// Metadata must be an object with labels as keys
|
|
925
570
|
if (typeof metadataJson !== 'object' || Array.isArray(metadataJson) || metadataJson === null) {
|
|
926
|
-
throw new Error(`
|
|
571
|
+
throw new Error(`Invalid metadata format. Expected object, got ${typeof metadataJson}`);
|
|
927
572
|
}
|
|
928
573
|
const metadata = CSL.GeneralTransactionMetadata.new();
|
|
929
574
|
for (const [label, value] of Object.entries(metadataJson)) {
|
|
930
|
-
// Convert label to BigNum
|
|
931
575
|
const numericLabel = parseInt(label, 10);
|
|
932
|
-
// Convert JSON Value to CSL TransactionMetadatum
|
|
933
576
|
const txMetadatum = this._jsonToCSLMetadatum(value);
|
|
934
577
|
logger.debug(`Created TransactionMetadatum for label ${numericLabel}`);
|
|
935
578
|
metadata.insert(CSL.BigNum.from_str(String(numericLabel)), txMetadatum);
|
|
@@ -939,15 +582,9 @@ class CSLTxBuilder {
|
|
|
939
582
|
}
|
|
940
583
|
/**
|
|
941
584
|
* Create Costmdls from protocol parameters for specific Plutus version
|
|
942
|
-
* Required for correct script integrity hash calculation
|
|
943
|
-
* @param version - Optional: specific Plutus version ('v1', 'v2', 'v3'). If not provided, adds all versions.
|
|
944
|
-
* @returns CSL.Costmdls
|
|
945
585
|
*/
|
|
946
586
|
_createCostModels(version) {
|
|
947
587
|
const costModels = CSL.Costmdls.new();
|
|
948
|
-
// Blockfrost returns cost models as objects ({name: value}), CSL expects arrays.
|
|
949
|
-
// Convert object format to sorted-key array defensively (normalizeCostModels in mappers.ts
|
|
950
|
-
// already does this at the backend level, but handle it here too for robustness).
|
|
951
588
|
const toArray = (costs) => {
|
|
952
589
|
if (Array.isArray(costs))
|
|
953
590
|
return costs;
|
|
@@ -956,60 +593,42 @@ class CSLTxBuilder {
|
|
|
956
593
|
}
|
|
957
594
|
return null;
|
|
958
595
|
};
|
|
596
|
+
const addCostModel = (language, costs, label) => {
|
|
597
|
+
const costModel = CSL.CostModel.new();
|
|
598
|
+
for (let i = 0; i < costs.length; i++) {
|
|
599
|
+
costModel.set(i, CSL.Int.new_i32(costs[i]));
|
|
600
|
+
}
|
|
601
|
+
costModels.insert(language, costModel);
|
|
602
|
+
logger.debug(`Added ${label} cost model with ${costs.length} parameters`);
|
|
603
|
+
};
|
|
959
604
|
try {
|
|
960
|
-
// Parse cost models JSON from protocol parameters
|
|
961
|
-
// Format: { "plutus:v1": [array of 166 numbers], "plutus:v2": [array of 175 numbers], ... }
|
|
962
605
|
const costModelsJson = JSON.parse(this.protocolParameters.costModels || '{}');
|
|
963
|
-
// PlutusV1 cost model
|
|
964
606
|
if (!version || version === 'v1') {
|
|
965
|
-
const
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
for (let i = 0; i < plutusV1Costs.length; i++) {
|
|
969
|
-
plutusV1CostModel.set(i, CSL.Int.new_i32(plutusV1Costs[i]));
|
|
970
|
-
}
|
|
971
|
-
costModels.insert(CSL.Language.new_plutus_v1(), plutusV1CostModel);
|
|
972
|
-
logger.debug(`[CSLTxBuilder] Added PlutusV1 cost model with ${plutusV1Costs.length} parameters`);
|
|
973
|
-
}
|
|
607
|
+
const v1Costs = toArray(costModelsJson['plutus:v1'] || costModelsJson['PlutusV1']);
|
|
608
|
+
if (v1Costs)
|
|
609
|
+
addCostModel(CSL.Language.new_plutus_v1(), v1Costs, 'PlutusV1');
|
|
974
610
|
}
|
|
975
|
-
// PlutusV2 cost model
|
|
976
611
|
if (!version || version === 'v2') {
|
|
977
|
-
const
|
|
978
|
-
if (
|
|
979
|
-
|
|
980
|
-
for (let i = 0; i < plutusV2Costs.length; i++) {
|
|
981
|
-
plutusV2CostModel.set(i, CSL.Int.new_i32(plutusV2Costs[i]));
|
|
982
|
-
}
|
|
983
|
-
costModels.insert(CSL.Language.new_plutus_v2(), plutusV2CostModel);
|
|
984
|
-
logger.debug(`[CSLTxBuilder] Added PlutusV2 cost model with ${plutusV2Costs.length} parameters`);
|
|
985
|
-
}
|
|
612
|
+
const v2Costs = toArray(costModelsJson['plutus:v2'] || costModelsJson['PlutusV2']);
|
|
613
|
+
if (v2Costs)
|
|
614
|
+
addCostModel(CSL.Language.new_plutus_v2(), v2Costs, 'PlutusV2');
|
|
986
615
|
}
|
|
987
|
-
// PlutusV3 cost model (check both "plutus:v3" and "PlutusV3" formats)
|
|
988
616
|
if (!version || version === 'v3') {
|
|
989
|
-
const
|
|
990
|
-
if (
|
|
991
|
-
// Pad to 297 parameters (Conway Chang 2) using defaults from cardano-costmodels-ts
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const plutusV3Costs = Array.from((0, cardano_costmodels_ts_1.toCostModelArrV3)(plutusV3CostsRaw)).map(Number);
|
|
995
|
-
const plutusV3CostModel = CSL.CostModel.new();
|
|
996
|
-
for (let i = 0; i < plutusV3Costs.length; i++) {
|
|
997
|
-
plutusV3CostModel.set(i, CSL.Int.new_i32(plutusV3Costs[i]));
|
|
998
|
-
}
|
|
999
|
-
costModels.insert(CSL.Language.new_plutus_v3(), plutusV3CostModel);
|
|
1000
|
-
logger.debug(`[CSLTxBuilder] Added PlutusV3 cost model with ${plutusV3Costs.length} parameters`);
|
|
617
|
+
const v3CostsRaw = toArray(costModelsJson['plutus:v3'] || costModelsJson['PlutusV3']);
|
|
618
|
+
if (v3CostsRaw) {
|
|
619
|
+
// Pad to 297 parameters (Conway Chang 2) using defaults from cardano-costmodels-ts
|
|
620
|
+
const v3Costs = Array.from((0, cardano_costmodels_ts_1.toCostModelArrV3)(v3CostsRaw)).map(Number);
|
|
621
|
+
addCostModel(CSL.Language.new_plutus_v3(), v3Costs, 'PlutusV3');
|
|
1001
622
|
}
|
|
1002
623
|
}
|
|
1003
624
|
}
|
|
1004
625
|
catch (error) {
|
|
1005
|
-
logger.warn(`
|
|
626
|
+
logger.warn(`Failed to parse cost models: ${error}. Using empty cost models.`);
|
|
1006
627
|
}
|
|
1007
628
|
return costModels;
|
|
1008
629
|
}
|
|
1009
630
|
/**
|
|
1010
631
|
* Convert JSON value to CSL TransactionMetadatum
|
|
1011
|
-
* @param value JSON value
|
|
1012
|
-
* @returns CSL TransactionMetadatum
|
|
1013
632
|
*/
|
|
1014
633
|
_jsonToCSLMetadatum(value) {
|
|
1015
634
|
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
@@ -1035,7 +654,7 @@ class CSLTxBuilder {
|
|
|
1035
654
|
}
|
|
1036
655
|
return CSL.TransactionMetadatum.new_map(map);
|
|
1037
656
|
}
|
|
1038
|
-
throw new Error(`
|
|
657
|
+
throw new Error(`Unsupported metadata value type: ${typeof value}`);
|
|
1039
658
|
}
|
|
1040
659
|
}
|
|
1041
660
|
exports.CSLTxBuilder = CSLTxBuilder;
|