Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions modules/sdk-coin-ada/src/adaToken.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Ada } from './ada';
import { Ada, AdaTxInfo } from './ada';
import {
BitGoBase,
CoinConstructor,
Expand Down Expand Up @@ -145,11 +145,12 @@ export class AdaToken extends Ada {
}
}
} else if (verification?.consolidationToBaseAddress) {
// For token consolidation, verify all outputs go to the base address
// For token consolidation, verify all outputs go to the base address or fee address (sponsored consolidations)
const baseAddress = wallet?.coinSpecific()?.baseAddress || wallet?.coinSpecific()?.rootAddress;
const feeAddress = (txPrebuild.txInfo as AdaTxInfo)?.feeAddress;

for (const output of txJson.outputs) {
if (output.address !== baseAddress) {
if (output.address !== baseAddress && output.address !== feeAddress) {
throw new Error('tx outputs does not match with expected address');
}
}
Expand Down
133 changes: 133 additions & 0 deletions modules/sdk-coin-ada/test/unit/tokenWithdrawal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -926,5 +926,138 @@ describe('ADA Token Operations', async () => {
})
.should.be.rejectedWith('tx outputs does not match with expected address');
});

it('should verify sponsored token consolidation when fee address output is present', async () => {
const feeAddress =
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp';
const quantity = '100';
const senderInputBalance = 5000000;
const feeAddressInputBalance = 20000000;
const totalAssetList = {
[fingerprint]: {
quantity: '100',
policy_id: policyId,
asset_name: asciiEncodedName,
},
};

// Build a sponsored token consolidation: tokens go back to senderAddress, fee sponsor gets ADA change
const txBuilder = factory.getTransferBuilder();
txBuilder.input({
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21',
transaction_index: 1,
});
txBuilder.input({
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba22',
transaction_index: 0,
});

// Consolidation output: tokens go back to the sender's base address
txBuilder.output({
address: senderAddress,
amount: '0',
multiAssets: {
asset_name: asciiEncodedName,
policy_id: policyId,
quantity,
fingerprint,
},
});

txBuilder.changeAddress(senderAddress, senderInputBalance.toString(), totalAssetList);
txBuilder.sponsorshipInfo({
feeAddress: feeAddress,
feeAddressInputBalance: feeAddressInputBalance.toString(),
});
txBuilder.ttl(800000000);
txBuilder.isTokenTransaction();
const tx = (await txBuilder.build()) as Transaction;
const txHex = tx.toBroadcastFormat();

const mockWallet = {
coinSpecific: () => ({
baseAddress: senderAddress,
}),
};

const txParams = { recipients: undefined };
const txPrebuild = { txHex, txInfo: { feeAddress } };
const verification = { consolidationToBaseAddress: true };

const isVerified = await adaToken.verifyTransaction({
txParams,
txPrebuild,
verification,
wallet: mockWallet as any,
});
isVerified.should.equal(true);
});

it('should fail sponsored token consolidation when output goes to unexpected third-party address', async () => {
const feeAddress =
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp';
const quantity = '100';
const senderInputBalance = 5000000;
const feeAddressInputBalance = 20000000;
const totalAssetList = {
[fingerprint]: {
quantity: '100',
policy_id: policyId,
asset_name: asciiEncodedName,
},
};

// Build transaction where token output goes to an unexpected third-party address
const txBuilder = factory.getTransferBuilder();
txBuilder.input({
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21',
transaction_index: 1,
});
txBuilder.input({
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba22',
transaction_index: 0,
});

// Output goes to receiverAddress (third party), not the base address
txBuilder.output({
address: receiverAddress,
amount: '0',
multiAssets: {
asset_name: asciiEncodedName,
policy_id: policyId,
quantity,
fingerprint,
},
});

txBuilder.changeAddress(senderAddress, senderInputBalance.toString(), totalAssetList);
txBuilder.sponsorshipInfo({
feeAddress: feeAddress,
feeAddressInputBalance: feeAddressInputBalance.toString(),
});
txBuilder.ttl(800000000);
txBuilder.isTokenTransaction();
const tx = (await txBuilder.build()) as Transaction;
const txHex = tx.toBroadcastFormat();

const mockWallet = {
coinSpecific: () => ({
baseAddress: senderAddress,
}),
};

const txParams = { recipients: undefined };
const txPrebuild = { txHex, txInfo: { feeAddress } };
const verification = { consolidationToBaseAddress: true };

await adaToken
.verifyTransaction({
txParams,
txPrebuild,
verification,
wallet: mockWallet as any,
})
.should.be.rejectedWith('tx outputs does not match with expected address');
});
});
});
Loading