@odatano/core 0.3.1
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/LICENSE +201 -0
- package/README.md +212 -0
- package/cds-plugin.js +5 -0
- package/config/preview/cardano-node/alonzo-genesis.json +196 -0
- package/config/preview/cardano-node/byron-genesis.json +117 -0
- package/config/preview/cardano-node/config.json +118 -0
- package/config/preview/cardano-node/conway-genesis.json +297 -0
- package/config/preview/cardano-node/shelley-genesis.json +68 -0
- package/config/preview/cardano-node/topology.json +19 -0
- package/db/schema.cds +1318 -0
- package/package.json +125 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +96 -0
- package/src/index.js.map +1 -0
- package/src/plugin.d.ts.map +1 -0
- package/src/plugin.js +92 -0
- package/src/plugin.js.map +1 -0
- package/srv/blockchain/backends/blockfrost-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/blockfrost-backend.js +398 -0
- package/srv/blockchain/backends/blockfrost-backend.js.map +1 -0
- package/srv/blockchain/backends/cardano-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/cardano-backend.js +12 -0
- package/srv/blockchain/backends/cardano-backend.js.map +1 -0
- package/srv/blockchain/backends/koios-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/koios-backend.js +537 -0
- package/srv/blockchain/backends/koios-backend.js.map +1 -0
- package/srv/blockchain/backends/ogmios-backend.d.ts.map +1 -0
- package/srv/blockchain/backends/ogmios-backend.js +516 -0
- package/srv/blockchain/backends/ogmios-backend.js.map +1 -0
- package/srv/blockchain/cardano-client.d.ts.map +1 -0
- package/srv/blockchain/cardano-client.js +377 -0
- package/srv/blockchain/cardano-client.js.map +1 -0
- package/srv/blockchain/cardano-indexer.d.ts.map +1 -0
- package/srv/blockchain/cardano-indexer.js +542 -0
- package/srv/blockchain/cardano-indexer.js.map +1 -0
- package/srv/blockchain/cardano-tx-builder.d.ts.map +1 -0
- package/srv/blockchain/cardano-tx-builder.js +232 -0
- package/srv/blockchain/cardano-tx-builder.js.map +1 -0
- package/srv/blockchain/circuit-breaker.d.ts.map +1 -0
- package/srv/blockchain/circuit-breaker.js +110 -0
- package/srv/blockchain/circuit-breaker.js.map +1 -0
- package/srv/blockchain/signing/external-signer.d.ts.map +1 -0
- package/srv/blockchain/signing/external-signer.js +302 -0
- package/srv/blockchain/signing/external-signer.js.map +1 -0
- package/srv/blockchain/signing/signature-verifier.d.ts.map +1 -0
- package/srv/blockchain/signing/signature-verifier.js +249 -0
- package/srv/blockchain/signing/signature-verifier.js.map +1 -0
- package/srv/blockchain/transaction-building/buildooor-tx.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/buildooor-tx.js +636 -0
- package/srv/blockchain/transaction-building/buildooor-tx.js.map +1 -0
- package/srv/blockchain/transaction-building/cardano-tx.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/cardano-tx.js +3 -0
- package/srv/blockchain/transaction-building/cardano-tx.js.map +1 -0
- package/srv/blockchain/transaction-building/csl-tx.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/csl-tx.js +766 -0
- package/srv/blockchain/transaction-building/csl-tx.js.map +1 -0
- package/srv/blockchain/transaction-building/tx-builder-registry.d.ts.map +1 -0
- package/srv/blockchain/transaction-building/tx-builder-registry.js +67 -0
- package/srv/blockchain/transaction-building/tx-builder-registry.js.map +1 -0
- package/srv/cardano-service.cds +179 -0
- package/srv/cardano-service.d.ts.map +1 -0
- package/srv/cardano-service.js +227 -0
- package/srv/cardano-service.js.map +1 -0
- package/srv/cardano-tx-service.cds +298 -0
- package/srv/cardano-tx-service.d.ts.map +1 -0
- package/srv/cardano-tx-service.js +646 -0
- package/srv/cardano-tx-service.js.map +1 -0
- package/srv/cardano-ui.cds +2949 -0
- package/srv/server.d.ts.map +1 -0
- package/srv/server.js +212 -0
- package/srv/server.js.map +1 -0
- package/srv/utils/backend-request-handler.d.ts.map +1 -0
- package/srv/utils/backend-request-handler.js +47 -0
- package/srv/utils/backend-request-handler.js.map +1 -0
- package/srv/utils/const.d.ts.map +1 -0
- package/srv/utils/const.js +86 -0
- package/srv/utils/const.js.map +1 -0
- package/srv/utils/error-codes.d.ts.map +1 -0
- package/srv/utils/error-codes.js +49 -0
- package/srv/utils/error-codes.js.map +1 -0
- package/srv/utils/errors.d.ts.map +1 -0
- package/srv/utils/errors.js +389 -0
- package/srv/utils/errors.js.map +1 -0
- package/srv/utils/mappers.d.ts.map +1 -0
- package/srv/utils/mappers.js +723 -0
- package/srv/utils/mappers.js.map +1 -0
- package/srv/utils/signing-helper.d.ts.map +1 -0
- package/srv/utils/signing-helper.js +128 -0
- package/srv/utils/signing-helper.js.map +1 -0
- package/srv/utils/tx-build-helper.d.ts.map +1 -0
- package/srv/utils/tx-build-helper.js +135 -0
- package/srv/utils/tx-build-helper.js.map +1 -0
- package/srv/utils/types.d.ts.map +1 -0
- package/srv/utils/types.js +36 -0
- package/srv/utils/types.js.map +1 -0
- package/srv/utils/validators.d.ts.map +1 -0
- package/srv/utils/validators.js +382 -0
- package/srv/utils/validators.js.map +1 -0
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.CSLTxBuilder = void 0;
|
|
40
|
+
const cds_1 = __importDefault(require("@sap/cds"));
|
|
41
|
+
const CSL = __importStar(require("@emurgo/cardano-serialization-lib-nodejs"));
|
|
42
|
+
const blake2b_1 = __importDefault(require("blake2b"));
|
|
43
|
+
const tx_build_helper_1 = require("../../utils/tx-build-helper");
|
|
44
|
+
const const_1 = require("../../utils/const");
|
|
45
|
+
const logger = cds_1.default.log('CSLTxBuilder');
|
|
46
|
+
/**
|
|
47
|
+
* CSLTxBuilder - Implementation of CardanoTxBuilder using cardano-serialization-lib (CSL)
|
|
48
|
+
*/
|
|
49
|
+
class CSLTxBuilder {
|
|
50
|
+
name = "CslTxBuilder";
|
|
51
|
+
txBuilderConfig;
|
|
52
|
+
protocolParameters; // Store for cost models
|
|
53
|
+
cardanoClient;
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the builder
|
|
56
|
+
* @param cardanoClient - The CardanoClient instance
|
|
57
|
+
* @param protocolParams - Optional protocol parameters (if not provided, fetched from backend)
|
|
58
|
+
*/
|
|
59
|
+
async init(cardanoClient, protocolParams) {
|
|
60
|
+
this.protocolParameters = protocolParams ?? await cardanoClient.getProtocolParameters();
|
|
61
|
+
this.txBuilderConfig = this._createTxBuilderConfig(this.protocolParameters);
|
|
62
|
+
this.cardanoClient = cardanoClient;
|
|
63
|
+
logger.info(`Initialized with protocol parameters.`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build unsigned ADA transfer transaction
|
|
67
|
+
* @param req transaction build request
|
|
68
|
+
* @param ctx transaction build context
|
|
69
|
+
* @returns {Promise<TxBuildResult>} transaction build result
|
|
70
|
+
*/
|
|
71
|
+
async buildUnsignedAdaTransfer(req, ctx) {
|
|
72
|
+
try {
|
|
73
|
+
// prepare addresses
|
|
74
|
+
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
75
|
+
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
76
|
+
// map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support)
|
|
77
|
+
// This allows spending UTxOs that contain native assets - they will be returned in the change output
|
|
78
|
+
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
79
|
+
// create Transaction Builder from stored config
|
|
80
|
+
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
81
|
+
// add recipient & output (lovelace), with optional inline datum
|
|
82
|
+
const amount = CSL.BigNum.from_str(String(req.lovelaceAmount));
|
|
83
|
+
const outValue = CSL.Value.new(amount);
|
|
84
|
+
const out = CSL.TransactionOutput.new(recipientAddress, outValue);
|
|
85
|
+
if (req.outputDatum) {
|
|
86
|
+
const plutusData = CSL.PlutusData.from_json(JSON.stringify(req.outputDatum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
87
|
+
out.set_plutus_data(plutusData);
|
|
88
|
+
}
|
|
89
|
+
txb.add_output(out);
|
|
90
|
+
// add inputs via coin selection + add change
|
|
91
|
+
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
92
|
+
txb.add_change_if_needed(changeAddress);
|
|
93
|
+
// build unsigned tx
|
|
94
|
+
const unsignedTx = txb.build_tx();
|
|
95
|
+
// Export the complete transaction (with empty witness set) for cardano-cli
|
|
96
|
+
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
97
|
+
// hash + fee + outputs
|
|
98
|
+
const body = unsignedTx.body();
|
|
99
|
+
const bodyBytes = body.to_bytes();
|
|
100
|
+
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
101
|
+
const txBodyHash = hash;
|
|
102
|
+
const feeLovelace = body.fee().to_str();
|
|
103
|
+
const outputs = [];
|
|
104
|
+
const txOuts = body.outputs();
|
|
105
|
+
for (let i = 0; i < txOuts.len(); i++) {
|
|
106
|
+
const o = txOuts.get(i);
|
|
107
|
+
outputs.push({
|
|
108
|
+
address: o.address().to_bech32(),
|
|
109
|
+
lovelace: o.amount().coin().to_str(),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
logger.info(`Built unsigned transaction successfully.`);
|
|
113
|
+
return {
|
|
114
|
+
unsignedTxCbor,
|
|
115
|
+
txBodyHash,
|
|
116
|
+
senderAddress: req.senderAddress,
|
|
117
|
+
network: this.cardanoClient.network,
|
|
118
|
+
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
119
|
+
builderEngine: this.name,
|
|
120
|
+
feeLovelace: feeLovelace,
|
|
121
|
+
inputs: ctx.utxos.map(u => ({
|
|
122
|
+
txHash: u.txHash,
|
|
123
|
+
index: u.outputIndex,
|
|
124
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
125
|
+
})),
|
|
126
|
+
outputs,
|
|
127
|
+
warnings: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async buildUnsignedTransactionWithMetadata(req, ctx) {
|
|
135
|
+
try {
|
|
136
|
+
// prepare addresses
|
|
137
|
+
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
138
|
+
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
139
|
+
// map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support)
|
|
140
|
+
// This allows spending UTxOs that contain native assets - they will be returned in the change output
|
|
141
|
+
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
142
|
+
// create Transaction Builder from stored config
|
|
143
|
+
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
144
|
+
// add recipient & output (lovelace)
|
|
145
|
+
const amount = CSL.BigNum.from_str(String(req.lovelaceAmount));
|
|
146
|
+
const outValue = CSL.Value.new(amount);
|
|
147
|
+
const out = CSL.TransactionOutput.new(recipientAddress, outValue);
|
|
148
|
+
txb.add_output(out);
|
|
149
|
+
// add metadata if provided
|
|
150
|
+
if (req.metadataJson) {
|
|
151
|
+
const metadata = this._mapOdatanoMetadataToCSLMetadata(req.metadataJson);
|
|
152
|
+
txb.set_metadata(metadata);
|
|
153
|
+
}
|
|
154
|
+
// add inputs via coin selection + add change
|
|
155
|
+
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
156
|
+
txb.add_change_if_needed(changeAddress);
|
|
157
|
+
// build unsigned tx
|
|
158
|
+
const unsignedTx = txb.build_tx();
|
|
159
|
+
// Export the complete transaction (with empty witness set) for cardano-cli
|
|
160
|
+
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
161
|
+
// hash + fee + outputs
|
|
162
|
+
const body = unsignedTx.body();
|
|
163
|
+
const bodyBytes = body.to_bytes();
|
|
164
|
+
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
165
|
+
const txBodyHash = hash;
|
|
166
|
+
const feeLovelace = body.fee().to_str();
|
|
167
|
+
const outputs = [];
|
|
168
|
+
const txOuts = body.outputs();
|
|
169
|
+
for (let i = 0; i < txOuts.len(); i++) {
|
|
170
|
+
const o = txOuts.get(i);
|
|
171
|
+
outputs.push({
|
|
172
|
+
address: o.address().to_bech32(),
|
|
173
|
+
lovelace: o.amount().coin().to_str(),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
logger.info(`Built unsigned transaction with metadata successfully.`);
|
|
177
|
+
return {
|
|
178
|
+
unsignedTxCbor,
|
|
179
|
+
txBodyHash,
|
|
180
|
+
senderAddress: req.senderAddress,
|
|
181
|
+
network: this.cardanoClient.network,
|
|
182
|
+
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
183
|
+
builderEngine: this.name,
|
|
184
|
+
feeLovelace,
|
|
185
|
+
inputs: ctx.utxos.map(u => ({
|
|
186
|
+
txHash: u.txHash,
|
|
187
|
+
index: u.outputIndex,
|
|
188
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
189
|
+
})),
|
|
190
|
+
outputs,
|
|
191
|
+
warnings: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async buildUnsignedMultiAssetTransaction(req, ctx) {
|
|
199
|
+
if (!req.assets || req.assets.length === 0) {
|
|
200
|
+
throw new Error('[CSLTxBuilder] buildUnsignedMultiAssetTransaction requires assets to be specified');
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
// prepare addresses
|
|
204
|
+
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
205
|
+
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
206
|
+
// map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support)
|
|
207
|
+
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
208
|
+
// create Transaction Builder from stored config
|
|
209
|
+
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
210
|
+
// Build output value with ADA + multi-assets
|
|
211
|
+
const lovelace = CSL.BigNum.from_str(String(req.lovelaceAmount));
|
|
212
|
+
const outputValue = CSL.Value.new(lovelace);
|
|
213
|
+
// Create MultiAsset structure for native tokens
|
|
214
|
+
const multiAsset = CSL.MultiAsset.new();
|
|
215
|
+
for (const asset of req.assets) {
|
|
216
|
+
// Skip 'lovelace' unit (already handled above)
|
|
217
|
+
if (asset.unit === 'lovelace')
|
|
218
|
+
continue;
|
|
219
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
220
|
+
// Get or create Assets for this policy
|
|
221
|
+
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
222
|
+
let assets = multiAsset.get(policyHash);
|
|
223
|
+
if (!assets) {
|
|
224
|
+
assets = CSL.Assets.new();
|
|
225
|
+
}
|
|
226
|
+
// Add asset with quantity
|
|
227
|
+
const assetNameBytes = Buffer.from(assetName, 'hex');
|
|
228
|
+
const cslAssetName = CSL.AssetName.new(assetNameBytes);
|
|
229
|
+
const quantity = CSL.BigNum.from_str(asset.quantity);
|
|
230
|
+
assets.insert(cslAssetName, quantity);
|
|
231
|
+
multiAsset.insert(policyHash, assets);
|
|
232
|
+
}
|
|
233
|
+
// Set multi-asset on output value
|
|
234
|
+
outputValue.set_multiasset(multiAsset);
|
|
235
|
+
// Create output with all assets
|
|
236
|
+
const recipientOutput = CSL.TransactionOutput.new(recipientAddress, outputValue);
|
|
237
|
+
txb.add_output(recipientOutput);
|
|
238
|
+
// add inputs via coin selection + add change
|
|
239
|
+
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
240
|
+
txb.add_change_if_needed(changeAddress);
|
|
241
|
+
// build unsigned tx
|
|
242
|
+
const unsignedTx = txb.build_tx();
|
|
243
|
+
// Export the complete transaction (with empty witness set) for cardano-cli
|
|
244
|
+
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString("hex");
|
|
245
|
+
// hash + fee + outputs
|
|
246
|
+
const body = unsignedTx.body();
|
|
247
|
+
const bodyBytes = body.to_bytes();
|
|
248
|
+
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
249
|
+
const txBodyHash = hash;
|
|
250
|
+
const feeLovelace = body.fee().to_str();
|
|
251
|
+
const outputs = [];
|
|
252
|
+
const txOuts = body.outputs();
|
|
253
|
+
for (let i = 0; i < txOuts.len(); i++) {
|
|
254
|
+
const o = txOuts.get(i);
|
|
255
|
+
outputs.push({
|
|
256
|
+
address: o.address().to_bech32(),
|
|
257
|
+
lovelace: o.amount().coin().to_str(),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
logger.info(`Built unsigned multi-asset transaction successfully.`);
|
|
261
|
+
return {
|
|
262
|
+
unsignedTxCbor,
|
|
263
|
+
txBodyHash,
|
|
264
|
+
senderAddress: req.senderAddress,
|
|
265
|
+
network: this.cardanoClient.network,
|
|
266
|
+
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
267
|
+
builderEngine: this.name,
|
|
268
|
+
feeLovelace,
|
|
269
|
+
inputs: ctx.utxos.map(u => ({
|
|
270
|
+
txHash: u.txHash,
|
|
271
|
+
index: u.outputIndex,
|
|
272
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
273
|
+
})),
|
|
274
|
+
outputs,
|
|
275
|
+
warnings: [],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
// Extract asset unit from error message if possible
|
|
280
|
+
const assetMatch = err?.message?.match(/not enough\s+([a-f0-9.]+)/i);
|
|
281
|
+
const assetUnit = assetMatch?.[1] || 'assets';
|
|
282
|
+
(0, tx_build_helper_1.mapBuilderError)(err, assetUnit);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async buildUnsignedMintTransaction(req, ctx) {
|
|
286
|
+
try {
|
|
287
|
+
// Determine execution units based on evaluator availability
|
|
288
|
+
let finalExUnits = {
|
|
289
|
+
mem: String(const_1.DEFAULT_EXECUTION_UNITS.mem),
|
|
290
|
+
cpu: String(const_1.DEFAULT_EXECUTION_UNITS.cpu)
|
|
291
|
+
};
|
|
292
|
+
if (ctx.evaluateTransaction) {
|
|
293
|
+
// Build first pass with high execution units for evaluation
|
|
294
|
+
logger.debug(`[CSLTxBuilder] Building evaluation pass with high execution units`);
|
|
295
|
+
const evalTx = this._buildMintTx(req, ctx, {
|
|
296
|
+
mem: String(const_1.HIGH_EXECUTION_UNITS.mem),
|
|
297
|
+
cpu: String(const_1.HIGH_EXECUTION_UNITS.cpu)
|
|
298
|
+
});
|
|
299
|
+
const evalTxCbor = Buffer.from(evalTx.to_bytes()).toString('hex');
|
|
300
|
+
try {
|
|
301
|
+
// Evaluate to get exact execution units
|
|
302
|
+
const evalResults = await ctx.evaluateTransaction(evalTxCbor);
|
|
303
|
+
logger.debug(`[CSLTxBuilder] Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
304
|
+
if (evalResults && evalResults.length > 0) {
|
|
305
|
+
// Use the evaluated budget (take first result for single script)
|
|
306
|
+
const budget = evalResults[0].budget;
|
|
307
|
+
// Add safety margin to evaluated units
|
|
308
|
+
finalExUnits = {
|
|
309
|
+
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER).toString(),
|
|
310
|
+
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER).toString()
|
|
311
|
+
};
|
|
312
|
+
logger.info(`[CSLTxBuilder] Using evaluated execution units: mem=${finalExUnits.mem}, cpu=${finalExUnits.cpu}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (evalError) {
|
|
316
|
+
logger.warn(`[CSLTxBuilder] Evaluation failed, using default units: ${evalError.message}`);
|
|
317
|
+
// Fall back to defaults on evaluation failure
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
logger.debug(`[CSLTxBuilder] No evaluator available, using default execution units`);
|
|
322
|
+
}
|
|
323
|
+
// Build final transaction with determined execution units
|
|
324
|
+
const unsignedTx = this._buildMintTx(req, ctx, finalExUnits);
|
|
325
|
+
logger.debug(`[CSLTxBuilder] Transaction built successfully`);
|
|
326
|
+
// Export as CBOR hex
|
|
327
|
+
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString('hex');
|
|
328
|
+
// Extract transaction details from the transaction
|
|
329
|
+
const body = unsignedTx.body();
|
|
330
|
+
const bodyBytes = body.to_bytes();
|
|
331
|
+
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
332
|
+
const txBodyHash = hash;
|
|
333
|
+
const feeLovelace = body.fee().to_str();
|
|
334
|
+
// Extract outputs
|
|
335
|
+
const outputs = [];
|
|
336
|
+
const txOuts = body.outputs();
|
|
337
|
+
for (let i = 0; i < txOuts.len(); i++) {
|
|
338
|
+
const o = txOuts.get(i);
|
|
339
|
+
outputs.push({
|
|
340
|
+
address: o.address().to_bech32(),
|
|
341
|
+
lovelace: o.amount().coin().to_str(),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
logger.info(`[CSLTxBuilder] Built unsigned minting transaction successfully. Fee: ${feeLovelace}`);
|
|
345
|
+
return {
|
|
346
|
+
unsignedTxCbor,
|
|
347
|
+
txBodyHash,
|
|
348
|
+
senderAddress: req.senderAddress,
|
|
349
|
+
network: this.cardanoClient.network,
|
|
350
|
+
sizeBytes: unsignedTxCbor.length / 2, // hex to bytes
|
|
351
|
+
builderEngine: this.name,
|
|
352
|
+
feeLovelace,
|
|
353
|
+
inputs: ctx.utxos.map(u => ({
|
|
354
|
+
txHash: u.txHash,
|
|
355
|
+
index: u.outputIndex,
|
|
356
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
357
|
+
})),
|
|
358
|
+
outputs,
|
|
359
|
+
warnings: [],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
logger.error(`[CSLTxBuilder] buildUnsignedMintTransaction error: ${error?.message || error}`);
|
|
364
|
+
(0, tx_build_helper_1.mapBuilderError)(error, 'lovelace');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Helper to build mint transaction with specified execution units
|
|
369
|
+
*/
|
|
370
|
+
_buildMintTx(req, ctx, exUnits) {
|
|
371
|
+
// Prepare addresses
|
|
372
|
+
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
373
|
+
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
374
|
+
// Map ODATANO UTxOs -> CSL TransactionUnspentOutputs (with multi-asset support for burn transactions)
|
|
375
|
+
const cslUtxos = this._mapMultiAssetUtxosToCslUtxos(ctx.utxos);
|
|
376
|
+
// Create Transaction Builder
|
|
377
|
+
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
378
|
+
// Parse the Plutus script from CBOR hex (CBOR-wrapped flat UPLC bytecode)
|
|
379
|
+
const scriptBytes = Buffer.from(req.mintingPolicyScript, 'hex');
|
|
380
|
+
const plutusScript = CSL.PlutusScript.new_v3(scriptBytes);
|
|
381
|
+
const scriptHash = plutusScript.hash();
|
|
382
|
+
// Create PlutusScriptSource - wrapper for the script
|
|
383
|
+
const scriptSource = CSL.PlutusScriptSource.new(plutusScript);
|
|
384
|
+
// Create MintBuilder
|
|
385
|
+
const mintBuilder = CSL.MintBuilder.new();
|
|
386
|
+
// Track total minted value for output
|
|
387
|
+
let totalMintedValue = CSL.Value.new(CSL.BigNum.from_str('0'));
|
|
388
|
+
// Process each mint action
|
|
389
|
+
for (const mintAction of req.mintActions) {
|
|
390
|
+
const { assetName } = (0, tx_build_helper_1.parseAssetUnit)(mintAction.assetUnit);
|
|
391
|
+
// Create asset name
|
|
392
|
+
const assetNameBytes = Buffer.from(assetName, 'hex');
|
|
393
|
+
const cslAssetName = CSL.AssetName.new(assetNameBytes);
|
|
394
|
+
// Create mint quantity (can be negative for burning)
|
|
395
|
+
const mintQuantity = CSL.Int.new_i32(Number(mintAction.quantity));
|
|
396
|
+
// Create redeemer with specified execution units
|
|
397
|
+
const redeemerData = CSL.PlutusData.new_integer(CSL.BigInt.from_str(String(mintAction.redeemer ?? 0)));
|
|
398
|
+
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)));
|
|
399
|
+
// Create MintWitness directly from PlutusScriptSource + Redeemer
|
|
400
|
+
const mintWitness = CSL.MintWitness.new_plutus_script(scriptSource, redeemer);
|
|
401
|
+
// Add to mint builder
|
|
402
|
+
mintBuilder.add_asset(mintWitness, cslAssetName, mintQuantity);
|
|
403
|
+
// Add to total minted value for output construction (only if quantity > 0)
|
|
404
|
+
if (Number(mintAction.quantity) > 0) {
|
|
405
|
+
const assetValue = CSL.Value.new(CSL.BigNum.from_str('0'));
|
|
406
|
+
const multiAsset = CSL.MultiAsset.new();
|
|
407
|
+
const assets = CSL.Assets.new();
|
|
408
|
+
assets.insert(cslAssetName, CSL.BigNum.from_str(String(mintAction.quantity)));
|
|
409
|
+
multiAsset.insert(scriptHash, assets);
|
|
410
|
+
assetValue.set_multiasset(multiAsset);
|
|
411
|
+
totalMintedValue = totalMintedValue.checked_add(assetValue);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Set the mint builder on transaction
|
|
415
|
+
txb.set_mint_builder(mintBuilder);
|
|
416
|
+
// Create output with minted assets + minimum lovelace
|
|
417
|
+
const minLovelace = CSL.BigNum.from_str(String(req.lovelaceAmount));
|
|
418
|
+
const outputValue = CSL.Value.new(minLovelace);
|
|
419
|
+
const finalOutputValue = outputValue.checked_add(totalMintedValue);
|
|
420
|
+
const recipientOutput = CSL.TransactionOutput.new(recipientAddress, finalOutputValue);
|
|
421
|
+
txb.add_output(recipientOutput);
|
|
422
|
+
// Add collateral for Plutus script execution
|
|
423
|
+
if (ctx.utxos.length > 0) {
|
|
424
|
+
const collateralUtxo = ctx.utxos[0];
|
|
425
|
+
const collateralBuilder = CSL.TxInputsBuilder.new();
|
|
426
|
+
const txHash = CSL.TransactionHash.from_bytes(Buffer.from(collateralUtxo.txHash, 'hex'));
|
|
427
|
+
const input = CSL.TransactionInput.new(txHash, collateralUtxo.outputIndex);
|
|
428
|
+
const address = CSL.Address.from_bech32(collateralUtxo.address);
|
|
429
|
+
const value = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(collateralUtxo).toString()));
|
|
430
|
+
collateralBuilder.add_regular_input(address, input, value);
|
|
431
|
+
txb.set_collateral(collateralBuilder);
|
|
432
|
+
}
|
|
433
|
+
// Add inputs via coin selection
|
|
434
|
+
txb.add_inputs_from(cslUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
435
|
+
// Calculate script data hash BEFORE add_change_if_needed
|
|
436
|
+
// Always call calc_script_data_hash for Plutus transactions - CSL requires it
|
|
437
|
+
const costModels = this._createCostModels('v3');
|
|
438
|
+
txb.calc_script_data_hash(costModels);
|
|
439
|
+
// Now add change
|
|
440
|
+
txb.add_change_if_needed(changeAddress);
|
|
441
|
+
// Build and return transaction
|
|
442
|
+
return txb.build_tx();
|
|
443
|
+
}
|
|
444
|
+
async buildUnsignedPlutusSpendTransaction(req, ctx) {
|
|
445
|
+
try {
|
|
446
|
+
// Determine execution units - default or from evaluator
|
|
447
|
+
let finalExUnits = {
|
|
448
|
+
mem: const_1.DEFAULT_EXECUTION_UNITS.mem.toString(),
|
|
449
|
+
cpu: const_1.DEFAULT_EXECUTION_UNITS.cpu.toString()
|
|
450
|
+
};
|
|
451
|
+
if (ctx.evaluateTransaction) {
|
|
452
|
+
// Build evaluation pass with high execution units
|
|
453
|
+
logger.debug(`[CSLTxBuilder] Building evaluation pass for Plutus spending`);
|
|
454
|
+
const highExUnits = {
|
|
455
|
+
mem: const_1.HIGH_EXECUTION_UNITS.mem.toString(),
|
|
456
|
+
cpu: const_1.HIGH_EXECUTION_UNITS.cpu.toString()
|
|
457
|
+
};
|
|
458
|
+
const evalTx = this._buildPlutusSpendTx(req, ctx, highExUnits);
|
|
459
|
+
const evalTxCbor = Buffer.from(evalTx.to_bytes()).toString('hex');
|
|
460
|
+
try {
|
|
461
|
+
const evalResults = await ctx.evaluateTransaction(evalTxCbor);
|
|
462
|
+
logger.debug(`[CSLTxBuilder] Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
463
|
+
if (evalResults && evalResults.length > 0) {
|
|
464
|
+
const budget = evalResults[0].budget;
|
|
465
|
+
finalExUnits = {
|
|
466
|
+
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER).toString(),
|
|
467
|
+
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER).toString()
|
|
468
|
+
};
|
|
469
|
+
logger.info(`[CSLTxBuilder] Using evaluated execution units: mem=${finalExUnits.mem}, cpu=${finalExUnits.cpu}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch (evalError) {
|
|
473
|
+
logger.warn(`[CSLTxBuilder] Evaluation failed, using default units: ${evalError.message}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
logger.debug(`[CSLTxBuilder] No evaluator available, using default execution units`);
|
|
478
|
+
}
|
|
479
|
+
// Build final transaction with determined execution units
|
|
480
|
+
const unsignedTx = this._buildPlutusSpendTx(req, ctx, finalExUnits);
|
|
481
|
+
// Export as CBOR hex
|
|
482
|
+
const unsignedTxCbor = Buffer.from(unsignedTx.to_bytes()).toString('hex');
|
|
483
|
+
// Extract transaction details
|
|
484
|
+
const body = unsignedTx.body();
|
|
485
|
+
const bodyBytes = body.to_bytes();
|
|
486
|
+
const hash = (0, blake2b_1.default)(32).update(bodyBytes).digest('hex');
|
|
487
|
+
const txBodyHash = hash;
|
|
488
|
+
const feeLovelace = body.fee().to_str();
|
|
489
|
+
// Extract outputs
|
|
490
|
+
const outputs = [];
|
|
491
|
+
const txOuts = body.outputs();
|
|
492
|
+
for (let i = 0; i < txOuts.len(); i++) {
|
|
493
|
+
const o = txOuts.get(i);
|
|
494
|
+
outputs.push({
|
|
495
|
+
address: o.address().to_bech32(),
|
|
496
|
+
lovelace: o.amount().coin().to_str(),
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
logger.info(`[CSLTxBuilder] Built unsigned Plutus spending transaction. Fee: ${feeLovelace}`);
|
|
500
|
+
return {
|
|
501
|
+
unsignedTxCbor,
|
|
502
|
+
txBodyHash,
|
|
503
|
+
senderAddress: req.senderAddress,
|
|
504
|
+
network: this.cardanoClient.network,
|
|
505
|
+
sizeBytes: unsignedTxCbor.length / 2,
|
|
506
|
+
builderEngine: this.name,
|
|
507
|
+
feeLovelace,
|
|
508
|
+
inputs: ctx.utxos.map(u => ({
|
|
509
|
+
txHash: u.txHash,
|
|
510
|
+
index: u.outputIndex,
|
|
511
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString(),
|
|
512
|
+
})),
|
|
513
|
+
outputs,
|
|
514
|
+
warnings: [],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
logger.error(`[CSLTxBuilder] buildUnsignedPlutusSpendTransaction error: ${error?.message || error}`);
|
|
519
|
+
(0, tx_build_helper_1.mapBuilderError)(error, 'lovelace');
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Helper to build Plutus spending transaction with specified execution units
|
|
524
|
+
*/
|
|
525
|
+
_buildPlutusSpendTx(req, ctx, exUnits) {
|
|
526
|
+
const { plutusScriptExecution } = req;
|
|
527
|
+
const scriptUtxoRef = plutusScriptExecution.scriptUtxo;
|
|
528
|
+
// Prepare addresses
|
|
529
|
+
const recipientAddress = CSL.Address.from_bech32(req.recipientAddress);
|
|
530
|
+
const changeAddress = CSL.Address.from_bech32(req.changeAddress ?? req.senderAddress);
|
|
531
|
+
// Create Transaction Builder
|
|
532
|
+
const txb = CSL.TransactionBuilder.new(this.txBuilderConfig);
|
|
533
|
+
// Parse the Plutus validator script from CBOR hex (CBOR-wrapped flat UPLC bytecode)
|
|
534
|
+
const scriptBytes = Buffer.from(plutusScriptExecution.validatorScript, 'hex');
|
|
535
|
+
const plutusScript = CSL.PlutusScript.new_v3(scriptBytes);
|
|
536
|
+
const scriptSource = CSL.PlutusScriptSource.new(plutusScript);
|
|
537
|
+
// Build redeemer (using DetailedSchema: { "constructor": 0, "fields": [] } format)
|
|
538
|
+
const redeemerPlutusData = CSL.PlutusData.from_json(JSON.stringify(plutusScriptExecution.redeemer), CSL.PlutusDatumSchema.DetailedSchema);
|
|
539
|
+
const redeemer = CSL.Redeemer.new(CSL.RedeemerTag.new_spend(), CSL.BigNum.from_str('0'), // index will be corrected by CSL
|
|
540
|
+
redeemerPlutusData, CSL.ExUnits.new(CSL.BigNum.from_str(exUnits.mem), CSL.BigNum.from_str(exUnits.cpu)));
|
|
541
|
+
// Build datum source
|
|
542
|
+
let datumSource;
|
|
543
|
+
if (plutusScriptExecution.datum) {
|
|
544
|
+
// Datum provided explicitly (hash-based datum)
|
|
545
|
+
const datumPlutusData = CSL.PlutusData.from_json(JSON.stringify(plutusScriptExecution.datum), CSL.PlutusDatumSchema.DetailedSchema);
|
|
546
|
+
datumSource = CSL.DatumSource.new(datumPlutusData);
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// No datum provided - for inline datums, pass a unit datum placeholder.
|
|
550
|
+
// The node will validate against the actual inline datum on-chain.
|
|
551
|
+
const unitDatum = CSL.PlutusData.new_constr_plutus_data(CSL.ConstrPlutusData.new(CSL.BigNum.from_str('0'), CSL.PlutusList.new()));
|
|
552
|
+
datumSource = CSL.DatumSource.new(unitDatum);
|
|
553
|
+
}
|
|
554
|
+
// Add the script input using TxInputsBuilder
|
|
555
|
+
const scriptInputsBuilder = CSL.TxInputsBuilder.new();
|
|
556
|
+
const scriptTxHash = CSL.TransactionHash.from_bytes(Buffer.from(scriptUtxoRef.txHash, 'hex'));
|
|
557
|
+
const scriptInput = CSL.TransactionInput.new(scriptTxHash, scriptUtxoRef.outputIndex);
|
|
558
|
+
// Find the script UTxO to get its value
|
|
559
|
+
const scriptOdatanoUtxo = ctx.utxos.find(u => u.txHash === scriptUtxoRef.txHash && u.outputIndex === scriptUtxoRef.outputIndex);
|
|
560
|
+
if (!scriptOdatanoUtxo) {
|
|
561
|
+
throw new Error(`[CSLTxBuilder] Script UTxO ${scriptUtxoRef.txHash}#${scriptUtxoRef.outputIndex} not found in provided UTxOs`);
|
|
562
|
+
}
|
|
563
|
+
const scriptUtxoValue = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(scriptOdatanoUtxo).toString()));
|
|
564
|
+
// Add Plutus script input
|
|
565
|
+
scriptInputsBuilder.add_plutus_script_input(CSL.PlutusWitness.new_with_ref(scriptSource, datumSource, redeemer), scriptInput, scriptUtxoValue);
|
|
566
|
+
txb.set_inputs(scriptInputsBuilder);
|
|
567
|
+
// Add sender UTxOs for fee payment via coin selection
|
|
568
|
+
const senderUtxos = ctx.utxos.filter(u => !(u.txHash === scriptUtxoRef.txHash && u.outputIndex === scriptUtxoRef.outputIndex));
|
|
569
|
+
const cslSenderUtxos = this._mapMultiAssetUtxosToCslUtxos(senderUtxos);
|
|
570
|
+
txb.add_inputs_from(cslSenderUtxos, CSL.CoinSelectionStrategyCIP2.LargestFirstMultiAsset);
|
|
571
|
+
// Create output for recipient
|
|
572
|
+
const outputValue = CSL.Value.new(CSL.BigNum.from_str(String(req.lovelaceAmount || 2_000_000)));
|
|
573
|
+
const recipientOutput = CSL.TransactionOutput.new(recipientAddress, outputValue);
|
|
574
|
+
txb.add_output(recipientOutput);
|
|
575
|
+
// Add collateral (from sender UTxOs)
|
|
576
|
+
if (senderUtxos.length > 0) {
|
|
577
|
+
const collateralUtxo = senderUtxos[0];
|
|
578
|
+
const collateralBuilder = CSL.TxInputsBuilder.new();
|
|
579
|
+
const colTxHash = CSL.TransactionHash.from_bytes(Buffer.from(collateralUtxo.txHash, 'hex'));
|
|
580
|
+
const colInput = CSL.TransactionInput.new(colTxHash, collateralUtxo.outputIndex);
|
|
581
|
+
const colAddress = CSL.Address.from_bech32(collateralUtxo.address);
|
|
582
|
+
const colValue = CSL.Value.new(CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(collateralUtxo).toString()));
|
|
583
|
+
collateralBuilder.add_regular_input(colAddress, colInput, colValue);
|
|
584
|
+
txb.set_collateral(collateralBuilder);
|
|
585
|
+
}
|
|
586
|
+
// Calculate script data hash
|
|
587
|
+
const costModels = this._createCostModels('v3');
|
|
588
|
+
txb.calc_script_data_hash(costModels);
|
|
589
|
+
// Add change
|
|
590
|
+
txb.add_change_if_needed(changeAddress);
|
|
591
|
+
// Build and return transaction
|
|
592
|
+
return txb.build_tx();
|
|
593
|
+
}
|
|
594
|
+
//---------------------------------------------------------------------------
|
|
595
|
+
// Private Helper Methods
|
|
596
|
+
//---------------------------------------------------------------------------
|
|
597
|
+
/**
|
|
598
|
+
* Map ODATANO UTxOs to CSL TransactionUnspentOutputs (with multi-asset support)
|
|
599
|
+
* @param utxos ODATANO UTxO array
|
|
600
|
+
* @returns CSL TransactionUnspentOutputs
|
|
601
|
+
*/
|
|
602
|
+
_mapMultiAssetUtxosToCslUtxos(utxos) {
|
|
603
|
+
const outs = CSL.TransactionUnspentOutputs.new();
|
|
604
|
+
logger.debug(`Mapping ${utxos.length} UTxOs to CSL format`);
|
|
605
|
+
for (const u of utxos) {
|
|
606
|
+
logger.debug(`UTxO ${u.txHash}:${u.outputIndex} has ${u.amount.length} amounts: ${JSON.stringify(u.amount)}`);
|
|
607
|
+
const txHashBytes = Buffer.from(u.txHash, "hex");
|
|
608
|
+
const txHash = CSL.TransactionHash.from_bytes(txHashBytes);
|
|
609
|
+
const input = CSL.TransactionInput.new(txHash, u.outputIndex);
|
|
610
|
+
const addr = CSL.Address.from_bech32(u.address);
|
|
611
|
+
// Build value with ADA + all native assets
|
|
612
|
+
const lovelace = CSL.BigNum.from_str((0, tx_build_helper_1.getLovelace)(u).toString());
|
|
613
|
+
const value = CSL.Value.new(lovelace);
|
|
614
|
+
// Add native assets if any
|
|
615
|
+
const nonAdaAssets = u.amount.filter(a => a.unit.toLowerCase() !== 'lovelace' && BigInt(a.quantity) > 0n);
|
|
616
|
+
if (nonAdaAssets.length > 0) {
|
|
617
|
+
const multiAsset = CSL.MultiAsset.new();
|
|
618
|
+
for (const asset of nonAdaAssets) {
|
|
619
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
620
|
+
const policyHash = CSL.ScriptHash.from_bytes(Buffer.from(policyId, 'hex'));
|
|
621
|
+
let assets = multiAsset.get(policyHash);
|
|
622
|
+
if (!assets) {
|
|
623
|
+
assets = CSL.Assets.new();
|
|
624
|
+
}
|
|
625
|
+
const assetNameBytes = Buffer.from(assetName, 'hex');
|
|
626
|
+
const assetNameObj = CSL.AssetName.new(assetNameBytes);
|
|
627
|
+
assets.insert(assetNameObj, CSL.BigNum.from_str(asset.quantity));
|
|
628
|
+
multiAsset.insert(policyHash, assets);
|
|
629
|
+
}
|
|
630
|
+
value.set_multiasset(multiAsset);
|
|
631
|
+
}
|
|
632
|
+
const output = CSL.TransactionOutput.new(addr, value);
|
|
633
|
+
outs.add(CSL.TransactionUnspentOutput.new(input, output));
|
|
634
|
+
}
|
|
635
|
+
return outs;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Create a CSL TransactionBuilderConfig from protocol parameters
|
|
639
|
+
* This config is created once and reused for all transactions
|
|
640
|
+
* @param protocolParams LedgerProtocolParameter
|
|
641
|
+
* @returns CSL.TransactionBuilderConfig
|
|
642
|
+
*/
|
|
643
|
+
_createTxBuilderConfig(protocolParams) {
|
|
644
|
+
// required values for CSL config
|
|
645
|
+
const minFeeA = protocolParams.minFeeA;
|
|
646
|
+
const minFeeB = protocolParams.minFeeB;
|
|
647
|
+
const poolDeposit = protocolParams.poolDeposit;
|
|
648
|
+
const keyDeposit = protocolParams.keyDeposit;
|
|
649
|
+
const maxTxSize = protocolParams.maxTxSize;
|
|
650
|
+
const maxValueSize = protocolParams.maxValSize;
|
|
651
|
+
const coinsPerUtxoByte = protocolParams.coinsPerUtxoSize;
|
|
652
|
+
const feeAlgo = CSL.LinearFee.new(CSL.BigNum.from_str(String(minFeeA)), CSL.BigNum.from_str(String(minFeeB)));
|
|
653
|
+
// Plutus execution unit prices for script fee calculation
|
|
654
|
+
// Protocol params may contain decimal prices (e.g., 0.0577) - convert to integer numerators
|
|
655
|
+
const priceMemValue = protocolParams.priceMem;
|
|
656
|
+
const priceStepValue = protocolParams.priceStep;
|
|
657
|
+
// Convert decimal prices to integer numerators (or use defaults if already integers or missing)
|
|
658
|
+
const priceMemNumerator = (typeof priceMemValue === 'number' && priceMemValue < 1)
|
|
659
|
+
? Math.round(priceMemValue * 10000) // 0.0577 -> 577
|
|
660
|
+
: (priceMemValue || 577);
|
|
661
|
+
const priceStepNumerator = (typeof priceStepValue === 'number' && priceStepValue < 1)
|
|
662
|
+
? Math.round(priceStepValue * 10000000) // 0.0000721 -> 721
|
|
663
|
+
: (priceStepValue || 721);
|
|
664
|
+
const exUnitPrices = CSL.ExUnitPrices.new(CSL.UnitInterval.new(CSL.BigNum.from_str(String(priceMemNumerator)), // numerator
|
|
665
|
+
CSL.BigNum.from_str('10000') // denominator (0.0577 per memory unit)
|
|
666
|
+
), CSL.UnitInterval.new(CSL.BigNum.from_str(String(priceStepNumerator)), // numerator
|
|
667
|
+
CSL.BigNum.from_str('10000000') // denominator (0.0000721 per CPU step)
|
|
668
|
+
));
|
|
669
|
+
const cfg = CSL.TransactionBuilderConfigBuilder.new()
|
|
670
|
+
.fee_algo(feeAlgo)
|
|
671
|
+
.pool_deposit(CSL.BigNum.from_str(String(poolDeposit)))
|
|
672
|
+
.key_deposit(CSL.BigNum.from_str(String(keyDeposit)))
|
|
673
|
+
.max_tx_size(Number(maxTxSize))
|
|
674
|
+
.max_value_size(Number(maxValueSize))
|
|
675
|
+
.coins_per_utxo_byte(CSL.BigNum.from_str(String(coinsPerUtxoByte)))
|
|
676
|
+
.ex_unit_prices(exUnitPrices) // Critical for Plutus fee calculation
|
|
677
|
+
.build();
|
|
678
|
+
logger.info(`TransactionBuilderConfig created with Plutus execution unit prices.`);
|
|
679
|
+
return cfg;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Map ODATANO metadata JSON to CSL GeneralTransactionMetadata
|
|
683
|
+
* @param metadataJson JSON metadata object
|
|
684
|
+
* @returns CSL GeneralTransactionMetadata
|
|
685
|
+
*/
|
|
686
|
+
_mapOdatanoMetadataToCSLMetadata(metadataJson) {
|
|
687
|
+
// Metadata must be an object with labels as keys
|
|
688
|
+
if (typeof metadataJson !== 'object' || Array.isArray(metadataJson) || metadataJson === null) {
|
|
689
|
+
throw new Error(`[CSLTxBuilder] Invalid metadata format. Expected object, got ${typeof metadataJson}`);
|
|
690
|
+
}
|
|
691
|
+
const metadata = CSL.GeneralTransactionMetadata.new();
|
|
692
|
+
for (const [label, value] of Object.entries(metadataJson)) {
|
|
693
|
+
// Convert label to BigNum
|
|
694
|
+
const numericLabel = parseInt(label, 10);
|
|
695
|
+
// Convert JSON Value to CSL TransactionMetadatum
|
|
696
|
+
const txMetadatum = this._jsonToCSLMetadatum(value);
|
|
697
|
+
logger.debug(`Created TransactionMetadatum for label ${numericLabel}`);
|
|
698
|
+
metadata.insert(CSL.BigNum.from_str(String(numericLabel)), txMetadatum);
|
|
699
|
+
}
|
|
700
|
+
logger.debug(`Created metadata with ${metadata.len()} labels`);
|
|
701
|
+
return metadata;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Create Costmdls from protocol parameters for specific Plutus version
|
|
705
|
+
* Required for correct script integrity hash calculation
|
|
706
|
+
* @param version - Optional: specific Plutus version ('v1', 'v2', 'v3'). If not provided, adds all versions.
|
|
707
|
+
* @returns CSL.Costmdls
|
|
708
|
+
*/
|
|
709
|
+
_createCostModels(version) {
|
|
710
|
+
const costModels = CSL.Costmdls.new();
|
|
711
|
+
try {
|
|
712
|
+
// Parse cost models JSON from protocol parameters
|
|
713
|
+
// Format: { "plutus:v1": [array of 166 numbers], "plutus:v2": [array of 175 numbers], ... }
|
|
714
|
+
const costModelsJson = JSON.parse(this.protocolParameters.costModels || '{}');
|
|
715
|
+
// PlutusV3 cost model (check both "plutus:v3" and "PlutusV3" formats)
|
|
716
|
+
if (!version || version === 'v3') {
|
|
717
|
+
const plutusV3Costs = costModelsJson['plutus:v3'] || costModelsJson['PlutusV3'];
|
|
718
|
+
if (plutusV3Costs && Array.isArray(plutusV3Costs)) {
|
|
719
|
+
const plutusV3CostModel = CSL.CostModel.new();
|
|
720
|
+
for (let i = 0; i < plutusV3Costs.length; i++) {
|
|
721
|
+
plutusV3CostModel.set(i, CSL.Int.new_i32(plutusV3Costs[i]));
|
|
722
|
+
}
|
|
723
|
+
costModels.insert(CSL.Language.new_plutus_v3(), plutusV3CostModel);
|
|
724
|
+
logger.debug(`[CSLTxBuilder] Added PlutusV3 cost model with ${plutusV3Costs.length} parameters`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
logger.warn(`[CSLTxBuilder] Failed to parse cost models: ${error}. Using empty cost models.`);
|
|
730
|
+
}
|
|
731
|
+
return costModels;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Convert JSON value to CSL TransactionMetadatum
|
|
735
|
+
* @param value JSON value
|
|
736
|
+
* @returns CSL TransactionMetadatum
|
|
737
|
+
*/
|
|
738
|
+
_jsonToCSLMetadatum(value) {
|
|
739
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
740
|
+
const intValue = CSL.Int.new_i32(Number(value));
|
|
741
|
+
return CSL.TransactionMetadatum.new_int(intValue);
|
|
742
|
+
}
|
|
743
|
+
if (typeof value === 'string') {
|
|
744
|
+
return CSL.TransactionMetadatum.new_text(value);
|
|
745
|
+
}
|
|
746
|
+
if (Array.isArray(value)) {
|
|
747
|
+
const list = CSL.MetadataList.new();
|
|
748
|
+
for (const item of value) {
|
|
749
|
+
list.add(this._jsonToCSLMetadatum(item));
|
|
750
|
+
}
|
|
751
|
+
return CSL.TransactionMetadatum.new_list(list);
|
|
752
|
+
}
|
|
753
|
+
if (typeof value === 'object' && value !== null) {
|
|
754
|
+
const map = CSL.MetadataMap.new();
|
|
755
|
+
for (const [k, v] of Object.entries(value)) {
|
|
756
|
+
const key = CSL.TransactionMetadatum.new_text(k);
|
|
757
|
+
const val = this._jsonToCSLMetadatum(v);
|
|
758
|
+
map.insert(key, val);
|
|
759
|
+
}
|
|
760
|
+
return CSL.TransactionMetadatum.new_map(map);
|
|
761
|
+
}
|
|
762
|
+
throw new Error(`[CSLTxBuilder] Unsupported metadata value type: ${typeof value}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
exports.CSLTxBuilder = CSLTxBuilder;
|
|
766
|
+
//# sourceMappingURL=csl-tx.js.map
|