diff --git a/packages/mesh-common/src/types/transaction-builder/index.ts b/packages/mesh-common/src/types/transaction-builder/index.ts index b03e62d93..179c825ca 100644 --- a/packages/mesh-common/src/types/transaction-builder/index.ts +++ b/packages/mesh-common/src/types/transaction-builder/index.ts @@ -29,6 +29,7 @@ export type MeshTxBuilderBody = { referenceInputs: RefTxIn[]; mints: MintParam[]; changeAddress: string; + changeAddressOutputIndex?: number; metadata: TxMetadata; scriptMetadata: ScriptMetadata[]; validityRange: ValidityRange; diff --git a/packages/mesh-transaction/src/mesh-tx-builder/index.ts b/packages/mesh-transaction/src/mesh-tx-builder/index.ts index b6c6339f5..bd1c116e5 100644 --- a/packages/mesh-transaction/src/mesh-tx-builder/index.ts +++ b/packages/mesh-transaction/src/mesh-tx-builder/index.ts @@ -313,6 +313,22 @@ export class MeshTxBuilder extends MeshTxBuilderCore { this.meshTxBuilderBody.fee = selectionSkeleton.fee.toString(); this.queueAllLastItem(); + + const changeLen = selectionSkeleton.change.length; + if (this.meshTxBuilderBody.changeAddressOutputIndex !== undefined && changeLen > 0) { + let idx = this.meshTxBuilderBody.changeAddressOutputIndex; + const changes = this.meshTxBuilderBody.outputs.splice(-changeLen, changeLen); + + const maxIdx = this.meshTxBuilderBody.outputs.length; + if (idx > maxIdx) { + idx = maxIdx; + } else if (idx < 0) { + idx = 0; + } + + this.meshTxBuilderBody.outputs.splice(idx, 0, ...changes); + } + this.removeDuplicateInputs(); this.sortTxParts(); this.updateRedeemer( 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 5ba3064ef..1636a3806 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 @@ -1519,10 +1519,14 @@ export class MeshTxBuilderCore { /** * Configure the address to accept change UTxO * @param addr The address to accept change UTxO + * @param outputIndex The index at which the change output should be placed * @returns The MeshTxBuilder instance */ - changeAddress = (addr: string) => { + changeAddress = (addr: string, outputIndex?: number) => { this.meshTxBuilderBody.changeAddress = addr; + if (outputIndex !== undefined) { + this.meshTxBuilderBody.changeAddressOutputIndex = outputIndex; + } return this; }; diff --git a/packages/mesh-transaction/test/mesh-tx-builder/tx.test.ts b/packages/mesh-transaction/test/mesh-tx-builder/tx.test.ts index f8f84bbf5..b515a84fa 100644 --- a/packages/mesh-transaction/test/mesh-tx-builder/tx.test.ts +++ b/packages/mesh-transaction/test/mesh-tx-builder/tx.test.ts @@ -36,6 +36,45 @@ describe("MeshTxBuilder transactions", () => { expect(txHex !== "").toBeTruthy(); }); + it("Basic send tx with change output index", async () => { + let mesh = new MeshTxBuilder(); + let txHex = await mesh + .txIn( + "2cb57168ee66b68bd04a0d595060b546edf30c04ae1031b883c9ac797967dd85", + 3, + [{ unit: "lovelace", quantity: "9891607895" }], + "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", + 0 + ) + .txOut( + "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", + [{ unit: "lovelace", quantity: "2000000" }], + ) + .txOut( + "addr_test1vru4e2un2tq50q4rv6qzk7t8w34gjdtw3y2uzuqxzj0ldrqqactxh", + [{ unit: "lovelace", quantity: "3000000" }], + ) + .changeAddress( + "addr_test1wql6cyymfrmqe9cjeyfh5d4h945nfszy3yup8d74kkrhsks4dkk0y", + 1 + ) + .complete(); + + const cardanoTx = Serialization.Transaction.fromCbor( + Serialization.TxCBOR(txHex), + ); + const outputs = cardanoTx.body().outputs(); + // 3 outputs expected: 2 explicit, 1 change + expect(outputs.length).toBe(3); + + // The change output should be at index 1 (between the two explicit outputs) + const changeOutput = outputs[1]; + expect(changeOutput!.address().toBech32()).toBe("addr_test1wql6cyymfrmqe9cjeyfh5d4h945nfszy3yup8d74kkrhsks4dkk0y"); + // Verify explicit outputs are pushed to indices 0 and 2 + expect(outputs[0]!.amount().coin().toString()).toBe("2000000"); + expect(outputs[2]!.amount().coin().toString()).toBe("3000000"); + }); + it("Basic send tx with set fee", () => { let mesh = new MeshTxBuilder(); let txHex = mesh