diff --git a/packages/mesh-common/src/types/transaction-builder/txin.ts b/packages/mesh-common/src/types/transaction-builder/txin.ts index 2e067f6d7..b0e8913e6 100644 --- a/packages/mesh-common/src/types/transaction-builder/txin.ts +++ b/packages/mesh-common/src/types/transaction-builder/txin.ts @@ -2,13 +2,14 @@ import { Asset } from "../asset"; import { DatumSource, Redeemer } from "./data"; import { ScriptSource, SimpleScriptSourceInfo } from "./script"; -export type RefTxIn = { txHash: string; txIndex: number, scriptSize?: number }; +export type RefTxIn = { txHash: string; txIndex: number; scriptSize?: number }; export type TxInParameter = { txHash: string; txIndex: number; amount?: Asset[]; address?: string; + scriptSize?: number; }; export type TxIn = PubKeyTxIn | SimpleScriptTxIn | ScriptTxIn; diff --git a/packages/mesh-transaction/src/mesh-tx-builder/index.ts b/packages/mesh-transaction/src/mesh-tx-builder/index.ts index 76923d9d3..23a483688 100644 --- a/packages/mesh-transaction/src/mesh-tx-builder/index.ts +++ b/packages/mesh-transaction/src/mesh-tx-builder/index.ts @@ -84,39 +84,12 @@ export class MeshTxBuilder extends MeshTxBuilderCore { // Checking if all inputs are complete const { inputs, collaterals, mints } = this.meshTxBuilderBody; - // We must check every input for ref scripts - const incompleteTxIns = [...inputs, ...collaterals]; + const incompleteTxIns = [...inputs, ...collaterals].filter( + (txIn) => !this.isInputComplete(txIn), + ); const incompleteMints = mints.filter((mint) => !this.isMintComplete(mint)); // Getting all missing utxo information await this.queryAllTxInfo(incompleteTxIns, incompleteMints); - // Gather all utxos with ref scripts - Object.values(this.queriedUTxOs).forEach((utxos) => { - for (let utxo of utxos) { - if (utxo.output.scriptRef !== undefined) { - this.utxosWithRefScripts.push(utxo); - } - } - }); - const missingRefInput = this.utxosWithRefScripts.filter((utxo) => { - this.meshTxBuilderBody.referenceInputs.forEach((refInput) => { - if ( - refInput.txHash === utxo.input.txHash && - refInput.txIndex === utxo.input.outputIndex - ) { - return false; - } - }); - return true; - }); - // Add any inputs with ref scripts into reference inputs - // serializer will then deduplicate, but keep the script size for fee calc - missingRefInput.forEach((utxo) => { - this.meshTxBuilderBody.referenceInputs.push({ - txHash: utxo.input.txHash, - txIndex: utxo.input.outputIndex, - scriptSize: utxo.output.scriptRef!.length / 2, - }); - }); // Completing all inputs incompleteTxIns.forEach((txIn) => { this.completeTxInformation(txIn); @@ -131,6 +104,22 @@ export class MeshTxBuilder extends MeshTxBuilderCore { this.completeSimpleScriptInfo(scriptSource); } }); + this.meshTxBuilderBody.inputs.forEach((input) => { + if (input.txIn.scriptSize && input.txIn.scriptSize > 0) { + if ( + this.meshTxBuilderBody.referenceInputs.find((refTxIn) => { + refTxIn.txHash === input.txIn.txHash && + refTxIn.txIndex === input.txIn.txIndex; + }) === undefined + ) { + this.meshTxBuilderBody.referenceInputs.push({ + txHash: input.txIn.txHash, + txIndex: input.txIn.txIndex, + scriptSize: input.txIn.scriptSize, + }); + } + } + }); this.addUtxosFromSelection(); let txHex = this.serializer.serializeTxBody( @@ -285,6 +274,11 @@ export class MeshTxBuilder extends MeshTxBuilderCore { ); input.txIn.address = address; } + if (utxo?.output.scriptRef) { + input.txIn.scriptSize = utxo.output.scriptRef.length / 2; + } else { + input.txIn.scriptSize = 0; + } }; protected completeScriptInfo = (scriptSource: ScriptSource) => { @@ -331,8 +325,8 @@ export class MeshTxBuilder extends MeshTxBuilderCore { }; protected isInputInfoComplete = (txIn: TxIn): boolean => { - const { amount, address } = txIn.txIn; - if (!amount || !address) return false; + const { amount, address, scriptSize } = txIn.txIn; + if (!amount || !address || scriptSize === undefined) return false; return true; }; diff --git a/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts b/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts index 621088d39..10b3a243c 100644 --- a/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts +++ b/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts @@ -73,6 +73,7 @@ export class MeshTxBuilderCore { * @param txIndex The transaction index of the input UTxO * @param amount The asset amount of index of the input UTxO * @param address The address of the input UTxO + * @param scriptSize The size of the ref script at this input (if there isn't one, explicitly put 0 as scriptSize for offline tx building) * @returns The MeshTxBuilder instance */ txIn = ( @@ -80,6 +81,7 @@ export class MeshTxBuilderCore { txIndex: number, amount?: Asset[], address?: string, + scriptSize?: number, ) => { if (this.txInQueueItem) { this.queueInput(); @@ -92,6 +94,7 @@ export class MeshTxBuilderCore { txIndex: txIndex, amount: amount, address: address, + scriptSize: scriptSize, }, }; } else { @@ -102,6 +105,7 @@ export class MeshTxBuilderCore { txIndex: txIndex, amount: amount, address: address, + scriptSize: scriptSize, }, scriptTxIn: {}, }; diff --git a/packages/mesh-transaction/src/transaction/index.ts b/packages/mesh-transaction/src/transaction/index.ts index 33c438c47..5c1977336 100644 --- a/packages/mesh-transaction/src/transaction/index.ts +++ b/packages/mesh-transaction/src/transaction/index.ts @@ -24,9 +24,9 @@ import { import { Cardano, CardanoSDKUtil, - deserializeTx, deserializeNativeScript, deserializePlutusScript, + deserializeTx, fromScriptRef, Serialization, Transaction as Tx, @@ -48,15 +48,14 @@ export class Transaction { this.initiator = options.initiator; } - static attachMetadata( - cborTx: string, - cborTxMetadata: string, - ) { + static attachMetadata(cborTx: string, cborTxMetadata: string) { const tx = deserializeTx(cborTx); const txAuxData = tx.auxiliaryData() ?? new Serialization.AuxiliaryData(); txAuxData.setMetadata( - Serialization.GeneralTransactionMetadata.fromCbor(CardanoSDKUtil.HexBlob(cborTxMetadata)) + Serialization.GeneralTransactionMetadata.fromCbor( + CardanoSDKUtil.HexBlob(cborTxMetadata), + ), ); if ( @@ -64,7 +63,7 @@ export class Transaction { tx.body().auxiliaryDataHash()?.toString() ) { throw new Error( - '[Transaction] attachMetadata: The metadata hash does not match the auxiliary data hash.' + "[Transaction] attachMetadata: The metadata hash does not match the auxiliary data hash.", ); } @@ -81,8 +80,15 @@ export class Transaction { const txMetadata = tx.auxiliaryData()?.metadata(); if (txMetadata !== undefined) { - const mockMetadata = new Map(); - txMetadata.metadata()?.forEach((metadatum, label) => mockMetadata.set(label, mask(metadatum))); + const mockMetadata = new Map< + bigint, + Serialization.TransactionMetadatum + >(); + txMetadata + .metadata() + ?.forEach((metadatum, label) => + mockMetadata.set(label, mask(metadatum)), + ); const txAuxData = tx.auxiliaryData(); txMetadata.setMetadata(mockMetadata); txAuxData?.setMetadata(txMetadata); @@ -94,18 +100,17 @@ export class Transaction { static readMetadata(cborTx: string) { const tx = deserializeTx(cborTx); - return tx.auxiliaryData()?.metadata()?.toCbor().toString() ?? ''; + return tx.auxiliaryData()?.metadata()?.toCbor().toString() ?? ""; } - static writeMetadata( - cborTx: string, - cborTxMetadata: string, - ) { + static writeMetadata(cborTx: string, cborTxMetadata: string) { const tx = deserializeTx(cborTx); const txAuxData = tx.auxiliaryData() ?? new Serialization.AuxiliaryData(); txAuxData.setMetadata( - Serialization.GeneralTransactionMetadata.fromCbor(CardanoSDKUtil.HexBlob(cborTxMetadata)) + Serialization.GeneralTransactionMetadata.fromCbor( + CardanoSDKUtil.HexBlob(cborTxMetadata), + ), ); return new Tx(tx.body(), tx.witnessSet(), txAuxData).toCbor().toString(); @@ -216,6 +221,7 @@ export class Transaction { input.input.outputIndex, input.output.amount, input.output.address, + input.output.scriptRef ? input.output.scriptRef.length / 2 : 0, ); }); @@ -715,13 +721,17 @@ export class Transaction { } } -function mask(metadatum: Serialization.TransactionMetadatum): Serialization.TransactionMetadatum { +function mask( + metadatum: Serialization.TransactionMetadatum, +): Serialization.TransactionMetadatum { switch (metadatum.getKind()) { case Serialization.TransactionMetadatumKind.Text: - return Serialization.TransactionMetadatum.newText("0".repeat(metadatum.asText()?.length ?? 0)); + return Serialization.TransactionMetadatum.newText( + "0".repeat(metadatum.asText()?.length ?? 0), + ); case Serialization.TransactionMetadatumKind.Bytes: case Serialization.TransactionMetadatumKind.Integer: - return metadatum + return metadatum; case Serialization.TransactionMetadatumKind.List: const list = new Serialization.MetadatumList(); for (let i = 0; i < (metadatum.asList()?.getLength() ?? 0); i++) { diff --git a/packages/mesh-transaction/test/mesh-tx-builder/info-completeness-checker.test.ts b/packages/mesh-transaction/test/mesh-tx-builder/info-completeness-checker.test.ts index e34fabbd1..1a54990dc 100644 --- a/packages/mesh-transaction/test/mesh-tx-builder/info-completeness-checker.test.ts +++ b/packages/mesh-transaction/test/mesh-tx-builder/info-completeness-checker.test.ts @@ -41,7 +41,7 @@ describe("MeshTxBuilder", () => { it("should return true for complete PubKey input", () => { const txIn: TxIn = { type: "PubKey", - txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address" }, + txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address", scriptSize: 0 }, }; expect(txBuilder.isInputCompleteExtended(txIn)).toBe(true); }); @@ -89,7 +89,7 @@ describe("MeshTxBuilder", () => { it("should return true for complete Script input", () => { const txIn: TxIn = { type: "Script", - txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address" }, + txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address", scriptSize: 0 }, scriptTxIn: { scriptSource: { type: "Provided", @@ -102,7 +102,7 @@ describe("MeshTxBuilder", () => { it("should return true for complete Script input, with complete script ref", () => { const txIn: TxIn = { type: "Script", - txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address" }, + txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address", scriptSize: 0 }, scriptTxIn: { scriptSource: { type: "Inline", @@ -176,7 +176,7 @@ describe("MeshTxBuilder", () => { it("should return true for complete PubKey input", () => { const txIn: TxIn = { type: "PubKey", - txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address" }, + txIn: { txHash: "hash", txIndex: 0, amount: [], address: "address", scriptSize: 0 }, }; expect(txBuilder.isInputInfoCompleteExtended(txIn)).toBe(true); });