@@ -76,7 +76,15 @@ import ScriptType2Of3 = utxolib.bitgo.outputScripts.ScriptType2Of3;
7676import { isReplayProtectionUnspent } from './replayProtection' ;
7777import { signAndVerifyPsbt , signAndVerifyWalletTransaction } from './sign' ;
7878import { supportedCrossChainRecoveries } from './config' ;
79- import { explainPsbt , explainTx , getPsbtTxInputs , getTxInputs } from './transaction' ;
79+ import {
80+ assertValidTransactionRecipient ,
81+ explainPsbt ,
82+ explainTx ,
83+ fromExtendedAddressFormat ,
84+ getPsbtTxInputs ,
85+ getTxInputs ,
86+ isExtendedAddressFormat ,
87+ } from './transaction' ;
8088import { assertDescriptorWalletAddress } from './descriptor/assertDescriptorWalletAddress' ;
8189
8290type UtxoCustomSigningFunction < TNumber extends number | bigint > = {
@@ -447,6 +455,20 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
447455 }
448456 }
449457
458+ preprocessBuildParams ( params : Record < string , any > ) : Record < string , any > {
459+ if ( params . recipients !== undefined ) {
460+ params . recipients =
461+ params . recipients instanceof Array
462+ ? params ?. recipients ?. map ( ( recipient ) => {
463+ const { address, ...rest } = recipient ;
464+ return { ...rest , ...fromExtendedAddressFormat ( address ) } ;
465+ } )
466+ : params . recipients ;
467+ }
468+
469+ return params ;
470+ }
471+
450472 /**
451473 * Get the latest block height
452474 * @param reqId
@@ -459,6 +481,13 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
459481 return ( chainhead as any ) . height ;
460482 }
461483
484+ checkRecipient ( recipient : { address : string ; amount : number | string } ) : void {
485+ assertValidTransactionRecipient ( recipient ) ;
486+ if ( ! isExtendedAddressFormat ( recipient . address ) ) {
487+ super . checkRecipient ( recipient ) ;
488+ }
489+ }
490+
462491 /**
463492 * Run custom coin logic after a transaction prebuild has been received from BitGo
464493 * @param prebuild
@@ -511,6 +540,18 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
511540 return utxolib . bitgo . createTransactionFromHex < TNumber > ( hex , this . network , this . amountType ) ;
512541 }
513542
543+ toCanonicalTransactionRecipient ( output : { valueString : string ; address ?: string } ) : {
544+ amount : bigint ;
545+ address ?: string ;
546+ } {
547+ const amount = BigInt ( output . valueString ) ;
548+ assertValidTransactionRecipient ( { amount, address : output . address } ) ;
549+ if ( ! output . address ) {
550+ return { amount } ;
551+ }
552+ return { amount, address : this . canonicalAddress ( output . address ) } ;
553+ }
554+
514555 /**
515556 * Extract and fill transaction details such as internal/change spend, external spend (explicit vs. implicit), etc.
516557 * @param params
@@ -567,25 +608,39 @@ export abstract class AbstractUtxoCoin extends BaseCoin {
567608 assert ( txParams . rbfTxIds . length === 1 ) ;
568609
569610 const txToBeReplaced = await wallet . getTransaction ( { txHash : txParams . rbfTxIds [ 0 ] , includeRbf : true } ) ;
570- expectedOutputs = txToBeReplaced . outputs
571- . filter ( ( output ) => output . wallet !== wallet . id ( ) ) // For self-sends, the walletId will be the same as the wallet's id
572- . map ( ( output ) => {
573- return { amount : BigInt ( output . valueString ) , address : this . canonicalAddress ( output . address ) } ;
574- } ) ;
611+ expectedOutputs = txToBeReplaced . outputs . flatMap (
612+ ( output : { valueString : string ; address ?: string ; wallet ?: string } ) => {
613+ // For self-sends, the walletId will be the same as the wallet's id
614+ if ( output . wallet === wallet . id ( ) ) {
615+ return [ ] ;
616+ }
617+ return [ this . toCanonicalTransactionRecipient ( output ) ] ;
618+ }
619+ ) ;
575620 } else {
576621 // verify that each recipient from txParams has their own output
577- expectedOutputs = _ . get ( txParams , 'recipients' , [ ] as TransactionRecipient [ ] ) . map ( ( output ) => {
578- return { ...output , address : this . canonicalAddress ( output . address ) } ;
622+ expectedOutputs = _ . get ( txParams , 'recipients' , [ ] as TransactionRecipient [ ] ) . flatMap ( ( output ) => {
623+ if ( output . address === undefined ) {
624+ if ( output . amount . toString ( ) !== '0' ) {
625+ throw new Error ( `Only zero amounts allowed for non-encodeable scriptPubkeys: ${ output } ` ) ;
626+ }
627+ return [ output ] ;
628+ }
629+ return [ { ...output , address : this . canonicalAddress ( output . address ) } ] ;
579630 } ) ;
580631 if ( params . txParams . allowExternalChangeAddress && params . txParams . changeAddress ) {
581632 // when an external change address is explicitly specified, count all outputs going towards that
582633 // address in the expected outputs (regardless of the output amount)
583634 expectedOutputs . push (
584- ...allOutputs
585- . map ( ( output ) => {
586- return { ...output , address : this . canonicalAddress ( output . address ) } ;
587- } )
588- . filter ( ( output ) => output . address === this . canonicalAddress ( params . txParams . changeAddress as string ) )
635+ ...allOutputs . flatMap ( ( output ) => {
636+ if (
637+ output . address === undefined ||
638+ output . address !== this . canonicalAddress ( params . txParams . changeAddress as string )
639+ ) {
640+ return [ ] ;
641+ }
642+ return [ { ...output , address : this . canonicalAddress ( output . address ) } ] ;
643+ } )
589644 ) ;
590645 }
591646 }
0 commit comments