@@ -982,6 +982,156 @@ describe('ADA', function () {
982982 res . transactions . length . should . equal ( 2 ) ;
983983 } ) ;
984984
985+ it ( 'should build unsigned consolidation recoveries for WRW (ADA only) and assert build response' , async function ( ) {
986+ const res = await basecoin . recoverConsolidations ( {
987+ bitgoKey : consolidationWrwUser . bitgoKey ,
988+ startingScanIndex : 1 ,
989+ endingScanIndex : 4 ,
990+ } ) ;
991+ res . should . not . be . empty ( ) ;
992+ res . txRequests . length . should . equal ( 2 ) ;
993+
994+ // Assert first consolidation txRequest structure
995+ const txRequest1 = res . txRequests [ 0 ] ;
996+ txRequest1 . transactions . length . should . equal ( 1 ) ;
997+ const unsignedTx1 = txRequest1 . transactions [ 0 ] . unsignedTx ;
998+ unsignedTx1 . should . hasOwnProperty ( 'serializedTx' ) ;
999+ unsignedTx1 . should . hasOwnProperty ( 'parsedTx' ) ;
1000+ unsignedTx1 . should . hasOwnProperty ( 'coinSpecific' ) ;
1001+
1002+ // Deserialize and verify inputs/outputs
1003+ const tx1 = new Transaction ( basecoin ) ;
1004+ tx1 . fromRawTransaction ( unsignedTx1 . serializedTx ) ;
1005+ const txJson1 = tx1 . toJson ( ) ;
1006+ const fee1 = Number ( tx1 . explainTransaction ( ) . fee . fee ) ;
1007+ should . deepEqual ( txJson1 . inputs [ 0 ] . transaction_id , testnetUTXO . UTXO_1 . tx_hash ) ;
1008+ should . deepEqual ( txJson1 . inputs [ 0 ] . transaction_index , testnetUTXO . UTXO_1 . tx_index ) ;
1009+ should . deepEqual ( txJson1 . outputs [ 0 ] . address , baseAddr ) ;
1010+ should . deepEqual ( Number ( txJson1 . outputs [ 0 ] . amount ) + fee1 , testnetUTXO . UTXO_1 . value ) ;
1011+
1012+ // parsedTx inputs/outputs must be present and carry correct address
1013+ const parsedTx1Inputs = unsignedTx1 . parsedTx . inputs ;
1014+ parsedTx1Inputs . length . should . equal ( 1 ) ;
1015+ should . deepEqual ( parsedTx1Inputs [ 0 ] . address , consolidationWrwUser . walletAddress2 ) ;
1016+ const parsedTx1Outputs = unsignedTx1 . parsedTx . outputs ;
1017+ parsedTx1Outputs . length . should . be . greaterThan ( 0 ) ;
1018+ should . deepEqual ( parsedTx1Outputs [ 0 ] . address , baseAddr ) ;
1019+
1020+ // Assert second consolidation txRequest structure
1021+ const txRequest2 = res . txRequests [ 1 ] ;
1022+ const unsignedTx2 = txRequest2 . transactions [ 0 ] . unsignedTx ;
1023+ unsignedTx2 . should . hasOwnProperty ( 'serializedTx' ) ;
1024+
1025+ const tx2 = new Transaction ( basecoin ) ;
1026+ tx2 . fromRawTransaction ( unsignedTx2 . serializedTx ) ;
1027+ const txJson2 = tx2 . toJson ( ) ;
1028+ const fee2 = Number ( tx2 . explainTransaction ( ) . fee . fee ) ;
1029+ should . deepEqual ( txJson2 . inputs [ 0 ] . transaction_id , testnetUTXO . UTXO_2 . tx_hash ) ;
1030+ should . deepEqual ( txJson2 . inputs [ 0 ] . transaction_index , testnetUTXO . UTXO_2 . tx_index ) ;
1031+ should . deepEqual ( txJson2 . outputs [ 0 ] . address , baseAddr ) ;
1032+ should . deepEqual ( Number ( txJson2 . outputs [ 0 ] . amount ) + fee2 , testnetUTXO . UTXO_2 . value ) ;
1033+
1034+ // lastScanIndex must be set in coinSpecific of the last txRequest
1035+ const lastCoinSpecific = unsignedTx2 . coinSpecific ;
1036+ lastCoinSpecific . should . hasOwnProperty ( 'lastScanIndex' ) ;
1037+ lastCoinSpecific . lastScanIndex . should . equal ( 3 ) ;
1038+ } ) ;
1039+
1040+ it ( 'should build unsigned consolidation recoveries for WRW (ADA + Token) and assert build response' , async function ( ) {
1041+ // Override walletAddress2 to return ADA+Token UTXOs
1042+ sandBox . restore ( ) ;
1043+ const callBack2 = sandBox . stub ( Ada . prototype , 'getDataFromNode' as keyof Ada ) ;
1044+ callBack2
1045+ . withArgs ( 'address_info' , { _addresses : [ consolidationWrwUser . walletAddress1 ] } )
1046+ . resolves ( endpointResponses . addressInfoResponse . ZeroUTXO ) ;
1047+ callBack2
1048+ . withArgs ( 'address_info' , { _addresses : [ consolidationWrwUser . walletAddress2 ] } )
1049+ . resolves ( endpointResponses . addressInfoResponse . ADAAndTokenUTXOs ) ;
1050+ callBack2
1051+ . withArgs ( 'address_info' , { _addresses : [ consolidationWrwUser . walletAddress3 ] } )
1052+ . resolves ( endpointResponses . addressInfoResponse . ZeroUTXO ) ;
1053+ callBack2 . withArgs ( 'tip' ) . resolves ( endpointResponses . tipInfoResponse ) ;
1054+
1055+ const tokenPolicyId = '2533cca6eb42076e144e9f2772c390dece9fce173bc38c72294b3924' ;
1056+ const tokenEncodedAssetName = '5741544552' ;
1057+ const tokenQuantity = '111' ;
1058+ const minADAForToken = 1500000 ;
1059+
1060+ const res = await basecoin . recoverConsolidations ( {
1061+ bitgoKey : consolidationWrwUser . bitgoKey ,
1062+ startingScanIndex : 1 ,
1063+ endingScanIndex : 4 ,
1064+ } ) ;
1065+ res . should . not . be . empty ( ) ;
1066+ res . txRequests . length . should . equal ( 1 ) ;
1067+
1068+ const unsignedTx = res . txRequests [ 0 ] . transactions [ 0 ] . unsignedTx ;
1069+ unsignedTx . should . hasOwnProperty ( 'serializedTx' ) ;
1070+
1071+ const tx = new Transaction ( basecoin ) ;
1072+ tx . fromRawTransaction ( unsignedTx . serializedTx ) ;
1073+ const txJson = tx . toJson ( ) ;
1074+
1075+ // Two inputs: ADA UTXO + Token UTXO
1076+ txJson . inputs . length . should . equal ( 2 ) ;
1077+ should . deepEqual ( txJson . inputs [ 0 ] . transaction_id , testnetUTXO . UTXO_1 . tx_hash ) ;
1078+ should . deepEqual ( txJson . inputs [ 1 ] . transaction_id , testnetUTXO . UTXO_TOKEN . tx_hash ) ;
1079+
1080+ // Two outputs: token output (with multiAssets) + pure ADA change to base address
1081+ txJson . outputs . length . should . equal ( 2 ) ;
1082+ const tokenOutput = txJson . outputs . find ( ( o ) => o . multiAssets !== undefined ) ;
1083+ should . exist ( tokenOutput ) ;
1084+ should . deepEqual ( tokenOutput ! . address , baseAddr ) ;
1085+ should . deepEqual ( Number ( tokenOutput ! . amount ) , minADAForToken ) ;
1086+ const expectedPolicyId = CardanoWasm . ScriptHash . from_bytes ( Buffer . from ( tokenPolicyId , 'hex' ) ) ;
1087+ const expectedAssetName = CardanoWasm . AssetName . new ( Buffer . from ( tokenEncodedAssetName , 'hex' ) ) ;
1088+ ( tokenOutput ! . multiAssets as CardanoWasm . MultiAsset )
1089+ . get_asset ( expectedPolicyId , expectedAssetName )
1090+ . to_str ( )
1091+ . should . equal ( tokenQuantity ) ;
1092+
1093+ const adaOutput = txJson . outputs . find ( ( o ) => o . multiAssets === undefined ) ;
1094+ should . exist ( adaOutput ) ;
1095+ should . deepEqual ( adaOutput ! . address , baseAddr ) ;
1096+ const fee = Number ( tx . explainTransaction ( ) . fee . fee ) ;
1097+ const totalBalance = testnetUTXO . UTXO_1 . value + testnetUTXO . UTXO_TOKEN . value ;
1098+ should . deepEqual ( Number ( adaOutput ! . amount ) , totalBalance - minADAForToken - fee ) ;
1099+
1100+ // parsedTx inputs must carry multiAssets for the token UTXO
1101+ const parsedTxInputs = unsignedTx . parsedTx . inputs as {
1102+ address : string ;
1103+ valueString : string ;
1104+ multiAssets ?: { policy_id : string ; asset_name : string ; quantity : string } [ ] ;
1105+ } [ ] ;
1106+ parsedTxInputs . length . should . equal ( 1 ) ;
1107+ should . deepEqual ( parsedTxInputs [ 0 ] . address , consolidationWrwUser . walletAddress2 ) ;
1108+ should . exist ( parsedTxInputs [ 0 ] . multiAssets ) ;
1109+ parsedTxInputs [ 0 ] . multiAssets ! . length . should . equal ( 1 ) ;
1110+ should . deepEqual ( parsedTxInputs [ 0 ] . multiAssets ! [ 0 ] . policy_id , tokenPolicyId ) ;
1111+ should . deepEqual ( parsedTxInputs [ 0 ] . multiAssets ! [ 0 ] . asset_name , tokenEncodedAssetName ) ;
1112+ should . deepEqual ( parsedTxInputs [ 0 ] . multiAssets ! [ 0 ] . quantity , tokenQuantity ) ;
1113+
1114+ // parsedTx outputs must carry multiAssets for the token output
1115+ const parsedTxOutputs = unsignedTx . parsedTx . outputs as {
1116+ address : string ;
1117+ valueString : string ;
1118+ multiAssets ?: { policy_id : string ; asset_name : string ; quantity : string } [ ] ;
1119+ } [ ] ;
1120+ const parsedTokenOutput = parsedTxOutputs . find ( ( o ) => o . multiAssets !== undefined ) ;
1121+ should . exist ( parsedTokenOutput ) ;
1122+ parsedTokenOutput ! . multiAssets ! . length . should . equal ( 1 ) ;
1123+ should . deepEqual ( parsedTokenOutput ! . multiAssets ! [ 0 ] . policy_id , tokenPolicyId ) ;
1124+ should . deepEqual ( parsedTokenOutput ! . multiAssets ! [ 0 ] . asset_name , tokenEncodedAssetName ) ;
1125+ should . deepEqual ( parsedTokenOutput ! . multiAssets ! [ 0 ] . quantity , tokenQuantity ) ;
1126+ // pure ADA output must not have multiAssets
1127+ const parsedAdaOutput = parsedTxOutputs . find ( ( o ) => o . multiAssets === undefined ) ;
1128+ should . exist ( parsedAdaOutput ) ;
1129+
1130+ // lastScanIndex in coinSpecific of the last txRequest
1131+ unsignedTx . coinSpecific . should . hasOwnProperty ( 'lastScanIndex' ) ;
1132+ unsignedTx . coinSpecific . lastScanIndex . should . equal ( 3 ) ;
1133+ } ) ;
1134+
9851135 it ( 'should throw error if all addresses have balance less than 1 ADA' , async function ( ) {
9861136 sandBox . restore ( ) ;
9871137 const callBack = sandBox . stub ( Ada . prototype , 'getDataFromNode' as keyof Ada ) ;
0 commit comments