Skip to main content

nautilus_cli/
opt.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use clap::Parser;
17
18/// Command-line interface for NautilusTrader.
19#[derive(Debug, Parser)]
20#[clap(version, about, author)]
21pub struct NautilusCli {
22    #[clap(subcommand)]
23    pub command: Commands,
24}
25
26/// Available top-level commands for the NautilusTrader CLI.
27#[derive(Parser, Debug)]
28pub enum Commands {
29    Database(DatabaseOpt),
30    #[cfg(feature = "defi")]
31    Blockchain(BlockchainOpt),
32}
33
34/// Database management options and subcommands.
35#[derive(Parser, Debug)]
36#[command(about = "Postgres database operations", long_about = None)]
37pub struct DatabaseOpt {
38    #[clap(subcommand)]
39    pub command: DatabaseCommand,
40}
41
42/// Configuration parameters for database connection and operations.
43#[derive(Parser, Debug, Clone)]
44pub struct DatabaseConfig {
45    /// Hostname or IP address of the database server.
46    #[arg(long)]
47    pub host: Option<String>,
48    /// Port number of the database server.
49    #[arg(long)]
50    pub port: Option<u16>,
51    /// Username for connecting to the database.
52    #[arg(long)]
53    pub username: Option<String>,
54    /// Name of the database.
55    #[arg(long)]
56    pub database: Option<String>,
57    /// Password for connecting to the database.
58    #[arg(long)]
59    pub password: Option<String>,
60    /// Directory path to the schema files.
61    #[arg(long)]
62    pub schema: Option<String>,
63}
64
65/// Available database management commands.
66#[derive(Parser, Debug, Clone)]
67#[command(about = "Postgres database operations", long_about = None)]
68pub enum DatabaseCommand {
69    /// Initializes a new Postgres database with the latest schema.
70    Init(DatabaseConfig),
71    /// Drops roles, privileges and deletes all data from the database.
72    Drop(DatabaseConfig),
73}
74
75#[cfg(feature = "defi")]
76/// Blockchain management options and subcommands.
77#[derive(Parser, Debug)]
78#[command(about = "Blockchain operations", long_about = None)]
79pub struct BlockchainOpt {
80    #[clap(subcommand)]
81    pub command: BlockchainCommand,
82}
83
84#[cfg(feature = "defi")]
85/// Available blockchain management commands.
86#[derive(Parser, Debug, Clone)]
87#[command(about = "Blockchain operations", long_about = None)]
88pub enum BlockchainCommand {
89    /// Syncs blockchain blocks.
90    SyncBlocks {
91        /// The blockchain chain name (case-insensitive). Examples: ethereum, arbitrum, base, polygon, bsc
92        #[arg(long)]
93        chain: String,
94        /// Starting block number to sync from (optional)
95        #[arg(long)]
96        from_block: Option<u64>,
97        /// Ending block number to sync to (optional, defaults to current chain head)
98        #[arg(long)]
99        to_block: Option<u64>,
100        /// Database configuration options
101        #[clap(flatten)]
102        database: DatabaseConfig,
103    },
104    /// Sync DEX pools.
105    SyncDex {
106        /// The blockchain chain name (case-insensitive). Examples: ethereum, arbitrum, base, polygon, bsc
107        #[arg(long)]
108        chain: String,
109        /// The DEX name (case-insensitive). Examples: `UniswapV3`, uniswapv3, `SushiSwapV2`, `PancakeSwapV3`
110        #[arg(long)]
111        dex: String,
112        /// RPC HTTP URL for blockchain calls (optional, falls back to `RPC_HTTP_URL` env var)
113        #[arg(long)]
114        rpc_url: Option<String>,
115        /// Reset sync progress and start from the beginning, ignoring last synced block
116        #[arg(long)]
117        reset: bool,
118        /// Maximum number of Multicall calls per RPC request (optional, defaults to 200)
119        #[arg(long)]
120        multicall_calls_per_rpc_request: Option<u32>,
121        /// Database configuration options
122        #[clap(flatten)]
123        database: DatabaseConfig,
124    },
125    /// Analyze a specific DEX pool.
126    AnalyzePool {
127        /// The blockchain chain name (case-insensitive). Examples: ethereum, arbitrum, base, polygon, bsc
128        #[arg(long)]
129        chain: String,
130        /// The DEX name (case-insensitive). Examples: UniswapV3, uniswapv3, SushiSwapV2, PancakeSwapV3
131        #[arg(long)]
132        dex: String,
133        /// The pool contract address
134        #[arg(long)]
135        address: String,
136        /// Starting block number to sync from (optional)
137        #[arg(long)]
138        from_block: Option<u64>,
139        /// Ending block number to sync to (optional, defaults to current chain head)
140        #[arg(long)]
141        to_block: Option<u64>,
142        /// RPC HTTP URL for blockchain calls (optional, falls back to RPC_HTTP_URL env var)
143        #[arg(long)]
144        rpc_url: Option<String>,
145        /// Reset sync progress and start from the beginning, ignoring last synced block
146        #[arg(long)]
147        reset: bool,
148        /// Maximum number of Multicall calls per RPC request (optional, defaults to 200)
149        #[arg(long)]
150        multicall_calls_per_rpc_request: Option<u32>,
151        /// Database configuration options
152        #[clap(flatten)]
153        database: DatabaseConfig,
154    },
155    /// Analyze several DEX pools in one runtime.
156    AnalyzePools {
157        /// The blockchain chain name (case-insensitive). Examples: ethereum, arbitrum, base, polygon, bsc
158        #[arg(long)]
159        chain: String,
160        /// The DEX name (case-insensitive). Examples: UniswapV3, uniswapv3, SushiSwapV2, PancakeSwapV3
161        #[arg(long)]
162        dex: String,
163        /// Pool contract address. Can be repeated.
164        #[arg(long = "address")]
165        addresses: Vec<String>,
166        /// File containing one pool contract address per line. Empty lines and comment lines are ignored.
167        #[arg(long)]
168        addresses_file: Option<String>,
169        /// Starting block number to sync from (optional)
170        #[arg(long)]
171        from_block: Option<u64>,
172        /// Ending block number to sync to (optional, defaults to current chain head)
173        #[arg(long)]
174        to_block: Option<u64>,
175        /// RPC HTTP URL for blockchain calls (optional, falls back to RPC_HTTP_URL env var)
176        #[arg(long)]
177        rpc_url: Option<String>,
178        /// Reset sync progress and start from the beginning, ignoring last synced block
179        #[arg(long)]
180        reset: bool,
181        /// Maximum number of Multicall calls per RPC request (optional, defaults to 200)
182        #[arg(long)]
183        multicall_calls_per_rpc_request: Option<u32>,
184        /// Database configuration options
185        #[clap(flatten)]
186        database: DatabaseConfig,
187    },
188}
189
190#[cfg(all(test, feature = "defi"))]
191mod tests {
192    use clap::Parser;
193    use rstest::rstest;
194
195    use super::*;
196
197    #[rstest]
198    fn analyze_pools_cli_parses_repeated_addresses_file_and_shared_options() {
199        let cli = NautilusCli::try_parse_from([
200            "nautilus",
201            "blockchain",
202            "analyze-pools",
203            "--chain",
204            "ethereum",
205            "--dex",
206            "UniswapV3",
207            "--address",
208            "0x1111111111111111111111111111111111111111",
209            "--address",
210            "0x2222222222222222222222222222222222222222",
211            "--addresses-file",
212            "/tmp/pools.txt",
213            "--from-block",
214            "100",
215            "--to-block",
216            "200",
217            "--rpc-url",
218            "http://localhost:8545",
219            "--reset",
220            "--multicall-calls-per-rpc-request",
221            "25",
222            "--host",
223            "localhost",
224            "--port",
225            "5433",
226            "--username",
227            "postgres",
228            "--database",
229            "nautilus",
230            "--password",
231            "secret",
232        ])
233        .unwrap();
234
235        match cli.command {
236            Commands::Blockchain(BlockchainOpt {
237                command:
238                    BlockchainCommand::AnalyzePools {
239                        chain,
240                        dex,
241                        addresses,
242                        addresses_file,
243                        from_block,
244                        to_block,
245                        rpc_url,
246                        reset,
247                        multicall_calls_per_rpc_request,
248                        database,
249                    },
250            }) => {
251                assert_eq!(chain, "ethereum");
252                assert_eq!(dex, "UniswapV3");
253                assert_eq!(
254                    addresses,
255                    vec![
256                        "0x1111111111111111111111111111111111111111".to_string(),
257                        "0x2222222222222222222222222222222222222222".to_string(),
258                    ]
259                );
260                assert_eq!(addresses_file.as_deref(), Some("/tmp/pools.txt"));
261                assert_eq!(from_block, Some(100));
262                assert_eq!(to_block, Some(200));
263                assert_eq!(rpc_url.as_deref(), Some("http://localhost:8545"));
264                assert!(reset);
265                assert_eq!(multicall_calls_per_rpc_request, Some(25));
266                assert_eq!(database.host.as_deref(), Some("localhost"));
267                assert_eq!(database.port, Some(5433));
268                assert_eq!(database.username.as_deref(), Some("postgres"));
269                assert_eq!(database.database.as_deref(), Some("nautilus"));
270                assert_eq!(database.password.as_deref(), Some("secret"));
271                assert_eq!(database.schema, None);
272            }
273            _ => panic!("Expected analyze-pools blockchain command"),
274        }
275    }
276}