1mod account;
2pub mod args;
3mod block;
4mod book;
5mod snapshot;
6mod trace;
7mod trades;
8mod tx;
9
10use std::time::Duration;
11
12use alloy::{
13 providers::{Provider, ProviderBuilder},
14 rpc::{client::RpcClient, types::BlockId},
15 transports::layers::{RetryBackoffLayer, ThrottleLayer},
16};
17use anyhow::Context;
18use args::Cli;
19use perpl_sdk::{Chain, state::SnapshotBuilder};
20use tokio_util::sync::CancellationToken;
21
22use crate::args::{Commands, ShowCommands};
23
24pub async fn run(cli: Cli) -> anyhow::Result<()> {
25 let chain = if cli.testnet { Chain::testnet() } else { Chain::mainnet() };
26 let (rpc, default) = cli.rpc.map(|rpc| (rpc, false)).unwrap_or(if cli.testnet {
27 (args::DEFAULT_TESTNET_RPC_PROVIDER.to_string(), true)
28 } else {
29 (args::DEFAULT_MAINNET_RPC_PROVIDER.to_string(), true)
30 });
31 let client = if default || cli.rpc_throttle.is_some() {
32 RpcClient::builder()
34 .layer(ThrottleLayer::new(cli.rpc_throttle.unwrap_or(args::DEFAULT_RPC_THROTTLING)))
35 .layer(RetryBackoffLayer::new(10, 100, 200))
36 .connect(&rpc)
37 .await
38 .context("connecting to RPC")?
39 } else {
40 RpcClient::builder()
41 .layer(RetryBackoffLayer::new(10, 100, 200))
42 .connect(&rpc)
43 .await
44 .context("connecting to RPC")?
45 };
46 client.set_poll_interval(Duration::from_millis(100));
47 let provider = ProviderBuilder::new().connect_client(client);
48
49 let chain = Chain::custom(
52 provider.get_chain_id().await?,
53 chain.collateral_token(),
54 chain.deployed_at_block(),
55 cli.exchange.unwrap_or(chain.exchange()),
56 cli.perp.clone(),
57 )
58 .with_excluded_perpetuals(chain.excluded_perpetuals().to_vec());
61
62 if !cli.perp.is_empty() {
63 let listed = perpl_sdk::state::listed_perpetuals(
64 &chain,
65 provider.clone(),
66 cli.block.map(BlockId::number).unwrap_or(BlockId::safe()),
67 )
68 .await
69 .context("discovering listed perpetuals")?;
70 if let Some(unknown_perp) = cli.perp.iter().find(|perp_id| !listed.contains(perp_id)) {
71 if chain.excluded_perpetuals().contains(unknown_perp) {
74 return Err(anyhow::anyhow!(
75 "perpetual ID {} is excluded from indexing for this chain",
76 unknown_perp,
77 ));
78 }
79 return Err(anyhow::anyhow!(
80 "unknown perpetual ID: {}, listed: {:?}",
81 unknown_perp,
82 listed,
83 ));
84 }
85 }
86
87 let mut builder = SnapshotBuilder::new(&chain, provider.clone());
88 if let Some(block) = cli.block {
89 builder = builder.at_block(BlockId::number(block));
90 }
91
92 if !cli.account.is_empty() {
93 builder = builder.with_accounts(cli.account.clone());
94 } else {
95 builder = builder.with_all_positions();
96 }
97
98 let builder = match &cli.command {
99 Commands::Block { block_number: _ } => None,
100 Commands::Snapshot | Commands::Trace => Some(builder),
101 Commands::Show { command } => match command {
102 ShowCommands::Account { num_trades: _ } => {
103 if cli.account.len() != 1 {
104 return Err(anyhow::anyhow!(
105 "exactly one account should be provided, see `--account`"
106 ));
107 }
108 Some(builder)
109 },
110 ShowCommands::Book { depth: _, orders_per_level: _, show_expired: _ } => {
111 if cli.perp.len() != 1 {
112 return Err(anyhow::anyhow!(
113 "exactly one perp should be provided, see `--perp`"
114 ));
115 }
116 Some(builder)
117 },
118 ShowCommands::Trades => None,
119 },
120 Commands::Tx { tx_hash: _ } => None,
121 };
122
123 let exchange = if let Some(builder) = builder {
124 Some(
125 builder
126 .build()
127 .await
128 .context("building exchange snapshot")?,
129 )
130 } else {
131 None
132 };
133
134 let cancellation_signal = CancellationToken::new();
135 let cancellation_token = cancellation_signal.child_token();
136 tokio::spawn(async move {
137 tokio::signal::ctrl_c()
138 .await
139 .expect("failed to install CTRL+C signal handler");
140 cancellation_signal.cancel();
141 });
142
143 match &cli.command {
144 Commands::Block { block_number } => block::render(&chain, provider, *block_number).await?,
145 Commands::Snapshot => snapshot::render(exchange.unwrap()),
146 Commands::Show { command } => match command {
147 ShowCommands::Account { num_trades } => {
148 account::render(
149 chain,
150 provider,
151 exchange.unwrap(),
152 cli.num_blocks,
153 *num_trades,
154 cancellation_token,
155 )
156 .await?
157 },
158 ShowCommands::Book { depth, orders_per_level, show_expired } => {
159 book::render(
160 chain,
161 provider,
162 exchange.unwrap(),
163 *depth,
164 *orders_per_level,
165 *show_expired,
166 cli.num_blocks,
167 cancellation_token,
168 )
169 .await?
170 },
171 ShowCommands::Trades => {
172 trades::render(chain, provider, cli.num_blocks, cancellation_token).await?
173 },
174 },
175 Commands::Trace => {
176 trace::render(chain, provider, exchange.unwrap(), cli.num_blocks, cancellation_token)
177 .await?
178 },
179 Commands::Tx { tx_hash } => tx::render(provider, *tx_hash).await?,
180 }
181
182 Ok(())
183}