From 23213173bfc6954178368a629fd9223b7f11a284 Mon Sep 17 00:00:00 2001 From: Dhanush GM Date: Thu, 5 Mar 2026 13:24:21 +0530 Subject: [PATCH] fix(sdk-coin-ada): providing unsigned sweep support for WRW Ticket: CSHLD-134 --- modules/sdk-coin-ada/src/ada.ts | 19 ++++--- modules/sdk-coin-ada/src/lib/transaction.ts | 49 +++++++++++++++++- modules/sdk-coin-ada/test/unit/ada.ts | 55 +++++++++++++++++++++ 3 files changed, 114 insertions(+), 9 deletions(-) diff --git a/modules/sdk-coin-ada/src/ada.ts b/modules/sdk-coin-ada/src/ada.ts index 31ad91e11a..0239121b86 100644 --- a/modules/sdk-coin-ada/src/ada.ts +++ b/modules/sdk-coin-ada/src/ada.ts @@ -507,13 +507,18 @@ export class Ada extends BaseCoin { value: new BigNumber(output.amount).toNumber(), }, ]; - const outputs = [ - { - address: output.address, - valueString: output.amount, - coinName: walletCoin, - }, - ]; + const outputs = ( + parsedTx.outputs as { + amount: string; + address: string; + tokenTransfers?: { policyId: string; assetName: string; quantity: string }[]; + }[] + ).map((o) => ({ + address: o.address, + valueString: o.amount, + coinName: walletCoin, + ...(o.tokenTransfers && { tokenTransfers: o.tokenTransfers }), + })); const spendAmount = output.amount; const completedParsedTx = { inputs: inputs, outputs: outputs, spendAmount: spendAmount, type: '' }; const fee = new BigNumber((parsedTx.fee as { fee: string }).fee); diff --git a/modules/sdk-coin-ada/src/lib/transaction.ts b/modules/sdk-coin-ada/src/lib/transaction.ts index 1fa4cb90ea..b859f0a8ca 100644 --- a/modules/sdk-coin-ada/src/lib/transaction.ts +++ b/modules/sdk-coin-ada/src/lib/transaction.ts @@ -87,6 +87,43 @@ export interface TxData { pledgeDetails?: PledgeDetails; } +function extractTokenTransfers( + multiAssets: CardanoWasm.MultiAsset | Asset | undefined +): Array<{ policyId: string; assetName: string; quantity: string }> | undefined { + if (!multiAssets) return undefined; + + if (!(multiAssets instanceof CardanoWasm.MultiAsset)) { + return [ + { + policyId: multiAssets.policy_id, + assetName: + (multiAssets as Asset & { encoded_asset_name?: string }).encoded_asset_name ?? multiAssets.asset_name, + quantity: multiAssets.quantity, + }, + ]; + } + + const transfers: Array<{ policyId: string; assetName: string; quantity: string }> = []; + const scriptHashes = multiAssets.keys(); + for (let i = 0; i < scriptHashes.len(); i++) { + const scriptHash = scriptHashes.get(i); + const assets = multiAssets.get(scriptHash); + if (!assets) continue; + const assetNames = assets.keys(); + for (let j = 0; j < assetNames.len(); j++) { + const assetName = assetNames.get(j); + const quantity = assets.get(assetName); + if (!quantity) continue; + transfers.push({ + policyId: Buffer.from(scriptHash.to_bytes()).toString('hex'), + assetName: Buffer.from(assetName.name()).toString('hex'), + quantity: quantity.to_str(), + }); + } + } + return transfers.length > 0 ? transfers : undefined; +} + export class Transaction extends BaseTransaction { private _transaction: CardanoWasm.Transaction; private _fee: string; @@ -381,7 +418,11 @@ export class Transaction extends BaseTransaction { /** @inheritdoc */ explainTransaction(): { - outputs: { amount: string; address: string }[]; + outputs: { + amount: string; + address: string; + tokenTransfers?: Array<{ policyId: string; assetName: string; quantity: string }>; + }[]; certificates: Cert[]; changeOutputs: string[]; outputAmount: string; @@ -414,7 +455,11 @@ export class Transaction extends BaseTransaction { return { displayOrder, id: txJson.id, - outputs: txJson.outputs.map((o) => ({ address: o.address, amount: o.amount })), + outputs: txJson.outputs.map((o) => ({ + address: o.address, + amount: o.amount, + ...(extractTokenTransfers(o.multiAssets) && { tokenTransfers: extractTokenTransfers(o.multiAssets) }), + })), outputAmount: outputAmount, changeOutputs: [], changeAmount: '0', diff --git a/modules/sdk-coin-ada/test/unit/ada.ts b/modules/sdk-coin-ada/test/unit/ada.ts index fd324cbab0..055856b5f7 100644 --- a/modules/sdk-coin-ada/test/unit/ada.ts +++ b/modules/sdk-coin-ada/test/unit/ada.ts @@ -674,6 +674,61 @@ describe('ADA', function () { should.deepEqual(Number(txJson.outputs[0].amount) + fee, testnetUTXO.UTXO_1.value); }); + it('should recover ADA plus token UTXOs - token and ADA both appear in outputs (unsigned sweep)', async function () { + callBack + .withArgs('address_info', { _addresses: [wrwUser.walletAddress0] }) + .resolves(endpointResponses.addressInfoResponse.ADAAndTokenUTXOs); + + const res = await basecoin.recover({ + bitgoKey: wrwUser.bitgoKey, + recoveryDestination: destAddr, + }); + res.should.not.be.empty(); + const unsignedTx = res.txRequests[0].transactions[0].unsignedTx; + unsignedTx.should.hasOwnProperty('serializedTx'); + + const tx = new Transaction(basecoin); + tx.fromRawTransaction(unsignedTx.serializedTx); + const txJson = tx.toJson(); + + txJson.inputs.length.should.equal(2); + should.deepEqual(txJson.inputs[0].transaction_id, testnetUTXO.UTXO_1.tx_hash); + should.deepEqual(txJson.inputs[1].transaction_id, testnetUTXO.UTXO_TOKEN.tx_hash); + + txJson.outputs.length.should.equal(2); + + const tokenPolicyId = '2533cca6eb42076e144e9f2772c390dece9fce173bc38c72294b3924'; + const tokenEncodedAssetName = '5741544552'; + const tokenQuantity = '111'; + const minADAForToken = 1500000; + + const tokenOutput = txJson.outputs.find((o) => o.multiAssets !== undefined); + should.exist(tokenOutput); + should.deepEqual(tokenOutput!.address, destAddr); + should.deepEqual(Number(tokenOutput!.amount), minADAForToken); + const expectedPolicyId = CardanoWasm.ScriptHash.from_bytes(Buffer.from(tokenPolicyId, 'hex')); + const expectedAssetName = CardanoWasm.AssetName.new(Buffer.from(tokenEncodedAssetName, 'hex')); + (tokenOutput!.multiAssets as CardanoWasm.MultiAsset) + .get_asset(expectedPolicyId, expectedAssetName) + .to_str() + .should.equal(tokenQuantity); + + const adaOutput = txJson.outputs.find((o) => o.multiAssets === undefined); + should.exist(adaOutput); + should.deepEqual(adaOutput!.address, destAddr); + const fee = Number(tx.explainTransaction().fee.fee); + const totalBalance = testnetUTXO.UTXO_1.value + testnetUTXO.UTXO_TOKEN.value; + should.deepEqual(Number(adaOutput!.amount), totalBalance - minADAForToken - fee); + + const explained = tx.explainTransaction(); + const explainedTokenOutput = explained.outputs.find((o) => o.tokenTransfers !== undefined); + should.exist(explainedTokenOutput); + explainedTokenOutput!.tokenTransfers!.length.should.equal(1); + should.deepEqual(explainedTokenOutput!.tokenTransfers![0].policyId, tokenPolicyId); + should.deepEqual(explainedTokenOutput!.tokenTransfers![0].assetName, tokenEncodedAssetName); + should.deepEqual(explainedTokenOutput!.tokenTransfers![0].quantity, tokenQuantity); + }); + it('should recover ADA plus token UTXOs - token and ADA both appear in outputs (signed)', async function () { callBack .withArgs('address_info', { _addresses: [wrwUser.walletAddress0] })