@mutants/cardano-tx-builder
Version:
A package that provides utility functions to build and destructure a cardano transaction
267 lines (266 loc) • 11.3 kB
JavaScript
"use strict";
// https://github.com/input-output-hk/cardano-ledger/blob/master/eras/babbage/test-suite/cddl-files/babbage.cddl
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionBuilder = void 0;
const cbor_1 = require("cbor");
const Plutusv1CostModel_json_1 = __importDefault(require("./Plutusv1CostModel.json"));
const Plutusv2CostModel_json_1 = __importDefault(require("./Plutusv2CostModel.json"));
const areInputsEqual_1 = require("./areInputsEqual");
const calculateChange_1 = require("./calculateChange");
const calculateFee_1 = require("./calculateFee");
const constants_1 = require("./constants");
const encodeInputs_1 = require("./encodeInputs");
const encodeOutputs_1 = require("./encodeOutputs");
const findInputIndex_1 = require("./findInputIndex");
const hexToHash_1 = require("./hexToHash");
const organizeOutputs_1 = require("./organizeOutputs");
const sortInputs_1 = require("./sortInputs");
const tagPlutusData_1 = require("./tagPlutusData");
const toScriptDataHash_1 = require("./toScriptDataHash");
class TransactionBuilder {
/**
* Initialize a transaction builder.
*
* @param protocolParameters The protocol pameters of the current epoch.
* Important: default values should not be used in production.
*/
constructor(protocolParameters = {
min_fee_a: 44,
min_fee_b: 155381,
price_mem: 0.0577,
price_step: 0.0000721,
collateral_percent: 150,
coins_per_utxo_word: "4310",
}) {
this.protocolParameters = protocolParameters;
this.inputs = [];
this.referenceInputs = [];
this.outputs = [];
this.fee = 0;
this.ttl = 0;
this.collateralInputs = [];
this.requiredSigners = [];
this.redeemers = [];
this.preBuildRedeemers = [];
this.plutusV1Scripts = [];
this.plutusV2Scripts = [];
this.plutusDatas = [];
this.vKeyWitnesses = [];
this.changeAddress = null;
this.totalCollateral = 0;
this.metadata = null;
}
setInputs(inputs) {
this.inputs = inputs;
}
setReferenceInputs(inputs) {
this.referenceInputs = inputs;
}
setOutputs(outputs) {
this.outputs = outputs;
}
setTtl(ttl) {
this.ttl = ttl;
}
setCollateralInputs(collateralInputs) {
this.collateralInputs = collateralInputs;
}
setProtocolParameters(protocolParameters) {
this.protocolParameters = protocolParameters;
}
setRequiredSigners(requiredSigners) {
this.requiredSigners = requiredSigners;
}
setChangeAddress(address) {
this.changeAddress = address;
}
setFee(fee) {
this.fee = fee;
}
calculateFee() {
this.buildRedeemers();
this.setFee((0, calculateFee_1.calculateFee)(this, this.protocolParameters));
this.totalCollateral =
(this.fee * (this.protocolParameters.collateral_percent || 150)) / 100;
}
buildRedeemers() {
if (this.preBuildRedeemers.length) {
this.redeemers = this.preBuildRedeemers.map((redeemer) => [
redeemer[0],
(0, findInputIndex_1.findInputIndex)(this.inputs, redeemer[1]),
(0, tagPlutusData_1.tagPlutusData)(redeemer[2]),
redeemer[3],
]);
}
}
setRedeemers(redeemers) {
this.preBuildRedeemers = redeemers;
}
setPlutusDatas(plutusDatas) {
this.plutusDatas = plutusDatas;
}
setEncodedVKeyWitnesses(cborVkeyWitness) {
const decodedVkeyWitness = (0, cbor_1.decode)(cborVkeyWitness);
if (Array.isArray(decodedVkeyWitness)) {
this.vKeyWitnesses = decodedVkeyWitness;
}
else if (decodedVkeyWitness instanceof Map) {
const arr = decodedVkeyWitness.get(0);
this.vKeyWitnesses = Array.isArray(arr) ? arr : [];
}
}
setPlutusV2Scripts(scripts) {
this.plutusV2Scripts = scripts;
}
setPlutusV1Scripts(scripts) {
this.plutusV1Scripts = scripts;
}
setMetadataMsg(messages) {
const metadataMessage = new Map();
metadataMessage.set("msg", messages);
this.setMetadataPublicLabel(674, metadataMessage);
}
setMetadataPublicLabel(label, value) {
if (!this.metadata) {
this.metadata = new Map();
}
this.metadata.set(label, value);
}
getRedeemers() {
return this.redeemers;
}
buildBody() {
this.inputs = (0, sortInputs_1.sortInputs)(this.inputs); // This is important to make sure the redeemer tags the correct input index.
const txBody = new Map();
// Simulate an output with current fee value, to make sure
// we have the necessary coin input.
const feeOutput = {
address: this.inputs[0].address,
value: {
coin: this.fee,
},
};
const change = (0, calculateChange_1.calculateChange)(this.inputs, [...this.outputs, feeOutput]);
const changeOutput = {
address: this.changeAddress || this.inputs[0].address,
value: change,
};
txBody.set(constants_1.BabbageTransactionBody.Inputs, (0, encodeInputs_1.encodeInputs)(this.inputs));
txBody.set(constants_1.BabbageTransactionBody.Outputs, (0, encodeOutputs_1.encodeOutputs)((0, organizeOutputs_1.organizeOutputs)([...this.outputs, changeOutput])));
txBody.set(constants_1.BabbageTransactionBody.Fee, this.fee); // can't serialize BigInt but also don't see a fee going over 2^53
if (this.ttl) {
txBody.set(constants_1.BabbageTransactionBody.TTL, this.ttl);
}
// TODO: Implement withdrawals
// if (this.withdrawals) {
// txBody.set(BabbageTransactionBody.Withdrawals, this.withdrawals);
// }
// TODO: Implement validity interval start
// if (this.validityIntervalStart !== null) {
// txBody.set(
// BabbageTransactionBody.ValidityIntervalStart,
// this.validityIntervalStart
// );
// }
if (this.metadata) {
txBody.set(constants_1.BabbageTransactionBody.AuxiliaryDataHash, Buffer.from((0, hexToHash_1.hexToHash)((0, cbor_1.encode)(this.metadata).toString("hex")), "hex"));
}
if (this.plutusV2Scripts.length ||
this.referenceInputs.some((referenceInput) => referenceInput.hasScript)) {
txBody.set(constants_1.BabbageTransactionBody.ScriptDataHash, Buffer.from((0, toScriptDataHash_1.toScriptDataHash)(this.redeemers, this.plutusDatas.length ? this.plutusDatas : "", Plutusv2CostModel_json_1.default.costModel), "hex"));
}
if (this.plutusV1Scripts.length) {
txBody.set(constants_1.BabbageTransactionBody.ScriptDataHash, Buffer.from((0, toScriptDataHash_1.toScriptDataHash)(this.redeemers, this.plutusDatas.length ? this.plutusDatas : "", Plutusv1CostModel_json_1.default.costModel), "hex"));
}
if (this.collateralInputs.length) {
txBody.set(constants_1.BabbageTransactionBody.CollateralInputs, (0, encodeInputs_1.encodeInputs)(this.collateralInputs));
}
if (this.requiredSigners.length) {
txBody.set(constants_1.BabbageTransactionBody.RequiredSigners, this.requiredSigners.sort().map((signer) => Buffer.from(signer, "hex")));
}
if (this.collateralInputs.length) {
const totalCollateral = BigInt(this.totalCollateral.toFixed(0)) || 1000000n;
const expectedCollateralOutput = {
address: "",
value: {
coin: totalCollateral,
},
};
txBody.set(constants_1.BabbageTransactionBody.CollateralReturn, (0, encodeOutputs_1.encodeOutput)({
address: this.collateralInputs[0].address,
value: (0, calculateChange_1.calculateChange)(this.collateralInputs, [
expectedCollateralOutput,
]),
}));
txBody.set(constants_1.BabbageTransactionBody.TotalCollateral, parseInt(totalCollateral.toString()));
}
if (this.referenceInputs.length) {
txBody.set(constants_1.BabbageTransactionBody.ReferenceInputs, (0, encodeInputs_1.encodeInputs)(this.referenceInputs));
}
return txBody;
}
buildWitnessSet() {
const witnessSet = new Map();
if (this.vKeyWitnesses.length) {
witnessSet.set(constants_1.BabbageWitnessSet.VKeyWitness, this.vKeyWitnesses);
}
if (this.plutusDatas.length ||
this.plutusV2Scripts.length ||
this.plutusV1Scripts.length) {
witnessSet.set(constants_1.BabbageWitnessSet.PlutusData, this.plutusDatas.map((plutusData) => (0, tagPlutusData_1.tagPlutusData)(plutusData)));
}
if (this.preBuildRedeemers.length) {
witnessSet.set(constants_1.BabbageWitnessSet.Redeemer, this.preBuildRedeemers.map((redeemer) => [
redeemer[0],
(0, findInputIndex_1.findInputIndex)(this.inputs, redeemer[1]),
(0, tagPlutusData_1.tagPlutusData)(redeemer[2]),
redeemer[3],
]));
}
if (this.plutusV2Scripts.length) {
witnessSet.set(constants_1.BabbageWitnessSet.PlutusV2Script, this.plutusV2Scripts.map((script) => Buffer.from(script, "hex")));
}
if (this.plutusV1Scripts.length) {
witnessSet.set(constants_1.BabbageWitnessSet.PlutusV1Script, this.plutusV1Scripts.map((script) => Buffer.from(script, "hex")));
}
return witnessSet;
}
build(isValid = true) {
return [this.buildBody(), this.buildWitnessSet(), isValid, this.metadata];
}
evaluateRedeemerByTxIn(txIn, exUnits) {
for (const redeemer of this.preBuildRedeemers) {
if ((0, areInputsEqual_1.areInputsEqual)(redeemer[1], txIn)) {
redeemer[3] = exUnits;
}
}
}
evaluateRedeemerByTagIndex(tag, index, exUnits) {
this.inputs = (0, sortInputs_1.sortInputs)(this.inputs);
for (const redeemer of this.preBuildRedeemers) {
const redeemerIndex = this.inputs.findIndex((input) => input.txHash === redeemer[1].txHash &&
input.txIndex === redeemer[1].txIndex);
if (redeemer[0] === tag && redeemerIndex === index) {
redeemer[3] = exUnits;
}
}
}
setRedeemerEvaluations(evaluations) {
for (const evaluation of evaluations) {
this.evaluateRedeemerByTagIndex(evaluation.tag, evaluation.index, [
evaluation.memory,
evaluation.steps,
]);
}
}
serialize() {
return (0, cbor_1.encode)(this.build()).toString("hex");
}
serializeBody() {
return (0, cbor_1.encode)(this.buildBody()).toString("hex");
}
}
exports.TransactionBuilder = TransactionBuilder;