@@ -22,6 +22,7 @@ import {
2222 IWallet ,
2323 KeychainsTriplet ,
2424 KeyIndices ,
25+ MismatchedRecipient ,
2526 MultisigType ,
2627 multisigTypes ,
2728 P2shP2wshUnsupportedError ,
@@ -39,6 +40,7 @@ import {
3940 TransactionParams as BaseTransactionParams ,
4041 TransactionPrebuild as BaseTransactionPrebuild ,
4142 Triple ,
43+ TxIntentMismatchRecipientError ,
4244 UnexpectedAddressError ,
4345 UnsupportedAddressTypeError ,
4446 VerificationOptions ,
@@ -72,6 +74,11 @@ import {
7274 parseTransaction ,
7375 verifyTransaction ,
7476} from './transaction' ;
77+ import {
78+ AggregateValidationError ,
79+ ErrorMissingOutputs ,
80+ ErrorImplicitExternalOutputs ,
81+ } from './transaction/descriptor/verifyTransaction' ;
7582import { assertDescriptorWalletAddress , getDescriptorMapFromWallet , isDescriptorWallet } from './descriptor' ;
7683import { getChainFromNetwork , getFamilyFromNetwork , getFullNameFromNetwork } from './names' ;
7784import { CustomChangeOptions } from './transaction/fixedScript' ;
@@ -113,6 +120,52 @@ const { getExternalChainCode, isChainCode, scriptTypeForChain, outputScripts } =
113120
114121type Unspent < TNumber extends number | bigint = number > = bitgo . Unspent < TNumber > ;
115122
123+ /**
124+ * Convert ValidationError to TxIntentMismatchRecipientError with structured data
125+ *
126+ * This preserves the structured error information from the original ValidationError
127+ * by extracting the mismatched outputs and converting them to the standardized format.
128+ * The original error is preserved as the `cause` for debugging purposes.
129+ */
130+ function convertValidationErrorToTxIntentMismatch (
131+ error : AggregateValidationError ,
132+ reqId : string | IRequestTracer | undefined ,
133+ txParams : BaseTransactionParams ,
134+ txHex : string | undefined
135+ ) : TxIntentMismatchRecipientError {
136+ const mismatchedRecipients : MismatchedRecipient [ ] = [ ] ;
137+
138+ for ( const err of error . errors ) {
139+ if ( err instanceof ErrorMissingOutputs ) {
140+ mismatchedRecipients . push (
141+ ...err . missingOutputs . map ( ( output ) => ( {
142+ address : output . address ,
143+ amount : output . amount . toString ( ) ,
144+ } ) )
145+ ) ;
146+ } else if ( err instanceof ErrorImplicitExternalOutputs ) {
147+ mismatchedRecipients . push (
148+ ...err . implicitExternalOutputs . map ( ( output ) => ( {
149+ address : output . address ,
150+ amount : output . amount . toString ( ) ,
151+ } ) )
152+ ) ;
153+ }
154+ }
155+
156+ const txIntentError = new TxIntentMismatchRecipientError (
157+ error . message ,
158+ reqId ,
159+ [ txParams ] ,
160+ txHex ,
161+ mismatchedRecipients
162+ ) ;
163+ // Preserve the original structured error as the cause for debugging
164+ // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause
165+ ( txIntentError as Error & { cause ?: Error } ) . cause = error ;
166+ return txIntentError ;
167+ }
168+
116169export type DecodedTransaction < TNumber extends number | bigint > =
117170 | utxolib . bitgo . UtxoTransaction < TNumber >
118171 | utxolib . bitgo . UtxoPsbt ;
@@ -631,13 +684,21 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
631684 * @param params.verification.disableNetworking Disallow fetching any data from the internet for verification purposes
632685 * @param params.verification.keychains Pass keychains manually rather than fetching them by id
633686 * @param params.verification.addresses Address details to pass in for out-of-band verification
634- * @returns {boolean }
687+ * @returns {boolean } True if verification passes
635688 * @throws {TxIntentMismatchError } if transaction validation fails
689+ * @throws {TxIntentMismatchRecipientError } if transaction recipients don't match user intent
636690 */
637691 async verifyTransaction < TNumber extends number | bigint = number > (
638692 params : VerifyTransactionOptions < TNumber >
639693 ) : Promise < boolean > {
640- return verifyTransaction ( this , this . bitgo , params ) ;
694+ try {
695+ return await verifyTransaction ( this , this . bitgo , params ) ;
696+ } catch ( error ) {
697+ if ( error instanceof AggregateValidationError ) {
698+ throw convertValidationErrorToTxIntentMismatch ( error , params . reqId , params . txParams , params . txPrebuild . txHex ) ;
699+ }
700+ throw error ;
701+ }
641702 }
642703
643704 /**
0 commit comments