@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,636 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BuildooorTxBuilder = void 0;
|
|
7
|
+
const buildooor_1 = require("@harmoniclabs/buildooor");
|
|
8
|
+
const uint8array_utils_1 = require("@harmoniclabs/uint8array-utils");
|
|
9
|
+
const tx_build_helper_1 = require("../../utils/tx-build-helper");
|
|
10
|
+
const cds_1 = __importDefault(require("@sap/cds"));
|
|
11
|
+
const cardano_ledger_ts_1 = require("@harmoniclabs/cardano-ledger-ts");
|
|
12
|
+
const TxMetadata_1 = require("@harmoniclabs/cardano-ledger-ts/dist/tx/metadata/TxMetadata");
|
|
13
|
+
const TxMetadatum_1 = require("@harmoniclabs/cardano-ledger-ts/dist/tx/metadata/TxMetadatum");
|
|
14
|
+
const plutus_data_1 = require("@harmoniclabs/plutus-data");
|
|
15
|
+
const const_1 = require("../../utils/const");
|
|
16
|
+
const logger = cds_1.default.log('BuildooorTxBuilder');
|
|
17
|
+
/**
|
|
18
|
+
* BuildooorTxBuilder - Implementation of CardanoTxBuilder using Buildooor library
|
|
19
|
+
*/
|
|
20
|
+
class BuildooorTxBuilder {
|
|
21
|
+
name = 'BuildooorTxBuilder';
|
|
22
|
+
txBuilder;
|
|
23
|
+
cardanoClient;
|
|
24
|
+
/**
|
|
25
|
+
* Initialize the builder
|
|
26
|
+
* @param client - The CardanoClient instance
|
|
27
|
+
* @param protocolParams - Optional protocol parameters (if not provided, fetched from backend)
|
|
28
|
+
*/
|
|
29
|
+
async init(client, protocolParams) {
|
|
30
|
+
this.cardanoClient = client;
|
|
31
|
+
const params = protocolParams ?? await client.getProtocolParameters();
|
|
32
|
+
const txbParameters = this._mapLedgerParametersToBuildooorParams(params);
|
|
33
|
+
this.txBuilder = new buildooor_1.TxBuilder(txbParameters);
|
|
34
|
+
logger.debug(`TxBuilder initialized with protocol parameters`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build unsigned ADA transfer transaction
|
|
38
|
+
* @param req transaction build request
|
|
39
|
+
* @param ctx transaction build context
|
|
40
|
+
* @returns {Promise<TxBuildResult>} transaction build result
|
|
41
|
+
*/
|
|
42
|
+
async buildUnsignedAdaTransfer(req, ctx) {
|
|
43
|
+
try {
|
|
44
|
+
// mapping of ODATANO UTxO Type to ledger-ts UTxO objects (with multi-asset support)
|
|
45
|
+
// This allows spending UTxOs that contain native assets - they will be returned in the change output
|
|
46
|
+
const ledgerUtxos = ctx.utxos.map(utxo => this._mapMultiAssetUtxoToLedgerUtxo(utxo));
|
|
47
|
+
// and map to Buildooor TxIn objects for inputs
|
|
48
|
+
const inputs = ledgerUtxos.map(utxo => ({ utxo }));
|
|
49
|
+
// set Addresses
|
|
50
|
+
const recipientAddress = cardano_ledger_ts_1.Address.fromString(req.recipientAddress);
|
|
51
|
+
const changeAddress = cardano_ledger_ts_1.Address.fromString(req.changeAddress ?? req.senderAddress);
|
|
52
|
+
// set Amount
|
|
53
|
+
const amount = BigInt(String(req.lovelaceAmount));
|
|
54
|
+
// build new outputs for recipient
|
|
55
|
+
const txOutParams = {
|
|
56
|
+
address: recipientAddress,
|
|
57
|
+
value: cardano_ledger_ts_1.Value.lovelaces(amount),
|
|
58
|
+
};
|
|
59
|
+
if (req.outputDatum) {
|
|
60
|
+
txOutParams.datum = (0, tx_build_helper_1.jsonToPlutusData)(req.outputDatum);
|
|
61
|
+
}
|
|
62
|
+
const outputs = [new cardano_ledger_ts_1.TxOut(txOutParams)];
|
|
63
|
+
// build the transaction
|
|
64
|
+
const tx = await this.txBuilder.build({
|
|
65
|
+
inputs,
|
|
66
|
+
outputs,
|
|
67
|
+
changeAddress,
|
|
68
|
+
});
|
|
69
|
+
// full unsigned tx cbor (4-tuple, witness empty)
|
|
70
|
+
const unsignedTxBytes = tx.toCbor().toBuffer();
|
|
71
|
+
const unsignedTxCbor = (0, uint8array_utils_1.toHex)(unsignedTxBytes);
|
|
72
|
+
const txBodyHash = tx.hash.toString();
|
|
73
|
+
logger.debug(`Built unsigned transaction successfully.`);
|
|
74
|
+
return {
|
|
75
|
+
unsignedTxCbor: unsignedTxCbor,
|
|
76
|
+
txBodyHash: txBodyHash,
|
|
77
|
+
senderAddress: req.senderAddress,
|
|
78
|
+
network: this.cardanoClient.network,
|
|
79
|
+
sizeBytes: unsignedTxBytes.length,
|
|
80
|
+
builderEngine: this.name,
|
|
81
|
+
feeLovelace: tx.body.fee.toString(),
|
|
82
|
+
inputs: ctx.utxos.map(u => ({
|
|
83
|
+
txHash: u.txHash,
|
|
84
|
+
index: u.outputIndex,
|
|
85
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString()
|
|
86
|
+
})),
|
|
87
|
+
outputs: tx.body.outputs.map((o) => ({
|
|
88
|
+
address: o.address?.toString?.() ?? "",
|
|
89
|
+
lovelace: o.value?.lovelaces?.toString?.() ?? "0"
|
|
90
|
+
})),
|
|
91
|
+
warnings: []
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async buildUnsignedTransactionWithMetadata(req, ctx) {
|
|
99
|
+
try {
|
|
100
|
+
// mapping of ODATANO UTxO Type to ledger-ts UTxO objects (with multi-asset support)
|
|
101
|
+
// This allows spending UTxOs that contain native assets - they will be returned in the change output
|
|
102
|
+
const ledgerUtxos = ctx.utxos.map(utxo => this._mapMultiAssetUtxoToLedgerUtxo(utxo));
|
|
103
|
+
// Buildooor TxIn objects for inputs
|
|
104
|
+
const inputs = ledgerUtxos.map(utxo => ({ utxo }));
|
|
105
|
+
// Addresses
|
|
106
|
+
const recipientAddress = cardano_ledger_ts_1.Address.fromString(req.recipientAddress);
|
|
107
|
+
const changeAddress = cardano_ledger_ts_1.Address.fromString(req.changeAddress ?? req.senderAddress);
|
|
108
|
+
// Amount
|
|
109
|
+
const amount = BigInt(String(req.lovelaceAmount));
|
|
110
|
+
const metadata = this._mapOdatanoMetadataToLedgerMetadata(req.metadataJson);
|
|
111
|
+
// build new outputs for recipient
|
|
112
|
+
const outputs = [
|
|
113
|
+
new cardano_ledger_ts_1.TxOut({
|
|
114
|
+
address: recipientAddress,
|
|
115
|
+
value: cardano_ledger_ts_1.Value.lovelaces(amount)
|
|
116
|
+
})
|
|
117
|
+
];
|
|
118
|
+
// build the transaction
|
|
119
|
+
const tx = await this.txBuilder.build({
|
|
120
|
+
inputs,
|
|
121
|
+
outputs,
|
|
122
|
+
changeAddress,
|
|
123
|
+
metadata
|
|
124
|
+
});
|
|
125
|
+
// full unsigned tx cbor (4-tuple, witness empty)
|
|
126
|
+
const unsignedTxBytes = tx.toCbor().toBuffer();
|
|
127
|
+
const unsignedTxCbor = (0, uint8array_utils_1.toHex)(unsignedTxBytes);
|
|
128
|
+
const txBodyHash = tx.hash.toString();
|
|
129
|
+
logger.debug(`Built unsigned transaction successfully.`);
|
|
130
|
+
return {
|
|
131
|
+
unsignedTxCbor: unsignedTxCbor,
|
|
132
|
+
txBodyHash: txBodyHash,
|
|
133
|
+
senderAddress: req.senderAddress,
|
|
134
|
+
network: this.cardanoClient.network,
|
|
135
|
+
builderEngine: this.name,
|
|
136
|
+
sizeBytes: unsignedTxBytes.length,
|
|
137
|
+
feeLovelace: tx.body.fee.toString(),
|
|
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: tx.body.outputs.map((o) => ({
|
|
144
|
+
address: o.address?.toString?.() ?? "",
|
|
145
|
+
lovelace: o.value?.lovelaces?.toString?.() ?? "0"
|
|
146
|
+
})),
|
|
147
|
+
warnings: []
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async buildUnsignedMultiAssetTransaction(req, ctx) {
|
|
155
|
+
if (!req.assets || req.assets.length === 0) {
|
|
156
|
+
throw new Error('[BuildooorTxBuilder] buildUnsignedMultiAssetTransaction requires assets to be specified');
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
// Map all available UTxOs to ledger UTxOs for Buildooor (let builder handle selection)
|
|
160
|
+
const ledgerUtxos = ctx.utxos.map(utxo => this._mapMultiAssetUtxoToLedgerUtxo(utxo));
|
|
161
|
+
// Buildooor TxIn objects for inputs
|
|
162
|
+
const inputs = ledgerUtxos.map(utxo => ({ utxo }));
|
|
163
|
+
// Addresses
|
|
164
|
+
const recipientAddress = cardano_ledger_ts_1.Address.fromString(req.recipientAddress);
|
|
165
|
+
const changeAddress = cardano_ledger_ts_1.Address.fromString(req.changeAddress ?? req.senderAddress);
|
|
166
|
+
// Build output value with ADA + assets
|
|
167
|
+
let outputValue = cardano_ledger_ts_1.Value.lovelaces(BigInt(req.lovelaceAmount));
|
|
168
|
+
for (const asset of req.assets) {
|
|
169
|
+
// Parse policyId and assetName from unit (format: policyId.assetName or policyId+assetName)
|
|
170
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(asset.unit);
|
|
171
|
+
const policyHash = new cardano_ledger_ts_1.Hash28(policyId);
|
|
172
|
+
const assetValue = cardano_ledger_ts_1.Value.singleAsset(policyHash, Buffer.from(assetName, 'hex'), BigInt(asset.quantity));
|
|
173
|
+
outputValue = cardano_ledger_ts_1.Value.add(outputValue, assetValue);
|
|
174
|
+
}
|
|
175
|
+
// Build output
|
|
176
|
+
const outputs = [
|
|
177
|
+
new cardano_ledger_ts_1.TxOut({
|
|
178
|
+
address: recipientAddress,
|
|
179
|
+
value: outputValue
|
|
180
|
+
})
|
|
181
|
+
];
|
|
182
|
+
// Build the transaction
|
|
183
|
+
const tx = await this.txBuilder.build({
|
|
184
|
+
inputs,
|
|
185
|
+
outputs,
|
|
186
|
+
changeAddress,
|
|
187
|
+
});
|
|
188
|
+
// Full unsigned tx cbor (4-tuple, witness empty)
|
|
189
|
+
const unsignedTxBytes = tx.toCbor().toBuffer();
|
|
190
|
+
const unsignedTxCbor = (0, uint8array_utils_1.toHex)(unsignedTxBytes);
|
|
191
|
+
const txBodyHash = tx.hash.toString();
|
|
192
|
+
logger.debug(`Built unsigned multi-asset transaction successfully.`);
|
|
193
|
+
return {
|
|
194
|
+
unsignedTxCbor: unsignedTxCbor,
|
|
195
|
+
txBodyHash: txBodyHash,
|
|
196
|
+
senderAddress: req.senderAddress,
|
|
197
|
+
network: this.cardanoClient.network,
|
|
198
|
+
sizeBytes: unsignedTxBytes.length,
|
|
199
|
+
builderEngine: this.name,
|
|
200
|
+
feeLovelace: tx.body.fee.toString(),
|
|
201
|
+
inputs: ctx.utxos.map(u => ({
|
|
202
|
+
txHash: u.txHash,
|
|
203
|
+
index: u.outputIndex,
|
|
204
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString()
|
|
205
|
+
})),
|
|
206
|
+
outputs: tx.body.outputs.map((o) => ({
|
|
207
|
+
address: o.address?.toString?.() ?? "",
|
|
208
|
+
lovelace: o.value?.lovelaces?.toString?.() ?? "0"
|
|
209
|
+
})),
|
|
210
|
+
warnings: []
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
// Extract asset unit from error message if possible
|
|
215
|
+
const assetMatch = err?.message?.match(/not enough\s+([a-f0-9.]+)/i);
|
|
216
|
+
const assetUnit = assetMatch?.[1];
|
|
217
|
+
(0, tx_build_helper_1.mapBuilderError)(err, assetUnit);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async buildUnsignedMintTransaction(req, ctx) {
|
|
221
|
+
try {
|
|
222
|
+
// Map all available UTxOs to ledger UTxOs (let builder handle selection)
|
|
223
|
+
// Use multi-asset mapper to support burn transactions
|
|
224
|
+
const ledgerUtxos = ctx.utxos.map(utxo => this._mapMultiAssetUtxoToLedgerUtxo(utxo));
|
|
225
|
+
// Buildooor TxIn objects for inputs
|
|
226
|
+
const inputs = ledgerUtxos.map(utxo => ({ utxo }));
|
|
227
|
+
// Addresses
|
|
228
|
+
const recipientAddress = cardano_ledger_ts_1.Address.fromString(req.recipientAddress);
|
|
229
|
+
const changeAddress = cardano_ledger_ts_1.Address.fromString(req.changeAddress ?? req.senderAddress);
|
|
230
|
+
// Parse the minting policy script once
|
|
231
|
+
const scriptBytes = Buffer.from(req.mintingPolicyScript, 'hex');
|
|
232
|
+
const script = cardano_ledger_ts_1.Script.fromCbor(scriptBytes);
|
|
233
|
+
// Helper to build mints array with specified execution units
|
|
234
|
+
const buildMints = (exUnits) => {
|
|
235
|
+
const mints = [];
|
|
236
|
+
for (const mintAction of req.mintActions) {
|
|
237
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(mintAction.assetUnit);
|
|
238
|
+
const policyHash = new cardano_ledger_ts_1.Hash28(policyId);
|
|
239
|
+
const assetValue = cardano_ledger_ts_1.Value.singleAsset(policyHash, Buffer.from(assetName, 'hex'), BigInt(mintAction.quantity));
|
|
240
|
+
mints.push({
|
|
241
|
+
value: assetValue,
|
|
242
|
+
script: {
|
|
243
|
+
inline: script,
|
|
244
|
+
redeemer: new plutus_data_1.DataI(mintAction.redeemer ?? 0),
|
|
245
|
+
executionUnits: exUnits
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return mints;
|
|
250
|
+
};
|
|
251
|
+
// Calculate total mint value for output (only positive quantities - mints, not burns)
|
|
252
|
+
let mintValue = cardano_ledger_ts_1.Value.lovelaces(0n);
|
|
253
|
+
for (const mintAction of req.mintActions) {
|
|
254
|
+
const quantity = BigInt(mintAction.quantity);
|
|
255
|
+
if (quantity > 0n) {
|
|
256
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(mintAction.assetUnit);
|
|
257
|
+
const policyHash = new cardano_ledger_ts_1.Hash28(policyId);
|
|
258
|
+
const assetValue = cardano_ledger_ts_1.Value.singleAsset(policyHash, Buffer.from(assetName, 'hex'), quantity);
|
|
259
|
+
mintValue = cardano_ledger_ts_1.Value.add(mintValue, assetValue);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Build output value - recipient gets the minted assets + min ADA
|
|
263
|
+
let outputValue = cardano_ledger_ts_1.Value.lovelaces(BigInt(req.lovelaceAmount || 1_000_000));
|
|
264
|
+
outputValue = cardano_ledger_ts_1.Value.add(outputValue, mintValue);
|
|
265
|
+
// Build output
|
|
266
|
+
const outputs = [
|
|
267
|
+
new cardano_ledger_ts_1.TxOut({
|
|
268
|
+
address: recipientAddress,
|
|
269
|
+
value: outputValue
|
|
270
|
+
})
|
|
271
|
+
];
|
|
272
|
+
// Find an ADA-only UTxO for collateral (Plutus scripts require ADA-only collateral)
|
|
273
|
+
const adaOnlyUtxo = ctx.utxos.find(u => u.amount.every(a => a.unit.toLowerCase() === 'lovelace'));
|
|
274
|
+
if (!adaOnlyUtxo) {
|
|
275
|
+
throw new Error('[BuildooorTxBuilder] No ADA-only UTxO available for collateral. Plutus scripts require ADA-only collateral.');
|
|
276
|
+
}
|
|
277
|
+
// Use only 1 collateral to minimize tx size
|
|
278
|
+
const collateralUtxos = [this._mapOdatanoUtxoToLedgerUtxo(adaOnlyUtxo)];
|
|
279
|
+
// Determine execution units based on evaluator availability
|
|
280
|
+
let finalExUnits = const_1.DEFAULT_EXECUTION_UNITS;
|
|
281
|
+
if (ctx.evaluateTransaction) {
|
|
282
|
+
// Build first pass with high execution units for evaluation
|
|
283
|
+
logger.debug(`Building evaluation pass with high execution units`);
|
|
284
|
+
const evalMints = buildMints(const_1.HIGH_EXECUTION_UNITS);
|
|
285
|
+
const evalTx = await this.txBuilder.build({
|
|
286
|
+
inputs,
|
|
287
|
+
outputs,
|
|
288
|
+
changeAddress,
|
|
289
|
+
mints: evalMints,
|
|
290
|
+
collaterals: collateralUtxos
|
|
291
|
+
});
|
|
292
|
+
const evalTxCbor = (0, uint8array_utils_1.toHex)(evalTx.toCbor().toBuffer());
|
|
293
|
+
try {
|
|
294
|
+
// Evaluate to get exact execution units
|
|
295
|
+
const evalResults = await ctx.evaluateTransaction(evalTxCbor);
|
|
296
|
+
logger.debug(`Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
297
|
+
if (evalResults && evalResults.length > 0) {
|
|
298
|
+
// Use the evaluated budget (take first result for single script)
|
|
299
|
+
const budget = evalResults[0].budget;
|
|
300
|
+
// Add safety margin to evaluated units
|
|
301
|
+
finalExUnits = {
|
|
302
|
+
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER),
|
|
303
|
+
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER)
|
|
304
|
+
};
|
|
305
|
+
logger.info(`Using evaluated execution units: mem=${finalExUnits.mem}, cpu=${finalExUnits.cpu}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (evalError) {
|
|
309
|
+
logger.warn(`Evaluation failed, using default units: ${evalError.message}`);
|
|
310
|
+
// Fall back to defaults on evaluation failure
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
logger.debug(`No evaluator available, using default execution units`);
|
|
315
|
+
}
|
|
316
|
+
// Build with final execution units
|
|
317
|
+
const mints = buildMints(finalExUnits);
|
|
318
|
+
// First build to calculate base fee
|
|
319
|
+
const txFirstPass = await this.txBuilder.build({
|
|
320
|
+
inputs,
|
|
321
|
+
outputs,
|
|
322
|
+
changeAddress,
|
|
323
|
+
mints: mints,
|
|
324
|
+
collaterals: collateralUtxos
|
|
325
|
+
});
|
|
326
|
+
// Add minimal buffer for witness set CBOR overhead (signing adds ~44 bytes)
|
|
327
|
+
const calculatedFee = BigInt(txFirstPass.body.fee.toString());
|
|
328
|
+
const witnessBuffer = BigInt(const_1.WITNESS_BUFFER_BYTES);
|
|
329
|
+
const adjustedMinFee = calculatedFee + witnessBuffer;
|
|
330
|
+
logger.debug(`First pass fee: ${calculatedFee}, rebuilding with witness buffer: ${adjustedMinFee}`);
|
|
331
|
+
// Final build with adjusted minimum fee to account for witness overhead
|
|
332
|
+
const tx = await this.txBuilder.build({
|
|
333
|
+
inputs,
|
|
334
|
+
outputs,
|
|
335
|
+
changeAddress,
|
|
336
|
+
mints: mints,
|
|
337
|
+
collaterals: collateralUtxos,
|
|
338
|
+
fee: adjustedMinFee
|
|
339
|
+
});
|
|
340
|
+
// Full unsigned tx cbor (4-tuple, witness empty)
|
|
341
|
+
const unsignedTxBytes = tx.toCbor().toBuffer();
|
|
342
|
+
const unsignedTxCbor = (0, uint8array_utils_1.toHex)(unsignedTxBytes);
|
|
343
|
+
const txBodyHash = tx.hash.toString();
|
|
344
|
+
logger.debug(`Built unsigned minting transaction successfully with fee: ${tx.body.fee.toString()}`);
|
|
345
|
+
return {
|
|
346
|
+
unsignedTxCbor: unsignedTxCbor,
|
|
347
|
+
txBodyHash: txBodyHash,
|
|
348
|
+
senderAddress: req.senderAddress,
|
|
349
|
+
network: this.cardanoClient.network,
|
|
350
|
+
builderEngine: this.name,
|
|
351
|
+
sizeBytes: unsignedTxBytes.length,
|
|
352
|
+
feeLovelace: tx.body.fee.toString(),
|
|
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: tx.body.outputs.map((o) => ({
|
|
359
|
+
address: o.address?.toString?.() ?? "",
|
|
360
|
+
lovelace: o.value?.lovelaces?.toString?.() ?? "0"
|
|
361
|
+
})),
|
|
362
|
+
warnings: []
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async buildUnsignedPlutusSpendTransaction(req, ctx) {
|
|
370
|
+
try {
|
|
371
|
+
const { plutusScriptExecution } = req;
|
|
372
|
+
// Parse the validator script
|
|
373
|
+
const scriptBytes = Buffer.from(plutusScriptExecution.validatorScript, 'hex');
|
|
374
|
+
const script = cardano_ledger_ts_1.Script.fromCbor(scriptBytes);
|
|
375
|
+
// Convert redeemer JSON to PlutusData
|
|
376
|
+
const redeemerData = (0, tx_build_helper_1.jsonToPlutusData)(plutusScriptExecution.redeemer);
|
|
377
|
+
// Determine datum: "inline" if no datum provided (assumes inline datum on UTxO), otherwise convert
|
|
378
|
+
const datum = plutusScriptExecution.datum
|
|
379
|
+
? (0, tx_build_helper_1.jsonToPlutusData)(plutusScriptExecution.datum)
|
|
380
|
+
: "inline";
|
|
381
|
+
// Find the specific script UTxO in the provided context UTxOs
|
|
382
|
+
// The coordinator is responsible for including the script UTxO in ctx.utxos
|
|
383
|
+
const scriptUtxoRef = plutusScriptExecution.scriptUtxo;
|
|
384
|
+
const scriptOdatanoUtxo = ctx.utxos.find(u => u.txHash === scriptUtxoRef.txHash && u.outputIndex === scriptUtxoRef.outputIndex);
|
|
385
|
+
if (!scriptOdatanoUtxo) {
|
|
386
|
+
throw new Error(`[BuildooorTxBuilder] Script UTxO ${scriptUtxoRef.txHash}#${scriptUtxoRef.outputIndex} not found in provided UTxOs`);
|
|
387
|
+
}
|
|
388
|
+
// Map the script UTxO to ledger format
|
|
389
|
+
const scriptLedgerUtxo = this._mapMultiAssetUtxoToLedgerUtxo(scriptOdatanoUtxo);
|
|
390
|
+
// Map sender UTxOs (excluding the script UTxO) as regular inputs
|
|
391
|
+
const senderUtxos = ctx.utxos.filter(u => !(u.txHash === scriptUtxoRef.txHash && u.outputIndex === scriptUtxoRef.outputIndex));
|
|
392
|
+
const senderLedgerUtxos = senderUtxos.map(utxo => this._mapMultiAssetUtxoToLedgerUtxo(utxo));
|
|
393
|
+
// Addresses
|
|
394
|
+
const recipientAddress = cardano_ledger_ts_1.Address.fromString(req.recipientAddress);
|
|
395
|
+
const changeAddress = cardano_ledger_ts_1.Address.fromString(req.changeAddress ?? req.senderAddress);
|
|
396
|
+
// Build output
|
|
397
|
+
const outputValue = cardano_ledger_ts_1.Value.lovelaces(BigInt(req.lovelaceAmount || 2_000_000));
|
|
398
|
+
const outputs = [
|
|
399
|
+
new cardano_ledger_ts_1.TxOut({
|
|
400
|
+
address: recipientAddress,
|
|
401
|
+
value: outputValue
|
|
402
|
+
})
|
|
403
|
+
];
|
|
404
|
+
// Find an ADA-only UTxO for collateral (from sender UTxOs only)
|
|
405
|
+
const adaOnlyUtxo = senderUtxos.find(u => u.amount.every(a => a.unit.toLowerCase() === 'lovelace'));
|
|
406
|
+
if (!adaOnlyUtxo) {
|
|
407
|
+
throw new Error('[BuildooorTxBuilder] No ADA-only UTxO available for collateral. Plutus scripts require ADA-only collateral.');
|
|
408
|
+
}
|
|
409
|
+
const collateralUtxos = [this._mapOdatanoUtxoToLedgerUtxo(adaOnlyUtxo)];
|
|
410
|
+
// Helper to build inputs with specified execution units
|
|
411
|
+
const buildInputs = (exUnits) => {
|
|
412
|
+
// Script input with witness
|
|
413
|
+
const scriptInput = {
|
|
414
|
+
utxo: scriptLedgerUtxo,
|
|
415
|
+
inputScript: {
|
|
416
|
+
script,
|
|
417
|
+
datum,
|
|
418
|
+
redeemer: redeemerData,
|
|
419
|
+
executionUnits: exUnits
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
// Regular sender inputs for fees
|
|
423
|
+
const regularInputs = senderLedgerUtxos.map(utxo => ({ utxo }));
|
|
424
|
+
return [scriptInput, ...regularInputs];
|
|
425
|
+
};
|
|
426
|
+
// Determine execution units based on evaluator availability
|
|
427
|
+
let finalExUnits = const_1.DEFAULT_EXECUTION_UNITS;
|
|
428
|
+
if (ctx.evaluateTransaction) {
|
|
429
|
+
// Build first pass with high execution units for evaluation
|
|
430
|
+
logger.debug(`Building evaluation pass with high execution units`);
|
|
431
|
+
const evalInputs = buildInputs(const_1.HIGH_EXECUTION_UNITS);
|
|
432
|
+
const evalTx = await this.txBuilder.build({
|
|
433
|
+
inputs: evalInputs,
|
|
434
|
+
outputs,
|
|
435
|
+
changeAddress,
|
|
436
|
+
collaterals: collateralUtxos
|
|
437
|
+
});
|
|
438
|
+
const evalTxCbor = (0, uint8array_utils_1.toHex)(evalTx.toCbor().toBuffer());
|
|
439
|
+
try {
|
|
440
|
+
const evalResults = await ctx.evaluateTransaction(evalTxCbor);
|
|
441
|
+
logger.debug(`Evaluation results: ${JSON.stringify(evalResults)}`);
|
|
442
|
+
if (evalResults && evalResults.length > 0) {
|
|
443
|
+
const budget = evalResults[0].budget;
|
|
444
|
+
finalExUnits = {
|
|
445
|
+
mem: Math.ceil(budget.memory * const_1.EXECUTION_UNIT_BUFFER),
|
|
446
|
+
cpu: Math.ceil(budget.cpu * const_1.EXECUTION_UNIT_BUFFER)
|
|
447
|
+
};
|
|
448
|
+
logger.info(`Using evaluated execution units: mem=${finalExUnits.mem}, cpu=${finalExUnits.cpu}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (evalError) {
|
|
452
|
+
logger.warn(`Evaluation failed, using default units: ${evalError.message}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
logger.debug(`No evaluator available, using default execution units`);
|
|
457
|
+
}
|
|
458
|
+
// Build with final execution units
|
|
459
|
+
const inputs = buildInputs(finalExUnits);
|
|
460
|
+
// First build to calculate base fee
|
|
461
|
+
const txFirstPass = await this.txBuilder.build({
|
|
462
|
+
inputs,
|
|
463
|
+
outputs,
|
|
464
|
+
changeAddress,
|
|
465
|
+
collaterals: collateralUtxos
|
|
466
|
+
});
|
|
467
|
+
// Add minimal buffer for witness set CBOR overhead
|
|
468
|
+
const calculatedFee = BigInt(txFirstPass.body.fee.toString());
|
|
469
|
+
const witnessBuffer = BigInt(const_1.WITNESS_BUFFER_BYTES);
|
|
470
|
+
const adjustedMinFee = calculatedFee + witnessBuffer;
|
|
471
|
+
logger.debug(`First pass fee: ${calculatedFee}, rebuilding with witness buffer: ${adjustedMinFee}`);
|
|
472
|
+
// Final build with adjusted minimum fee
|
|
473
|
+
const tx = await this.txBuilder.build({
|
|
474
|
+
inputs,
|
|
475
|
+
outputs,
|
|
476
|
+
changeAddress,
|
|
477
|
+
collaterals: collateralUtxos,
|
|
478
|
+
fee: adjustedMinFee
|
|
479
|
+
});
|
|
480
|
+
const unsignedTxBytes = tx.toCbor().toBuffer();
|
|
481
|
+
const unsignedTxCbor = (0, uint8array_utils_1.toHex)(unsignedTxBytes);
|
|
482
|
+
const txBodyHash = tx.hash.toString();
|
|
483
|
+
logger.debug(`Built unsigned Plutus spending transaction successfully with fee: ${tx.body.fee.toString()}`);
|
|
484
|
+
return {
|
|
485
|
+
unsignedTxCbor,
|
|
486
|
+
txBodyHash,
|
|
487
|
+
senderAddress: req.senderAddress,
|
|
488
|
+
network: this.cardanoClient.network,
|
|
489
|
+
builderEngine: this.name,
|
|
490
|
+
sizeBytes: unsignedTxBytes.length,
|
|
491
|
+
feeLovelace: tx.body.fee.toString(),
|
|
492
|
+
inputs: ctx.utxos.map(u => ({
|
|
493
|
+
txHash: u.txHash,
|
|
494
|
+
index: u.outputIndex,
|
|
495
|
+
lovelace: (0, tx_build_helper_1.getLovelace)(u).toString()
|
|
496
|
+
})),
|
|
497
|
+
outputs: tx.body.outputs.map((o) => ({
|
|
498
|
+
address: o.address?.toString?.() ?? "",
|
|
499
|
+
lovelace: o.value?.lovelaces?.toString?.() ?? "0"
|
|
500
|
+
})),
|
|
501
|
+
warnings: []
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
(0, tx_build_helper_1.mapBuilderError)(err, 'lovelace');
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
//---------------------------------------------------------------------------
|
|
509
|
+
// Private Helper Methods
|
|
510
|
+
//---------------------------------------------------------------------------
|
|
511
|
+
/**
|
|
512
|
+
* Map ODATANO LedgerProtocolParameter to Buildooor's ProtocolParameters shape
|
|
513
|
+
* @param protocolParameters ledger protocol parameters
|
|
514
|
+
* @returns mapped protocol parameters
|
|
515
|
+
*/
|
|
516
|
+
_mapLedgerParametersToBuildooorParams(protocolParameters) {
|
|
517
|
+
// Map LedgerProtocolParameter to Buildooor's ProtocolParameters shape
|
|
518
|
+
// Using defaultProtocolParameters as base and overriding with actual values
|
|
519
|
+
return {
|
|
520
|
+
...cardano_ledger_ts_1.defaultProtocolParameters,
|
|
521
|
+
txFeePerByte: Number(protocolParameters.minFeeA),
|
|
522
|
+
txFeeFixed: Number(protocolParameters.minFeeB),
|
|
523
|
+
utxoCostPerByte: Number(protocolParameters.coinsPerUtxoSize),
|
|
524
|
+
poolDeposit: Number(protocolParameters.poolDeposit),
|
|
525
|
+
keyDeposit: Number(protocolParameters.keyDeposit),
|
|
526
|
+
maxTxSize: Number(protocolParameters.maxTxSize),
|
|
527
|
+
maxValueSize: Number(protocolParameters.maxValSize),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Map ODATANO UTxO to Ledger UTxO (ADA-only)
|
|
532
|
+
* @param utxos ODATANO UTxO
|
|
533
|
+
* @returns mapped Ledger UTxO
|
|
534
|
+
*/
|
|
535
|
+
_mapOdatanoUtxoToLedgerUtxo(utxos) {
|
|
536
|
+
(0, tx_build_helper_1.assertAdaOnly)(utxos);
|
|
537
|
+
const outRef = new cardano_ledger_ts_1.TxOutRef({
|
|
538
|
+
id: utxos.txHash,
|
|
539
|
+
index: utxos.outputIndex
|
|
540
|
+
});
|
|
541
|
+
const addr = cardano_ledger_ts_1.Address.fromString(utxos.address);
|
|
542
|
+
const value = cardano_ledger_ts_1.Value.lovelaces((0, tx_build_helper_1.getLovelace)(utxos));
|
|
543
|
+
return new cardano_ledger_ts_1.UTxO({
|
|
544
|
+
utxoRef: outRef,
|
|
545
|
+
resolved: new cardano_ledger_ts_1.TxOut({
|
|
546
|
+
address: addr,
|
|
547
|
+
value,
|
|
548
|
+
datum: undefined,
|
|
549
|
+
refScript: undefined
|
|
550
|
+
})
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Map ODATANO UTxO to Ledger UTxO (with multi-asset support)
|
|
555
|
+
* @param utxo ODATANO UTxO
|
|
556
|
+
* @returns mapped Ledger UTxO
|
|
557
|
+
*/
|
|
558
|
+
_mapMultiAssetUtxoToLedgerUtxo(utxo) {
|
|
559
|
+
const outRef = new cardano_ledger_ts_1.TxOutRef({
|
|
560
|
+
id: utxo.txHash,
|
|
561
|
+
index: utxo.outputIndex
|
|
562
|
+
});
|
|
563
|
+
const addr = cardano_ledger_ts_1.Address.fromString(utxo.address);
|
|
564
|
+
// Build Value from all amounts
|
|
565
|
+
let value = cardano_ledger_ts_1.Value.lovelaces((0, tx_build_helper_1.getLovelace)(utxo));
|
|
566
|
+
for (const amount of utxo.amount) {
|
|
567
|
+
if (amount.unit.toLowerCase() !== 'lovelace') {
|
|
568
|
+
const { policyId, assetName } = (0, tx_build_helper_1.parseAssetUnit)(amount.unit);
|
|
569
|
+
const policyHash = new cardano_ledger_ts_1.Hash28(policyId);
|
|
570
|
+
const assetValue = cardano_ledger_ts_1.Value.singleAsset(policyHash, Buffer.from(assetName, 'hex'), BigInt(amount.quantity));
|
|
571
|
+
value = cardano_ledger_ts_1.Value.add(value, assetValue);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return new cardano_ledger_ts_1.UTxO({
|
|
575
|
+
utxoRef: outRef,
|
|
576
|
+
resolved: new cardano_ledger_ts_1.TxOut({
|
|
577
|
+
address: addr,
|
|
578
|
+
value,
|
|
579
|
+
datum: undefined,
|
|
580
|
+
refScript: undefined
|
|
581
|
+
})
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Map ODATANO metadata JSON to Ledger TxMetadata
|
|
586
|
+
* @param metadataJson metadata in JSON format
|
|
587
|
+
* @returns mapped TxMetadata
|
|
588
|
+
*/
|
|
589
|
+
_mapOdatanoMetadataToLedgerMetadata(metadataJson) {
|
|
590
|
+
if (!metadataJson) {
|
|
591
|
+
return new TxMetadata_1.TxMetadata({});
|
|
592
|
+
}
|
|
593
|
+
const metadata = {};
|
|
594
|
+
for (const [label, value] of Object.entries(metadataJson)) {
|
|
595
|
+
// convert label to number
|
|
596
|
+
const numericLabel = parseInt(label, 10);
|
|
597
|
+
// convert JSON value to TxMetadatum
|
|
598
|
+
const txMetadatum = this._jsonToTxMetadatum(value);
|
|
599
|
+
logger.debug(`Created TxMetadatum for label ${numericLabel}: ${txMetadatum.constructor.name}`);
|
|
600
|
+
metadata[numericLabel] = txMetadatum;
|
|
601
|
+
}
|
|
602
|
+
logger.debug(`Creating TxMetadata with ${Object.keys(metadata).length} labels`);
|
|
603
|
+
const txMetadata = new TxMetadata_1.TxMetadata(metadata);
|
|
604
|
+
logger.debug(`TxMetadata created: ${txMetadata.constructor.name}, instanceof check: ${txMetadata instanceof TxMetadata_1.TxMetadata}`);
|
|
605
|
+
return txMetadata;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Recursively convert JSON value to TxMetadatum
|
|
609
|
+
* @param value JSON value
|
|
610
|
+
* @returns corresponding TxMetadatum
|
|
611
|
+
*/
|
|
612
|
+
_jsonToTxMetadatum(value) {
|
|
613
|
+
if (typeof value === 'number' || typeof value === 'bigint') {
|
|
614
|
+
return new TxMetadatum_1.TxMetadatumInt(BigInt(value));
|
|
615
|
+
}
|
|
616
|
+
if (typeof value === 'string') {
|
|
617
|
+
return new TxMetadatum_1.TxMetadatumText(value);
|
|
618
|
+
}
|
|
619
|
+
if (Array.isArray(value)) {
|
|
620
|
+
return new TxMetadatum_1.TxMetadatumList(value.map(v => this._jsonToTxMetadatum(v)));
|
|
621
|
+
}
|
|
622
|
+
if (typeof value === 'object' && value !== null) {
|
|
623
|
+
const map = [];
|
|
624
|
+
for (const [k, v] of Object.entries(value)) {
|
|
625
|
+
map.push({
|
|
626
|
+
k: new TxMetadatum_1.TxMetadatumText(k),
|
|
627
|
+
v: this._jsonToTxMetadatum(v)
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return new TxMetadatum_1.TxMetadatumMap(map);
|
|
631
|
+
}
|
|
632
|
+
throw new Error(`[BuildooorTxBuilder] Unsupported metadata value type: ${typeof value}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
exports.BuildooorTxBuilder = BuildooorTxBuilder;
|
|
636
|
+
//# sourceMappingURL=buildooor-tx.js.map
|