@@ -433,4 +433,141 @@ mod test_online {
433433 "balance should reflect exactly one confirmed spend after RBF, got {final_balance}"
434434 ) ;
435435 }
436+
437+ #[ test]
438+ fn test_create_tx_multiple_recipients ( ) {
439+ use bdk_wallet:: bitcoin:: Psbt ;
440+
441+ let ( cli, mut cmd_init, env) = setup_online_wallet ( ) ;
442+ cmd_init. assert ( ) . success ( ) ;
443+ fund_and_sync_wallet ( & cli, & env) ;
444+
445+ let second = cli_new_address ( & cli) ;
446+
447+ let psbt_b64 = run_wallet_json (
448+ & cli,
449+ & [
450+ "create_tx" ,
451+ "--to" ,
452+ & format ! ( "{RECIPIENT}:20000" ) ,
453+ "--to" ,
454+ & format ! ( "{second}:30000" ) ,
455+ ] ,
456+ ) [ "psbt" ]
457+ . as_str ( )
458+ . expect ( "create_tx: missing 'psbt' field" )
459+ . to_string ( ) ;
460+
461+ let psbt: Psbt = psbt_b64
462+ . parse ( )
463+ . expect ( "create_tx returned an invalid PSBT" ) ;
464+ let values: Vec < u64 > = psbt
465+ . unsigned_tx
466+ . output
467+ . iter ( )
468+ . map ( |o| o. value . to_sat ( ) )
469+ . collect ( ) ;
470+
471+ assert ! (
472+ values. contains( & 20_000 ) ,
473+ "first recipient (20_000) output missing: {values:?}"
474+ ) ;
475+ assert ! (
476+ values. contains( & 30_000 ) ,
477+ "second recipient (30_000) output missing: {values:?}"
478+ ) ;
479+ assert ! (
480+ psbt. unsigned_tx. output. len( ) >= 2 ,
481+ "expected at least the two recipient outputs, got {}" ,
482+ psbt. unsigned_tx. output. len( )
483+ ) ;
484+ }
485+
486+ #[ cfg( feature = "rpc" ) ]
487+ mod test_online_rpc {
488+ use crate :: common:: BdkCli ;
489+ use bdk_testenv:: { TestEnv , bitcoincore_rpc:: RpcApi } ;
490+ use bdk_wallet:: bitcoin:: { Address , Amount , Network } ;
491+ use serde_json:: Value ;
492+ use std:: str:: FromStr ;
493+ use tempfile:: TempDir ;
494+
495+ static WALLET_NAME : & str = "test_rpc_wallet" ;
496+
497+
498+ #[ test]
499+ fn test_rpc_full_scan_reflects_funding ( ) {
500+ let env = TestEnv :: new ( ) . expect ( "start testenv" ) ;
501+ let rpc_url = env. bitcoind . rpc_url ( ) ;
502+ let cookie = env
503+ . bitcoind
504+ . params
505+ . cookie_file
506+ . to_str ( )
507+ . unwrap ( )
508+ . to_string ( ) ;
509+
510+ let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
511+ let cli = BdkCli :: new ( "regtest" , Some ( temp_dir. path ( ) . to_path_buf ( ) ) ) ;
512+
513+ // descriptors
514+ let desc = cli. cmd ( "descriptor" , & [ "--type" , "tr" ] ) . output ( ) . unwrap ( ) ;
515+ let dv: Value = serde_json:: from_slice ( & desc. stdout ) . unwrap ( ) ;
516+ let ext = dv[ "private_descriptors" ] [ "external" ] . as_str ( ) . unwrap ( ) ;
517+ let int = dv[ "private_descriptors" ] [ "internal" ] . as_str ( ) . unwrap ( ) ;
518+
519+ // config: rpc client-type, --url flag, cookie as a POSITIONAL arg
520+ cli. build_base_cmd ( )
521+ . args ( [ "wallet" , "--wallet" , WALLET_NAME , "config" ] )
522+ . args ( [ "--ext-descriptor" , ext, "--int-descriptor" , int] )
523+ . args ( [ "--client-type" , "rpc" , "--database-type" , "sqlite" ] )
524+ . args ( [ "--url" , & rpc_url] )
525+ . arg ( & cookie)
526+ . assert ( )
527+ . success ( ) ;
528+
529+ // wallet address to fund
530+ let out = cli
531+ . wallet_cmd ( & [ "--wallet" , WALLET_NAME , "new_address" ] )
532+ . output ( )
533+ . unwrap ( ) ;
534+ let addr_json: Value = serde_json:: from_slice ( & out. stdout ) . unwrap ( ) ;
535+ let address = Address :: from_str ( addr_json[ "address" ] . as_str ( ) . unwrap ( ) )
536+ . unwrap ( )
537+ . require_network ( Network :: Regtest )
538+ . unwrap ( ) ;
539+
540+ // fund + confirm
541+ let node_addr = env
542+ . rpc_client ( )
543+ . get_new_address ( None , None )
544+ . unwrap ( )
545+ . assume_checked ( ) ;
546+ env. mine_blocks ( 101 , Some ( node_addr) ) . unwrap ( ) ;
547+ env. send ( & address, Amount :: from_btc ( 0.5 ) . unwrap ( ) ) . unwrap ( ) ;
548+ env. mine_blocks ( 3 , None ) . unwrap ( ) ;
549+
550+ // full_scan + balance via rpc
551+ let scan = cli
552+ . wallet_cmd ( & [ "--wallet" , WALLET_NAME , "full_scan" ] )
553+ . output ( )
554+ . unwrap ( ) ;
555+ assert ! (
556+ scan. status. success( ) ,
557+ "rpc full_scan failed: {}" ,
558+ String :: from_utf8_lossy( & scan. stderr)
559+ ) ;
560+
561+ let bal = cli
562+ . wallet_cmd ( & [ "--wallet" , WALLET_NAME , "balance" ] )
563+ . output ( )
564+ . unwrap ( ) ;
565+ let bj: Value = serde_json:: from_slice ( & bal. stdout ) . unwrap ( ) ;
566+ assert_eq ! (
567+ bj[ "confirmed" ] . as_u64( ) ,
568+ Some ( 50_000_000 ) ,
569+ "rpc-synced confirmed balance mismatch: {bj}"
570+ ) ;
571+ }
572+ }
436573}
0 commit comments