1use crate::imports::*;
2use convert_case::{Case, Casing};
3use kaspa_rpc_core::api::ops::RpcApiOps;
4
5#[derive(Default, Handler)]
6#[help("Execute RPC commands against the connected Kaspa node")]
7pub struct Rpc;
8
9impl Rpc {
10 fn println<T>(&self, ctx: &Arc<KaspaCli>, v: T)
11 where
12 T: core::fmt::Debug,
13 {
14 ctx.term().writeln(format!("{v:#?}").crlf());
15 }
16
17 async fn main(self: Arc<Self>, ctx: &Arc<dyn Context>, mut argv: Vec<String>, cmd: &str) -> Result<()> {
18 let ctx = ctx.clone().downcast_arc::<KaspaCli>()?;
19 let rpc = ctx.wallet().rpc_api().clone();
20 if argv.is_empty() {
23 return self.display_help(ctx, argv).await;
24 }
25
26 let op_str = argv.remove(0);
27
28 let sanitize = regex::Regex::new(r"\s*rpc\s+\S+\s+").unwrap();
29 let _args = sanitize.replace(cmd, "").trim().to_string();
30 let op_str_uc = op_str.to_case(Case::UpperCamel).to_string();
31 let op = RpcApiOps::from_str(op_str_uc.as_str()).ok_or(Error::custom(format!("No such rpc method: '{op_str}'")))?;
34
35 match op {
36 RpcApiOps::Ping => {
37 rpc.ping().await?;
38 tprintln!(ctx, "ok");
39 }
40 RpcApiOps::GetMetrics => {
41 let result = rpc.get_metrics(true, true, true, true, true, true).await?;
42 self.println(&ctx, result);
43 }
44 RpcApiOps::GetSystemInfo => {
45 let result = rpc.get_system_info().await?;
46 self.println(&ctx, result);
47 }
48 RpcApiOps::GetConnections => {
49 let result = rpc.get_connections(true).await?;
50 self.println(&ctx, result);
51 }
52 RpcApiOps::GetServerInfo => {
53 let result = rpc.get_server_info_call(None, GetServerInfoRequest {}).await?;
54 self.println(&ctx, result);
55 }
56 RpcApiOps::GetSyncStatus => {
57 let result = rpc.get_sync_status_call(None, GetSyncStatusRequest {}).await?;
58 self.println(&ctx, result);
59 }
60 RpcApiOps::GetCurrentNetwork => {
61 let result = rpc.get_current_network_call(None, GetCurrentNetworkRequest {}).await?;
62 self.println(&ctx, result);
63 }
64 RpcApiOps::GetPeerAddresses => {
73 let result = rpc.get_peer_addresses_call(None, GetPeerAddressesRequest {}).await?;
74 self.println(&ctx, result);
75 }
76 RpcApiOps::GetSink => {
77 let result = rpc.get_sink_call(None, GetSinkRequest {}).await?;
78 self.println(&ctx, result);
79 }
80 RpcApiOps::GetMempoolEntries => {
85 let result = rpc
87 .get_mempool_entries_call(
88 None,
89 GetMempoolEntriesRequest { include_orphan_pool: true, filter_transaction_pool: true },
90 )
91 .await?;
92 self.println(&ctx, result);
93 }
94 RpcApiOps::GetConnectedPeerInfo => {
95 let result = rpc.get_connected_peer_info_call(None, GetConnectedPeerInfoRequest {}).await?;
96 self.println(&ctx, result);
97 }
98 RpcApiOps::AddPeer => {
99 if argv.is_empty() {
100 return Err(Error::custom("Usage: rpc addpeer <ip:port> [true|false for 'is_permanent']"));
101 }
102 let peer_address = argv.remove(0).parse::<RpcContextualPeerAddress>()?;
103 let is_permanent = argv.remove(0).parse::<bool>().unwrap_or(false);
104 let result = rpc.add_peer_call(None, AddPeerRequest { peer_address, is_permanent }).await?;
105 self.println(&ctx, result);
106 }
107 RpcApiOps::GetBlock => {
112 if argv.is_empty() {
113 return Err(Error::custom("Missing block hash argument"));
114 }
115 let hash = argv.remove(0);
116 let hash = RpcHash::from_hex(hash.as_str())?;
117 let include_transactions = argv.first().and_then(|x| x.parse::<bool>().ok()).unwrap_or(true);
118 let result = rpc.get_block_call(None, GetBlockRequest { hash, include_transactions }).await?;
119 self.println(&ctx, result);
120 }
121 RpcApiOps::GetVirtualChainFromBlock => {
126 if argv.is_empty() {
127 return Err(Error::custom("Missing startHash argument"));
128 };
129 let start_hash = RpcHash::from_hex(argv.remove(0).as_str())?;
130 let include_accepted_transaction_ids = argv.first().and_then(|x| x.parse::<bool>().ok()).unwrap_or_default();
131 let result = rpc
132 .get_virtual_chain_from_block_call(
133 None,
134 GetVirtualChainFromBlockRequest { start_hash, include_accepted_transaction_ids },
135 )
136 .await?;
137 self.println(&ctx, result);
138 }
139 RpcApiOps::GetBlockCount => {
144 let result = rpc.get_block_count_call(None, GetBlockCountRequest {}).await?;
145 self.println(&ctx, result);
146 }
147 RpcApiOps::GetBlockDagInfo => {
148 let result = rpc.get_block_dag_info_call(None, GetBlockDagInfoRequest {}).await?;
149 self.println(&ctx, result);
150 }
151 RpcApiOps::Shutdown => {
156 let result = rpc.shutdown_call(None, ShutdownRequest {}).await?;
157 self.println(&ctx, result);
158 }
159 RpcApiOps::GetUtxosByAddresses => {
164 if argv.is_empty() {
165 return Err(Error::custom("Please specify at least one address"));
166 }
167 let addresses = argv.iter().map(|s| Address::try_from(s.as_str())).collect::<std::result::Result<Vec<_>, _>>()?;
168 let result = rpc.get_utxos_by_addresses_call(None, GetUtxosByAddressesRequest { addresses }).await?;
169 self.println(&ctx, result);
170 }
171 RpcApiOps::GetBalanceByAddress => {
172 if argv.is_empty() {
173 return Err(Error::custom("Please specify at least one address"));
174 }
175 let addresses = argv.iter().map(|s| Address::try_from(s.as_str())).collect::<std::result::Result<Vec<_>, _>>()?;
176 for address in addresses {
177 let result = rpc.get_balance_by_address_call(None, GetBalanceByAddressRequest { address }).await?;
178 self.println(&ctx, sompi_to_kaspa(result.balance));
179 }
180 }
181 RpcApiOps::GetBalancesByAddresses => {
182 if argv.is_empty() {
183 return Err(Error::custom("Please specify at least one address"));
184 }
185 let addresses = argv.iter().map(|s| Address::try_from(s.as_str())).collect::<std::result::Result<Vec<_>, _>>()?;
186 let result = rpc.get_balances_by_addresses_call(None, GetBalancesByAddressesRequest { addresses }).await?;
187 self.println(&ctx, result);
188 }
189 RpcApiOps::GetSinkBlueScore => {
190 let result = rpc.get_sink_blue_score_call(None, GetSinkBlueScoreRequest {}).await?;
191 self.println(&ctx, result);
192 }
193 RpcApiOps::Ban => {
194 if argv.is_empty() {
195 return Err(Error::custom("Please specify peer IP address"));
196 }
197 let ip: RpcIpAddress = argv.remove(0).parse()?;
198 let result = rpc.ban_call(None, BanRequest { ip }).await?;
199 self.println(&ctx, result);
200 }
201 RpcApiOps::Unban => {
202 if argv.is_empty() {
203 return Err(Error::custom("Please specify peer IP address"));
204 }
205 let ip: RpcIpAddress = argv.remove(0).parse()?;
206 let result = rpc.unban_call(None, UnbanRequest { ip }).await?;
207 self.println(&ctx, result);
208 }
209 RpcApiOps::GetInfo => {
210 let result = rpc.get_info_call(None, GetInfoRequest {}).await?;
211 self.println(&ctx, result);
212 }
213 RpcApiOps::GetMempoolEntriesByAddresses => {
218 if argv.is_empty() {
219 return Err(Error::custom("Please specify at least one address"));
220 }
221 let addresses = argv.iter().map(|s| Address::try_from(s.as_str())).collect::<std::result::Result<Vec<_>, _>>()?;
222 let include_orphan_pool = true;
223 let filter_transaction_pool = true;
224 let result = rpc
225 .get_mempool_entries_by_addresses_call(
226 None,
227 GetMempoolEntriesByAddressesRequest { addresses, include_orphan_pool, filter_transaction_pool },
228 )
229 .await?;
230 self.println(&ctx, result);
231 }
232 RpcApiOps::GetCoinSupply => {
233 let result = rpc.get_coin_supply_call(None, GetCoinSupplyRequest {}).await?;
234 self.println(&ctx, result);
235 }
236 RpcApiOps::GetDaaScoreTimestampEstimate => {
237 if argv.is_empty() {
238 return Err(Error::custom("Please specify a daa_score"));
239 }
240 let daa_score_result = argv.iter().map(|s| s.parse::<u64>()).collect::<std::result::Result<Vec<_>, _>>();
241
242 match daa_score_result {
243 Ok(daa_scores) => {
244 let result = rpc
245 .get_daa_score_timestamp_estimate_call(None, GetDaaScoreTimestampEstimateRequest { daa_scores })
246 .await?;
247 self.println(&ctx, result);
248 }
249 Err(_err) => {
250 return Err(Error::custom("Could not parse daa_scores to u64"));
251 }
252 }
253 }
254 RpcApiOps::GetFeeEstimate => {
255 let result = rpc.get_fee_estimate_call(None, GetFeeEstimateRequest {}).await?;
256 self.println(&ctx, result);
257 }
258 RpcApiOps::GetFeeEstimateExperimental => {
259 let verbose = if argv.is_empty() { false } else { argv.remove(0).parse().unwrap_or(false) };
260 let result = rpc.get_fee_estimate_experimental_call(None, GetFeeEstimateExperimentalRequest { verbose }).await?;
261 self.println(&ctx, result);
262 }
263 RpcApiOps::GetCurrentBlockColor => {
264 if argv.is_empty() {
265 return Err(Error::custom("Missing block hash argument"));
266 }
267 let hash = argv.remove(0);
268 let hash = RpcHash::from_hex(hash.as_str())?;
269 let result = rpc.get_current_block_color_call(None, GetCurrentBlockColorRequest { hash }).await?;
270 self.println(&ctx, result);
271 }
272 _ => {
273 tprintln!(ctx, "rpc method exists but is not supported by the cli: '{op_str}'\r\n");
274 return Ok(());
275 }
276 }
277
278 let prefix = Regex::new(r"(?i)^\s*rpc\s+\S+\s+").unwrap();
279 let _req = prefix.replace(cmd, "").trim().to_string();
280
281 Ok(())
282 }
283
284 async fn display_help(self: Arc<Self>, ctx: Arc<KaspaCli>, _argv: Vec<String>) -> Result<()> {
285 let help = RpcApiOps::into_iter()
287 .filter_map(|op| op.rustdoc().is_not_empty().then_some((op.as_str().to_case(Case::Kebab).to_string(), op.rustdoc())))
288 .collect::<Vec<(_, _)>>();
289
290 ctx.term().help(&help, None)?;
291
292 tprintln!(ctx);
293 tprintln!(ctx, "Please note that not all listed RPC methods are currently implemented");
294 tprintln!(ctx);
295
296 Ok(())
297 }
298}