Skip to content

Commit a8f5e6e

Browse files
committed
fix(sdk-coin-ada): token recovery build
Ticket: CSHLD-133
1 parent ac86ce7 commit a8f5e6e

File tree

3 files changed

+101
-2
lines changed

3 files changed

+101
-2
lines changed

modules/sdk-coin-ada/src/ada.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,32 @@ export class Ada extends BaseCoin {
393393
throw new Error('Did not find address with funds to recover.');
394394
}
395395

396+
// Aggregate token assets from all UTxOs into a fingerprint-keyed map for the builder
397+
const aggregatedAssetList: Record<string, { policy_id: string; asset_name: string; quantity: string }> = {};
398+
for (const utxo of utxoSet) {
399+
for (const asset of utxo.asset_list ?? []) {
400+
const fp = asset.fingerprint as string;
401+
if (aggregatedAssetList[fp]) {
402+
aggregatedAssetList[fp].quantity = (
403+
BigInt(aggregatedAssetList[fp].quantity) + BigInt(asset.quantity)
404+
).toString();
405+
} else {
406+
aggregatedAssetList[fp] = {
407+
policy_id: asset.policy_id,
408+
asset_name: asset.encoded_asset_name ?? asset.asset_name,
409+
quantity: asset.quantity,
410+
};
411+
}
412+
}
413+
}
414+
396415
// first build the unsigned txn
397416
const tipAbsSlot = await this.getChainTipInfo();
398417
const txBuilder = this.getBuilder().getTransferBuilder();
399-
txBuilder.changeAddress(params.recoveryDestination, balance.toString());
418+
txBuilder.changeAddress(params.recoveryDestination, balance.toString(), aggregatedAssetList);
419+
if (Object.keys(aggregatedAssetList).length > 0) {
420+
txBuilder.isTokenTransaction();
421+
}
400422
for (const utxo of utxoSet) {
401423
txBuilder.input({ transaction_id: utxo.tx_hash, transaction_index: utxo.tx_index });
402424
}

modules/sdk-coin-ada/test/resources/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,20 @@ export const testnetUTXO = {
375375
tx_index: 0,
376376
value: 1000000,
377377
},
378+
UTXO_TOKEN: {
379+
tx_hash: 'a824b6a13e2649c7b4ef27277c23f588a67a55618b957d36320a98ac71ed4af5',
380+
tx_index: 0,
381+
value: 5000000,
382+
asset_list: [
383+
{
384+
policy_id: '2533cca6eb42076e144e9f2772c390dece9fce173bc38c72294b3924',
385+
asset_name: 'water',
386+
encoded_asset_name: '5741544552',
387+
quantity: '111',
388+
fingerprint: 'asset1t9uhe7a7lkjrezseduvwvnwwn38hfm3s',
389+
},
390+
],
391+
},
378392
};
379393

380394
const ZeroUTXO = {
@@ -427,12 +441,23 @@ const TwoUTXO = {
427441
],
428442
};
429443

444+
const ADAAndTokenUTXOs = {
445+
status: 200,
446+
body: [
447+
{
448+
balance: testnetUTXO.UTXO_1.value + testnetUTXO.UTXO_TOKEN.value,
449+
utxo_set: [testnetUTXO.UTXO_1, testnetUTXO.UTXO_TOKEN],
450+
},
451+
],
452+
};
453+
430454
const addressInfoResponse = {
431455
ZeroUTXO,
432456
OneUTXO,
433457
OneUTXO2,
434458
TwoUTXO,
435459
OneSmallUTXO,
460+
ADAAndTokenUTXOs,
436461
};
437462

