diff --git a/modules/sdk-coin-ada/src/lib/transaction.ts b/modules/sdk-coin-ada/src/lib/transaction.ts index 21e8bcd3ac..6ee96e034b 100644 --- a/modules/sdk-coin-ada/src/lib/transaction.ts +++ b/modules/sdk-coin-ada/src/lib/transaction.ts @@ -101,6 +101,7 @@ export class Transaction extends BaseTransaction { private _transaction: CardanoWasm.Transaction; private _fee: string; private _pledgeDetails?: PledgeDetails; + private _changeAddress?: string; constructor(coinConfig: Readonly) { super(coinConfig); @@ -391,7 +392,7 @@ export class Transaction extends BaseTransaction { /** @inheritdoc */ explainTransaction(): { - outputs: { amount: string; address: string; multiAssets?: Asset[] }[]; + outputs: { amount: string; address: string; multiAssets?: Asset[]; change?: boolean }[]; certificates: Cert[]; changeOutputs: string[]; outputAmount: string; @@ -426,10 +427,12 @@ export class Transaction extends BaseTransaction { id: txJson.id, outputs: txJson.outputs.map((o) => { const multiAssets = Transaction.parseMultiAssets(o.multiAssets as CardanoWasm.MultiAsset | undefined); + const isChange = this._changeAddress !== undefined && o.address === this._changeAddress; return { address: o.address, amount: o.amount, ...(multiAssets && { multiAssets }), + ...(isChange && { change: true }), }; }), outputAmount: outputAmount, @@ -485,6 +488,14 @@ export class Transaction extends BaseTransaction { fee(fee: string) { this._fee = fee; } + + set changeAddress(address: string) { + this._changeAddress = address; + } + + get changeAddress(): string | undefined { + return this._changeAddress; + } } export interface SponsorshipInfo { diff --git a/modules/sdk-coin-ada/src/lib/transactionBuilder.ts b/modules/sdk-coin-ada/src/lib/transactionBuilder.ts index 16edafe0a9..ec363a6292 100644 --- a/modules/sdk-coin-ada/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-ada/src/lib/transactionBuilder.ts @@ -227,6 +227,9 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { this.setMutableSenderAssetList(); this.addOutputs(outputs); this._transaction.transaction = this.prepareAdaTransactionDraft(inputs, outputs, true); + if (this._changeAddress) { + this._transaction.changeAddress = this._changeAddress; + } return this.transaction; } @@ -573,6 +576,9 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { const finalOutputs = this.buildExplicitOutputsCollection(this._fee); this._transaction.transaction = this.prepareAdaTransactionDraft(inputs, finalOutputs, true); + if (this._changeAddress) { + this._transaction.changeAddress = this._changeAddress; + } return this.transaction; } @@ -924,6 +930,9 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { }); witnessSet.set_vkeys(vkeyWitnesses); this._transaction.transaction = CardanoWasm.Transaction.new(txRaw, witnessSet); + if (this._changeAddress) { + this._transaction.changeAddress = this._changeAddress; + } return this.transaction; } diff --git a/modules/sdk-coin-ada/test/unit/transactionBuilder.ts b/modules/sdk-coin-ada/test/unit/transactionBuilder.ts index 8055657bc9..7c722591dc 100644 --- a/modules/sdk-coin-ada/test/unit/transactionBuilder.ts +++ b/modules/sdk-coin-ada/test/unit/transactionBuilder.ts @@ -549,4 +549,71 @@ describe('ADA Transaction Builder', async () => { // console.log(err); // } // }); + + describe('explainTransaction change output marking', () => { + it('should mark the change output with change: true for a shelley send tx', async () => { + const txBuilder = factory.getTransferBuilder(); + txBuilder.input({ + transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21', + transaction_index: 1, + }); + const outputAmount = 7823121; + txBuilder.output({ + address: testData.rawTx.outputAddress1.address, + amount: outputAmount.toString(), + }); + const totalInput = 21032023; + txBuilder.changeAddress(testData.rawTx.outputAddress2.address, totalInput.toString()); + txBuilder.ttl(800000000); + const tx = (await txBuilder.build()) as Transaction; + const explained = tx.explainTransaction(); + explained.outputs.length.should.equal(2); + const recipientOutput = explained.outputs.find((o) => o.address === testData.rawTx.outputAddress1.address); + const changeOutput = explained.outputs.find((o) => o.address === testData.rawTx.outputAddress2.address); + should.exist(recipientOutput); + should.exist(changeOutput); + should.not.exist(recipientOutput!.change); + changeOutput!.change!.should.be.true(); + }); + + it('should mark the change output with change: true for a byron send tx', async () => { + const txBuilder = factory.getTransferBuilder(); + txBuilder.input({ + transaction_id: '1b53331e069a6e58fe77919d30c0cf299d13a2f5b3d9970ce473c1a66d71bf03', + transaction_index: 1, + }); + const outputAmount = 200000000; + txBuilder.output({ + address: testData.rawTxByron.outputAddress1.address, + amount: outputAmount.toString(), + }); + const totalInput = 999600000; + txBuilder.changeAddress(testData.rawTxByron.outputAddress2.address, totalInput.toString()); + txBuilder.ttl(800000000); + const tx = (await txBuilder.build()) as Transaction; + const explained = tx.explainTransaction(); + explained.outputs.length.should.equal(2); + const recipientOutput = explained.outputs.find((o) => o.address === testData.rawTxByron.outputAddress1.address); + const changeOutput = explained.outputs.find((o) => o.address === testData.rawTxByron.outputAddress2.address); + should.exist(recipientOutput); + should.exist(changeOutput); + should.not.exist(recipientOutput!.change); + changeOutput!.change!.should.be.true(); + }); + + it('should not set change on any output when no change address is set (consolidation)', async () => { + const txBuilder = factory.getTransferBuilder(); + txBuilder.input({ + transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21', + transaction_index: 1, + }); + const totalInput = 20000000; + txBuilder.changeAddress(testData.rawTx.outputAddress1.address, totalInput.toString()); + txBuilder.ttl(800000000); + const tx = (await txBuilder.build()) as Transaction; + const explained = tx.explainTransaction(); + explained.outputs.length.should.equal(1); + explained.outputs[0].change!.should.be.true(); + }); + }); });