Skip to content

Commit 5765348

Browse files
authored
fix(sdk-coin-ada): allowing fee address for sponsored tokens
2 parents 5727538 + d22bc1c commit 5765348

2 files changed

Lines changed: 137 additions & 3 deletions

File tree

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Ada } from './ada';
1+
import { Ada, AdaTxInfo } from './ada';
22
import {
33
BitGoBase,
44
CoinConstructor,
@@ -145,11 +145,12 @@ export class AdaToken extends Ada {
145145
}
146146
}
147147
} else if (verification?.consolidationToBaseAddress) {
148-
// For token consolidation, verify all outputs go to the base address
148+
// For token consolidation, verify all outputs go to the base address or fee address (sponsored consolidations)
149149
const baseAddress = wallet?.coinSpecific()?.baseAddress || wallet?.coinSpecific()?.rootAddress;
150+
const feeAddress = (txPrebuild.txInfo as AdaTxInfo)?.feeAddress;
150151

151152
for (const output of txJson.outputs) {
152-
if (output.address !== baseAddress) {
153+
if (output.address !== baseAddress && output.address !== feeAddress) {
153154
throw new Error('tx outputs does not match with expected address');
154155
}
155156
}

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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,5 +926,138 @@ describe('ADA Token Operations', async () => {
926926
})
927927
.should.be.rejectedWith('tx outputs does not match with expected address');
928928
});
929+
930+
it('should verify sponsored token consolidation when fee address output is present', async () => {
931+
const feeAddress =
932+
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp';
933+
const quantity = '100';
934+
const senderInputBalance = 5000000;
935+
const feeAddressInputBalance = 20000000;
936+
const totalAssetList = {
937+
[fingerprint]: {
938+
quantity: '100',
939+
policy_id: policyId,
940+
asset_name: asciiEncodedName,
941+
},
942+
};
943+
944+
// Build a sponsored token consolidation: tokens go back to senderAddress, fee sponsor gets ADA change
945+
const txBuilder = factory.getTransferBuilder();
946+
txBuilder.input({
947+
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21',
948+
transaction_index: 1,
949+
});
950+
txBuilder.input({
951+
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba22',
952+
transaction_index: 0,
953+
});
954+
955+
// Consolidation output: tokens go back to the sender's base address
956+
txBuilder.output({
957+
address: senderAddress,
958+
amount: '0',
959+
multiAssets: {
960+
asset_name: asciiEncodedName,
961+
policy_id: policyId,
962+
quantity,
963+
fingerprint,
964+
},
965+
});
966+
967+
txBuilder.changeAddress(senderAddress, senderInputBalance.toString(), totalAssetList);
968+
txBuilder.sponsorshipInfo({
969+
feeAddress: feeAddress,
970+
feeAddressInputBalance: feeAddressInputBalance.toString(),
971+
});
972+
txBuilder.ttl(800000000);
973+
txBuilder.isTokenTransaction();
974+
const tx = (await txBuilder.build()) as Transaction;
975+
const txHex = tx.toBroadcastFormat();
976+
977+
const mockWallet = {
978+
coinSpecific: () => ({
979+
baseAddress: senderAddress,
980+
}),
981+
};
982+
983+
const txParams = { recipients: undefined };
984+
const txPrebuild = { txHex, txInfo: { feeAddress } };
985+
const verification = { consolidationToBaseAddress: true };
986+
987+
const isVerified = await adaToken.verifyTransaction({
988+
txParams,
989+
txPrebuild,
990+
verification,
991+
wallet: mockWallet as any,
992+
});
993+
isVerified.should.equal(true);
994+
});
995+
996+
it('should fail sponsored token consolidation when output goes to unexpected third-party address', async () => {
997+
const feeAddress =
998+
'addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp';
999+
const quantity = '100';
1000+
const senderInputBalance = 5000000;
1001+
const feeAddressInputBalance = 20000000;
1002+
const totalAssetList = {
1003+
[fingerprint]: {
1004+
quantity: '100',
1005+
policy_id: policyId,
1006+
asset_name: asciiEncodedName,
1007+
},
1008+
};
1009+
1010+
// Build transaction where token output goes to an unexpected third-party address
1011+
const txBuilder = factory.getTransferBuilder();
1012+
txBuilder.input({
1013+
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba21',
1014+
transaction_index: 1,
1015+
});
1016+
txBuilder.input({
1017+
transaction_id: '3677e75c7ba699bfdc6cd57d42f246f86f63aefd76025006ac78313fad2bba22',
1018+
transaction_index: 0,
1019+
});
1020+
1021+
// Output goes to receiverAddress (third party), not the base address
1022+
txBuilder.output({
1023+
address: receiverAddress,
1024+
amount: '0',
1025+
multiAssets: {
1026+
asset_name: asciiEncodedName,
1027+
policy_id: policyId,
1028+
quantity,
1029+
fingerprint,
1030+
},
1031+
});
1032+
1033+
txBuilder.changeAddress(senderAddress, senderInputBalance.toString(), totalAssetList);
1034+
txBuilder.sponsorshipInfo({
1035+
feeAddress: feeAddress,
1036+
feeAddressInputBalance: feeAddressInputBalance.toString(),
1037+
});
1038+
txBuilder.ttl(800000000);
1039+
txBuilder.isTokenTransaction();
1040+
const tx = (await txBuilder.build()) as Transaction;
1041+
const txHex = tx.toBroadcastFormat();
1042+
1043+
const mockWallet = {
1044+
coinSpecific: () => ({
1045+
baseAddress: senderAddress,
1046+
}),
1047+
};
1048+
1049+
const txParams = { recipients: undefined };
1050+
const txPrebuild = { txHex, txInfo: { feeAddress } };
1051+
const verification = { consolidationToBaseAddress: true };
1052+
1053+
await adaToken
1054+
.verifyTransaction({
1055+
txParams,
1056+
txPrebuild,
1057+
verification,
1058+
wallet: mockWallet as any,
1059+
})
1060+
.should.be.rejectedWith('tx outputs does not match with expected address');
1061+
});
9291062
});
9301063
});

0 commit comments

Comments
 (0)