438463
const tipInfoResponse = {

modules/sdk-coin-ada/test/unit/ada.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
wrwUser,
2222
} from '../resources';
2323
import * as _ from 'lodash';
24+
import * as CardanoWasm from '@emurgo/cardano-serialization-lib-nodejs';
2425
import { Ada, KeyPair, Tada } from '../../src';
2526
import { Transaction } from '../../src/lib';
2627
import { TransactionType } from '../../../sdk-core/src/account-lib/baseCoin/enum';
@@ -584,9 +585,10 @@ describe('ADA', function () {
584585
describe('Recover Transactions:', () => {
585586
const destAddr = address.address2;
586587
const sandBox = sinon.createSandbox();
588+
let callBack: sinon.SinonStub;
587589

588590
beforeEach(function () {
589-
const callBack = sandBox.stub(Ada.prototype, 'getDataFromNode' as keyof Ada);
591+
callBack = sandBox.stub(Ada.prototype, 'getDataFromNode' as keyof Ada);
590592
callBack
591593
.withArgs('address_info', {
592594
_addresses: [wrwUser.walletAddress0],
@@ -671,6 +673,56 @@ describe('ADA', function () {
671673
should.deepEqual(txJson.outputs[0].address, destAddr);
672674
should.deepEqual(Number(txJson.outputs[0].amount) + fee, testnetUTXO.UTXO_1.value);
673675
});
676+
677+
it('should recover ADA plus token UTXOs - token and ADA both appear in outputs (signed)', async function () {
678+
callBack
679+
.withArgs('address_info', { _addresses: [wrwUser.walletAddress0] })
680+
.resolves(endpointResponses.addressInfoResponse.ADAAndTokenUTXOs);
681+
682+
const res = await basecoin.recover({
683+
userKey: wrwUser.userKey,
684+
backupKey: wrwUser.backupKey,
685+
bitgoKey: wrwUser.bitgoKey,
686+
walletPassphrase: wrwUser.walletPassphrase,
687+
recoveryDestination: destAddr,
688+
});
689+
res.should.not.be.empty();
690+
res.should.hasOwnProperty('serializedTx');
691+
692+
const tx = new Transaction(basecoin);
693+
tx.fromRawTransaction(res.serializedTx);
694+
const txJson = tx.toJson();
695+
696+
txJson.inputs.length.should.equal(2);
697+
should.deepEqual(txJson.inputs[0].transaction_id, testnetUTXO.UTXO_1.tx_hash);
698+
should.deepEqual(txJson.inputs[1].transaction_id, testnetUTXO.UTXO_TOKEN.tx_hash);
699+
700+
// expect 2 outputs: one token output + one ADA change output, both at destAddr
701+
txJson.outputs.length.should.equal(2);
702+
703+
const tokenPolicyId = '2533cca6eb42076e144e9f2772c390dece9fce173bc38c72294b3924';
704+
const tokenEncodedAssetName = '5741544552';
705+
const tokenQuantity = '111';
706+
const minADAForToken = 1500000;
707+
708+
const tokenOutput = txJson.outputs.find((o) => o.multiAssets !== undefined);
709+
should.exist(tokenOutput);
710+
should.deepEqual(tokenOutput!.address, destAddr);
711+
should.deepEqual(Number(tokenOutput!.amount), minADAForToken);
712+
const expectedPolicyId = CardanoWasm.ScriptHash.from_bytes(Buffer.from(tokenPolicyId, 'hex'));
713+
const expectedAssetName = CardanoWasm.AssetName.new(Buffer.from(tokenEncodedAssetName, 'hex'));
714+
(tokenOutput!.multiAssets as CardanoWasm.MultiAsset)
715+
.get_asset(expectedPolicyId, expectedAssetName)
716+
.to_str()
717+
.should.equal(tokenQuantity);
718+
719+
const adaOutput = txJson.outputs.find((o) => o.multiAssets === undefined);
720+
should.exist(adaOutput);
721+
should.deepEqual(adaOutput!.address, destAddr);
722+
const fee = Number(tx.explainTransaction().fee.fee);
723+
const totalBalance = testnetUTXO.UTXO_1.value + testnetUTXO.UTXO_TOKEN.value;
724+
should.deepEqual(Number(adaOutput!.amount), totalBalance - minADAForToken - fee);
725+
});
674726
});
675727

676728
describe('Recover Transactions Multiple UTXO:', () => {

0 commit comments

Comments
 (0)