@@ -5,7 +5,7 @@ use sonic_rs::{Value, json};
55
66use crate :: context:: Context ;
77use crate :: error:: RpcError ;
8- use crate :: handlers:: required_u64;
8+ use crate :: handlers:: { required_str , required_u64, serde_to_sonic } ;
99
1010const BLOCK_VSIZE_TARGET : u64 = 1_000_000 ;
1111const DEFAULT_MIN_FEERATE_SAT_PER_KVB : u64 = 1_000 ; // 1 sat/vB
@@ -68,6 +68,65 @@ pub(crate) fn estimaterawfee(ctx: &Arc<Context>, params: &Value) -> Result<Value
6868 "long" : { "feerate" : feerate, "decay" : 0.962 , "scale" : 1 }
6969 } ) )
7070}
71+
72+ pub ( crate ) fn validateaddress ( ctx : & Arc < Context > , params : & Value ) -> Result < Value , RpcError > {
73+ use core:: str:: FromStr as _;
74+
75+ use bitcoin:: hex:: DisplayHex as _;
76+
77+ let address_str = required_str ( params, 0 , "address is required" ) ?;
78+ let network = match ctx. chain_network {
79+ bitcoin_rs_primitives:: Network :: Mainnet => bitcoin:: Network :: Bitcoin ,
80+ bitcoin_rs_primitives:: Network :: Testnet3 => bitcoin:: Network :: Testnet ,
81+ bitcoin_rs_primitives:: Network :: Testnet4 => bitcoin:: Network :: Testnet4 ,
82+ bitcoin_rs_primitives:: Network :: Signet => bitcoin:: Network :: Signet ,
83+ bitcoin_rs_primitives:: Network :: Regtest => bitcoin:: Network :: Regtest ,
84+ } ;
85+ let Ok ( unchecked) = bitcoin:: Address :: from_str ( address_str) else {
86+ return Ok ( json ! ( { "isvalid" : false } ) ) ;
87+ } ;
88+ let Ok ( address) = unchecked. require_network ( network) else {
89+ return Ok ( json ! ( { "isvalid" : false } ) ) ;
90+ } ;
91+
92+ let script = address. script_pubkey ( ) ;
93+ let script_hex = script. as_bytes ( ) . to_lower_hex_string ( ) ;
94+ let address_canon = address. to_string ( ) ;
95+ let mut response = serde_json:: Map :: new ( ) ;
96+ response. insert ( "isvalid" . to_owned ( ) , serde_json:: Value :: Bool ( true ) ) ;
97+ response. insert (
98+ "address" . to_owned ( ) ,
99+ serde_json:: Value :: String ( address_canon) ,
100+ ) ;
101+ response. insert (
102+ "scriptPubKey" . to_owned ( ) ,
103+ serde_json:: Value :: String ( script_hex) ,
104+ ) ;
105+ response. insert (
106+ "isscript" . to_owned ( ) ,
107+ serde_json:: Value :: Bool ( script. is_p2sh ( ) || script. is_p2wsh ( ) ) ,
108+ ) ;
109+ response. insert (
110+ "iswitness" . to_owned ( ) ,
111+ serde_json:: Value :: Bool ( script. is_witness_program ( ) ) ,
112+ ) ;
113+ if let Some ( version) = script. witness_version ( ) {
114+ response. insert (
115+ "witness_version" . to_owned ( ) ,
116+ serde_json:: Value :: Number ( i64:: from ( version. to_num ( ) ) . into ( ) ) ,
117+ ) ;
118+ // Witness program is the bytes after the 1-byte version prefix and 1-byte push opcode.
119+ let bytes = script. as_bytes ( ) ;
120+ if bytes. len ( ) >= 2 {
121+ response. insert (
122+ "witness_program" . to_owned ( ) ,
123+ serde_json:: Value :: String ( bytes[ 2 ..] . to_lower_hex_string ( ) ) ,
124+ ) ;
125+ }
126+ }
127+
128+ serde_to_sonic ( & serde_json:: Value :: Object ( response) )
129+ }
71130#[ cfg( test) ]
72131mod tests {
73132 use super :: * ;
@@ -89,3 +148,40 @@ mod tests {
89148 ) ;
90149 }
91150}
151+
152+ #[ cfg( test) ]
153+ mod validateaddress_tests {
154+ use super :: * ;
155+ use alloc:: sync:: Arc ;
156+ use sonic_rs:: JsonValueTrait ;
157+
158+ #[ test]
159+ fn validateaddress_returns_isvalid_false_for_garbage ( ) {
160+ let ctx = Arc :: new ( Context :: new ( ) ) ;
161+ let result = validateaddress ( & ctx, & json ! ( [ "not a real address" ] ) )
162+ . unwrap_or_else ( |err| panic ! ( "validateaddress failed: {err}" ) ) ;
163+ let Some ( isvalid) = result
164+ . get ( "isvalid" )
165+ . and_then ( sonic_rs:: JsonValueTrait :: as_bool)
166+ else {
167+ panic ! ( "isvalid missing: {result:?}" ) ;
168+ } ;
169+ assert ! ( !isvalid) ;
170+ }
171+
172+ #[ test]
173+ fn validateaddress_returns_isvalid_true_for_p2pkh_mainnet ( ) {
174+ // ctx defaults to Mainnet network selector.
175+ let ctx = Arc :: new ( Context :: new ( ) ) ;
176+ // 1BoatSLRHtKNngkdXEeobR76b53LETtpyT is a famous P2PKH address.
177+ let result = validateaddress ( & ctx, & json ! ( [ "1BoatSLRHtKNngkdXEeobR76b53LETtpyT" ] ) )
178+ . unwrap_or_else ( |err| panic ! ( "validateaddress failed: {err}" ) ) ;
179+ let Some ( isvalid) = result
180+ . get ( "isvalid" )
181+ . and_then ( sonic_rs:: JsonValueTrait :: as_bool)
182+ else {
183+ panic ! ( "isvalid missing: {result:?}" ) ;
184+ } ;
185+ assert ! ( isvalid, "expected valid: {result:?}" ) ;
186+ }
187+ }
0 commit comments