import { Multiasset } from "../utils/assets"; import { Cbor, CborMetadata } from "../utils/cbor"; import { CborAddress } from "../utils/cbor/address"; import { CborArray } from "../utils/cbor/array"; import { MetadataMap } from "../utils/cbor/metadata"; import { CborScriptData } from "../utils/cbor/scriptData"; import { CborTransaction } from "../utils/cbor/transaction"; import { HashUtils } from "../utils/hash"; import { ScriptData, ScriptDataUtils } from "../utils/scriptData"; import { CardanoUTXO, RequiredInputs, RequiredOutputs, UTXOS, } from "../utils/utxos"; export type TransactionOutput = { address: string; amount: number | [number, Multiasset]; datum?: ScriptData; }; type TBody = { inputsUtxo: CardanoUTXO[]; requiredInputs: RequiredInputs; requiredOutputs: RequiredOutputs; auxiliaryDataHash?: string; collateral?: CardanoUTXO[]; scriptHash?: string; requiredSigners?: string; }; type FeeOptions = { minFeeA: number; minFeeB: number; priceStep: number; priceMem: number; scriptMemUnits: number; scriptCpuUnits: number; executionTimes?: number; }; type TWitness = { vkeywitness?: string; script?: string; plutusDatas?: ScriptData[]; redeemers?: string[]; }; const buildTransactionWitness = ({ plutusDatas, script, vkeywitness, redeemers, }: TWitness) => { const mapSize = (script ? 1 : 0) + (redeemers ? 1 : 0) + (vkeywitness ? 1 : 0) + (plutusDatas ? 1 : 0); const mapValues = [ vkeywitness ? `00${vkeywitness}` : "", script ? `0381${script}` : "", // TODO: Support multiple plutus scripts plutusDatas ? `04${CborArray.sizeToCbor(plutusDatas.length)}${plutusDatas.reduce( (acc, d) => acc + CborScriptData.encode(d).encoded, "" )}` : "", redeemers ? `05${CborArray.sizeToCbor(redeemers.length)}${redeemers.join("")}` : "", ]; return `a${mapSize}${mapValues.join("")}`; }; const buildTransaction = ( txBody: string, txWitnessSet: string, metadata?: string ) => { return `84${txBody}${txWitnessSet}f5${metadata || "f6"}`; }; const buildTransactionOutputs = (outputs: TransactionOutput[]) => { const buildOutput = (output: TransactionOutput) => { const datumHash = output.datum ? ScriptDataUtils.toHash(output.datum) : ""; const encodedAddress = CborAddress.encode(output.address); const encodedValue = Cbor.encodeValue(output.amount); return `8${ datumHash ? "3" : "2" }${encodedAddress}${encodedValue}${datumHash}`; }; const outputLength = CborArray.sizeToCbor(outputs.length); const outputBody = outputs.map(buildOutput).join(""); return `${outputLength}${outputBody}`; }; const buildTransactionInputs = (inputsUtxo: CardanoUTXO[]) => { const inputs = inputsUtxo.map( (u) => CborTransaction.encodeTransaction(u.transaction).encoded ); return `${CborArray.sizeToCbor(inputs.length)}${inputs.join("")}`; }; const buildRedeemer = ( tag: string, index: string, plutusData: ScriptData, exUnits: string = Cbor.getExUnits(5000000, 4000000000) ) => { return `84${tag}${index}${ CborScriptData.encode(plutusData).encoded }${exUnits}`; }; const buildTransactionBody = async (tbody: TBody, fee?: number) => { const outputs = UTXOS.calculateOutputs( tbody.inputsUtxo, tbody.requiredInputs, tbody.requiredOutputs ); const transactionInputs = TransactionBuilder.buildTransactionInputs( tbody.inputsUtxo ); const transactionOutputs = TransactionBuilder.buildTransactionOutputs(outputs); const body = CborTransaction.encodeTransactionBody( transactionInputs, transactionOutputs, fee || 1000000, tbody.requiredSigners, tbody.scriptHash, tbody.collateral, tbody.auxiliaryDataHash ); return body; }; const calculateFee = async ( tbody: TBody, twitness: TWitness, { minFeeA, minFeeB, priceStep, priceMem, scriptMemUnits = 5000000, scriptCpuUnits = 4000000000, executionTimes = 1, }: FeeOptions, metadataHash?: string ) => { const body = await TransactionBuilder.buildTransactionBody(tbody); const witness = await TransactionBuilder.buildTransactionWitness(twitness); // Total length consists on: Transaction Array (2) + Body Length + Witness Length + Is Valid (2) + Metadata hash or Null (2) const totalLength = 2 + body.length + 2 + witness.length + (metadataHash?.length || 2) + 138; // Not sure where this is 138 is coming from but is the usual diff return ( minFeeA * totalLength + minFeeB + (twitness.script ? 300000 + (priceMem * scriptMemUnits + priceStep * scriptCpuUnits) * executionTimes : 0) ); }; const buildScriptHash = ( encodedRedeemers: string[], plutusDatas: ScriptData[], encodedCostModel: string ) => { return HashUtils.hexToHash( `${CborArray.sizeToCbor(encodedRedeemers.length)}${encodedRedeemers.join( "" )}` + `${CborArray.sizeToCbor(plutusDatas.length)}${plutusDatas .map((p) => CborScriptData.encode(p).encoded) .join("")}` + encodedCostModel ); }; const buildAuxiliaryDataHash = (map: MetadataMap) => { return HashUtils.hexToHash(CborMetadata.encode(map)); }; export const TransactionBuilder = { buildTransaction, buildTransactionBody, buildTransactionInputs, buildTransactionOutputs, buildTransactionWitness, buildAuxiliaryDataHash, buildRedeemer, buildScriptHash, calculateFee, };