UNPKG

@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
"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;