diff --git a/modules/sdk-coin-ada/src/ada.ts b/modules/sdk-coin-ada/src/ada.ts index 265dcb3e1b..4d1621dc6b 100644 --- a/modules/sdk-coin-ada/src/ada.ts +++ b/modules/sdk-coin-ada/src/ada.ts @@ -54,6 +54,7 @@ export interface AdaTxInfo { export interface ExplainTransactionOptions { txPrebuild: TransactionPrebuild; + changeAddress?: string; } export interface AdaParseTransactionOptions extends BaseParseTransactionOptions { @@ -204,16 +205,20 @@ export class Ada extends BaseCoin { */ async explainTransaction(params: ExplainTransactionOptions): Promise { const factory = this.getBuilder(); - let rebuiltTransaction: BaseTransaction; + let rebuiltTransaction: Transaction; const txRaw = params.txPrebuild.txHex; try { const transactionBuilder = factory.from(txRaw); - rebuiltTransaction = await transactionBuilder.build(); + rebuiltTransaction = (await transactionBuilder.build()) as Transaction; } catch { throw new Error('Invalid transaction'); } + if (params.changeAddress) { + rebuiltTransaction.changeAddress = params.changeAddress; + } + return rebuiltTransaction.explainTransaction() as unknown as AdaTransactionExplanation; } diff --git a/modules/sdk-coin-ada/src/lib/transaction.ts b/modules/sdk-coin-ada/src/lib/transaction.ts index 21e8bcd3ac..9fdefdfc0b 100644 --- a/modules/sdk-coin-ada/src/lib/transaction.ts +++ b/modules/sdk-coin-ada/src/lib/transaction.ts @@ -101,6 +101,11 @@ export class Transaction extends BaseTransaction { private _transaction: CardanoWasm.Transaction; private _fee: string; private _pledgeDetails?: PledgeDetails; + private _changeAddress?: string; + + set changeAddress(address: string) { + this._changeAddress = address; + } constructor(coinConfig: Readonly) { super(coinConfig); @@ -426,10 +431,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, 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..09aaa1a60b 100644 --- a/modules/sdk-coin-ada/test/unit/transactionBuilder.ts +++ b/modules/sdk-coin-ada/test/unit/transactionBuilder.ts @@ -549,4 +549,66 @@ describe('ADA Transaction Builder', async () => { // console.log(err); // } // }); + + describe('explainTransaction change output marking', () => { + it('should mark the change output with change: true in explainTransaction', 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 changeAddr = testData.rawTx.outputAddress2.address; + const totalInput = 21032023; + txBuilder.changeAddress(changeAddr, totalInput.toString()); + txBuilder.ttl(800000000); + + const tx = (await txBuilder.build()) as Transaction; + const explained = tx.explainTransaction(); + + const changeOutputs = explained.outputs.filter((o) => o.change === true); + const nonChangeOutputs = explained.outputs.filter((o) => !o.change); + + changeOutputs.length.should.equal(1); + changeOutputs[0].address.should.equal(changeAddr); + nonChangeOutputs.length.should.equal(1); + nonChangeOutputs[0].address.should.equal(testData.rawTx.outputAddress1.address); + }); + + it('should not mark any output as change when no changeAddress is set', async () => { + const preBuiltTx = new Transaction(coins.get('tada')); + preBuiltTx.fromRawTransaction(testData.rawTx.unsignedTx2); + const txBuilder = factory.getTransferBuilder(); + txBuilder.initBuilder(preBuiltTx); + + const tx = (await txBuilder.build()) as Transaction; + const explained = tx.explainTransaction(); + + const changeOutputs = explained.outputs.filter((o) => o.change === true); + changeOutputs.length.should.equal(0); + }); + + it('should mark consolidation output with change: true when changeAddress matches the single output', async () => { + const txBuilder = factory.getTransferBuilder(); + txBuilder.input({ + transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21', + transaction_index: 1, + }); + const changeAddr = testData.rawTx.outputAddress1.address; + const totalInput = 20000000; + txBuilder.changeAddress(changeAddr, totalInput.toString()); + txBuilder.ttl(800000000); + + const tx = (await txBuilder.build()) as Transaction; + const explained = tx.explainTransaction(); + + const changeOutputs = explained.outputs.filter((o) => o.change === true); + changeOutputs.length.should.equal(1); + changeOutputs[0].address.should.equal(changeAddr); + }); + }); });