o2-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,147 @@
1
+ """o2 admin commands - Admin operations."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.cli import get_state
8
+ from o2_cli.client import O2Client
9
+ from o2_cli.config import load_config, get_active_profile
10
+ from o2_cli.exceptions import APIError, ConnectionError
11
+ from o2_cli.output import OutputFormatter
12
+
13
+ app = typer.Typer(help="Admin operations")
14
+
15
+
16
+ @app.command("gas-status")
17
+ def gas_status():
18
+ """Show gas pool status and funding info."""
19
+ asyncio.run(_gas_status())
20
+
21
+
22
+ async def _gas_status():
23
+ state = get_state()
24
+ formatter = OutputFormatter(json_mode=state["json_output"])
25
+ config = load_config()
26
+ profile = get_active_profile(config)
27
+ api_url = state.get("api_url") or profile.api_url
28
+ timeout = state.get("timeout") or profile.timeout
29
+
30
+ if not profile.token and profile.auth_type == "jwt":
31
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
32
+ raise typer.Exit(1)
33
+
34
+ async with O2Client(api_url, timeout) as client:
35
+ client.set_jwt(profile.token)
36
+ if profile.api_key_id:
37
+ client.set_api_key(profile.api_key_id, profile.api_secret)
38
+ try:
39
+ data = await client.get("/admin/gas/status")
40
+ formatter.print_raw(data)
41
+ except APIError as e:
42
+ formatter.print_error(str(e), e.code)
43
+ raise typer.Exit(1)
44
+ except ConnectionError as e:
45
+ formatter.print_error(str(e))
46
+ raise typer.Exit(1)
47
+
48
+
49
+ @app.command("proxy-list")
50
+ def proxy_list():
51
+ """List all deposit proxy addresses."""
52
+ asyncio.run(_proxy_list())
53
+
54
+
55
+ async def _proxy_list():
56
+ state = get_state()
57
+ formatter = OutputFormatter(json_mode=state["json_output"])
58
+ config = load_config()
59
+ profile = get_active_profile(config)
60
+ api_url = state.get("api_url") or profile.api_url
61
+ timeout = state.get("timeout") or profile.timeout
62
+
63
+ if not profile.token and profile.auth_type == "jwt":
64
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
65
+ raise typer.Exit(1)
66
+
67
+ async with O2Client(api_url, timeout) as client:
68
+ client.set_jwt(profile.token)
69
+ if profile.api_key_id:
70
+ client.set_api_key(profile.api_key_id, profile.api_secret)
71
+ try:
72
+ data = await client.get("/admin/proxy-addresses")
73
+ formatter.print_raw(data)
74
+ except APIError as e:
75
+ formatter.print_error(str(e), e.code)
76
+ raise typer.Exit(1)
77
+ except ConnectionError as e:
78
+ formatter.print_error(str(e))
79
+ raise typer.Exit(1)
80
+
81
+
82
+ @app.command("api-keys")
83
+ def api_keys():
84
+ """List registered API keys."""
85
+ asyncio.run(_api_keys())
86
+
87
+
88
+ async def _api_keys():
89
+ state = get_state()
90
+ formatter = OutputFormatter(json_mode=state["json_output"])
91
+ config = load_config()
92
+ profile = get_active_profile(config)
93
+ api_url = state.get("api_url") or profile.api_url
94
+ timeout = state.get("timeout") or profile.timeout
95
+
96
+ if not profile.token and profile.auth_type == "jwt":
97
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
98
+ raise typer.Exit(1)
99
+
100
+ async with O2Client(api_url, timeout) as client:
101
+ client.set_jwt(profile.token)
102
+ if profile.api_key_id:
103
+ client.set_api_key(profile.api_key_id, profile.api_secret)
104
+ try:
105
+ data = await client.get("/admin/api-keys")
106
+ formatter.print_raw(data)
107
+ except APIError as e:
108
+ formatter.print_error(str(e), e.code)
109
+ raise typer.Exit(1)
110
+ except ConnectionError as e:
111
+ formatter.print_error(str(e))
112
+ raise typer.Exit(1)
113
+
114
+
115
+ @app.command("reconcile")
116
+ def reconcile():
117
+ """Trigger order reconciliation."""
118
+ asyncio.run(_reconcile())
119
+
120
+
121
+ async def _reconcile():
122
+ state = get_state()
123
+ formatter = OutputFormatter(json_mode=state["json_output"])
124
+ config = load_config()
125
+ profile = get_active_profile(config)
126
+ api_url = state.get("api_url") or profile.api_url
127
+ timeout = state.get("timeout") or profile.timeout
128
+
129
+ if not profile.token and profile.auth_type == "jwt":
130
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
131
+ raise typer.Exit(1)
132
+
133
+ async with O2Client(api_url, timeout) as client:
134
+ client.set_jwt(profile.token)
135
+ if profile.api_key_id:
136
+ client.set_api_key(profile.api_key_id, profile.api_secret)
137
+ try:
138
+ data = await client.post("/admin/reconcile")
139
+ formatter.print_raw(data)
140
+ if not state["json_output"]:
141
+ formatter.print_success("Reconciliation triggered")
142
+ except APIError as e:
143
+ formatter.print_error(str(e), e.code)
144
+ raise typer.Exit(1)
145
+ except ConnectionError as e:
146
+ formatter.print_error(str(e))
147
+ raise typer.Exit(1)
@@ -0,0 +1,140 @@
1
+ """o2 auth commands - Authentication management."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.client import O2Client
8
+ from o2_cli.config import save_token
9
+ from o2_cli.exceptions import APIError, ConnectionError
10
+ from o2_cli.commands._helpers import resolve_context, setup_client
11
+
12
+ app = typer.Typer(help="Authentication commands")
13
+
14
+
15
+ @app.command("test-login")
16
+ def test_login():
17
+ """Dev: get a test JWT token."""
18
+ asyncio.run(_test_login())
19
+
20
+
21
+ async def _test_login():
22
+ profile, formatter, api_url, timeout = resolve_context()
23
+
24
+ async with O2Client(api_url, timeout) as client:
25
+ try:
26
+ data = await client.auth_test_login()
27
+ token = data.get("token") or data.get("access_token")
28
+ if token:
29
+ save_token(profile.auth_type if profile.auth_type == "jwt" else "default", token)
30
+ # Save to active profile
31
+ from o2_cli.config import load_config, get_active_profile, CONFIG_FILE
32
+ config = load_config(CONFIG_FILE)
33
+ save_token(config.active_profile, token)
34
+ formatter.print_success("Logged in (token saved to config)")
35
+ formatter.print_raw(data)
36
+ except APIError as e:
37
+ formatter.print_error(str(e), e.code)
38
+ raise typer.Exit(1)
39
+ except ConnectionError as e:
40
+ formatter.print_error(str(e))
41
+ raise typer.Exit(1)
42
+
43
+
44
+ @app.command("challenge")
45
+ def challenge(
46
+ wallet: str = typer.Option(..., "--wallet", "-w", help="Wallet address"),
47
+ ):
48
+ """Get a login challenge nonce for wallet signing."""
49
+ asyncio.run(_challenge(wallet))
50
+
51
+
52
+ async def _challenge(wallet: str):
53
+ profile, formatter, api_url, timeout = resolve_context()
54
+
55
+ async with O2Client(api_url, timeout) as client:
56
+ try:
57
+ data = await client.auth_challenge(wallet)
58
+ formatter.print_raw(data)
59
+ formatter.print_success("Sign this message with your wallet to login")
60
+ except APIError as e:
61
+ formatter.print_error(str(e), e.code)
62
+ raise typer.Exit(1)
63
+ except ConnectionError as e:
64
+ formatter.print_error(str(e))
65
+ raise typer.Exit(1)
66
+
67
+
68
+ @app.command("login")
69
+ def login(
70
+ wallet: str = typer.Option(..., "--wallet", "-w", help="Wallet address"),
71
+ signature: str = typer.Option(..., "--signature", "-s", help="Wallet signature"),
72
+ message: str = typer.Option(..., "--message", "-m", help="Signed message"),
73
+ ):
74
+ """Login with wallet signature."""
75
+ asyncio.run(_login(wallet, signature, message))
76
+
77
+
78
+ async def _login(wallet: str, signature: str, message: str):
79
+ profile, formatter, api_url, timeout = resolve_context()
80
+
81
+ async with O2Client(api_url, timeout) as client:
82
+ try:
83
+ data = await client.auth_signature_login(wallet, signature, message)
84
+ token = data.get("token") or data.get("access_token")
85
+ if token:
86
+ from o2_cli.config import load_config, CONFIG_FILE
87
+ config = load_config(CONFIG_FILE)
88
+ save_token(config.active_profile, token)
89
+ formatter.print_success(f"Logged in as {wallet[:8]}...")
90
+ formatter.print_raw(data)
91
+ except APIError as e:
92
+ formatter.print_error(str(e), e.code)
93
+ raise typer.Exit(1)
94
+ except ConnectionError as e:
95
+ formatter.print_error(str(e))
96
+ raise typer.Exit(1)
97
+
98
+
99
+ @app.command("me")
100
+ def me():
101
+ """Show current user info."""
102
+ asyncio.run(_me())
103
+
104
+
105
+ async def _me():
106
+ profile, formatter, api_url, timeout = resolve_context()
107
+
108
+ async with O2Client(api_url, timeout) as client:
109
+ setup_client(client, profile)
110
+ try:
111
+ data = await client.auth_me()
112
+ formatter.print_raw(data)
113
+ except APIError as e:
114
+ formatter.print_error(str(e), e.code)
115
+ raise typer.Exit(1)
116
+ except ConnectionError as e:
117
+ formatter.print_error(str(e))
118
+ raise typer.Exit(1)
119
+
120
+
121
+ @app.command("session")
122
+ def session():
123
+ """Check session status."""
124
+ asyncio.run(_session())
125
+
126
+
127
+ async def _session():
128
+ profile, formatter, api_url, timeout = resolve_context()
129
+
130
+ async with O2Client(api_url, timeout) as client:
131
+ setup_client(client, profile)
132
+ try:
133
+ data = await client.auth_session_status()
134
+ formatter.print_raw(data)
135
+ except APIError as e:
136
+ formatter.print_error(str(e), e.code)
137
+ raise typer.Exit(1)
138
+ except ConnectionError as e:
139
+ formatter.print_error(str(e))
140
+ raise typer.Exit(1)
@@ -0,0 +1,64 @@
1
+ """o2 balance commands - Balance queries."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.client import O2Client
8
+ from o2_cli.exceptions import APIError, ConnectionError
9
+ from o2_cli.commands._helpers import resolve_context, setup_client, require_auth
10
+
11
+ app = typer.Typer(help="Balance queries")
12
+
13
+
14
+ @app.command("show")
15
+ def show():
16
+ """Show account balance (cash + bonus)."""
17
+ asyncio.run(_show())
18
+
19
+
20
+ async def _show():
21
+ profile, formatter, api_url, timeout = resolve_context()
22
+
23
+ if not require_auth(profile, formatter):
24
+ raise typer.Exit(1)
25
+
26
+ async with O2Client(api_url, timeout) as client:
27
+ setup_client(client, profile)
28
+ try:
29
+ data = await client.get_balance()
30
+ formatter.print_balance(data)
31
+ except APIError as e:
32
+ formatter.print_error(str(e), e.code)
33
+ raise typer.Exit(1)
34
+ except ConnectionError as e:
35
+ formatter.print_error(str(e))
36
+ raise typer.Exit(1)
37
+
38
+
39
+ @app.command("history")
40
+ def history(
41
+ limit: int = typer.Option(50, "--limit", "-n", help="Number of records"),
42
+ offset: int = typer.Option(0, "--offset", help="Offset"),
43
+ ):
44
+ """Show balance change history."""
45
+ asyncio.run(_history(limit, offset))
46
+
47
+
48
+ async def _history(limit: int, offset: int):
49
+ profile, formatter, api_url, timeout = resolve_context()
50
+
51
+ if not require_auth(profile, formatter):
52
+ raise typer.Exit(1)
53
+
54
+ async with O2Client(api_url, timeout) as client:
55
+ setup_client(client, profile)
56
+ try:
57
+ data = await client.get_balance_history(limit, offset)
58
+ formatter.print_raw(data)
59
+ except APIError as e:
60
+ formatter.print_error(str(e), e.code)
61
+ raise typer.Exit(1)
62
+ except ConnectionError as e:
63
+ formatter.print_error(str(e))
64
+ raise typer.Exit(1)
@@ -0,0 +1,89 @@
1
+ """o2 deposits commands - Deposit management."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.cli import get_state
8
+ from o2_cli.client import O2Client
9
+ from o2_cli.config import load_config, get_active_profile
10
+ from o2_cli.exceptions import APIError, ConnectionError
11
+ from o2_cli.output import OutputFormatter
12
+
13
+ app = typer.Typer(help="Deposit management")
14
+
15
+
16
+ @app.command("address")
17
+ def address(
18
+ chain: str = typer.Option(
19
+ "base", "--chain", "-c", help="Blockchain network (base/arbitrum/ethereum)"
20
+ ),
21
+ ):
22
+ """Get or activate your deposit address."""
23
+ asyncio.run(_address(chain))
24
+
25
+
26
+ async def _address(chain: str):
27
+ state = get_state()
28
+ formatter = OutputFormatter(json_mode=state["json_output"])
29
+ config = load_config()
30
+ profile = get_active_profile(config)
31
+ api_url = state.get("api_url") or profile.api_url
32
+ timeout = state.get("timeout") or profile.timeout
33
+
34
+ if not profile.token and profile.auth_type == "jwt":
35
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
36
+ raise typer.Exit(1)
37
+
38
+ async with O2Client(api_url, timeout) as client:
39
+ client.set_jwt(profile.token)
40
+ if profile.api_key_id:
41
+ client.set_api_key(profile.api_key_id, profile.api_secret)
42
+ try:
43
+ data = await client.get_deposit_address(chain)
44
+ formatter.print_raw(data)
45
+ if not state["json_output"]:
46
+ deposit_addr = data.get("deposit_address", "")
47
+ if deposit_addr:
48
+ formatter.print_success(f"Deposit address: {deposit_addr}")
49
+ except APIError as e:
50
+ formatter.print_error(str(e), e.code)
51
+ raise typer.Exit(1)
52
+ except ConnectionError as e:
53
+ formatter.print_error(str(e))
54
+ raise typer.Exit(1)
55
+
56
+
57
+ @app.command("history")
58
+ def history(
59
+ limit: int = typer.Option(50, "--limit", "-n", help="Number of records"),
60
+ ):
61
+ """Show deposit history."""
62
+ asyncio.run(_history(limit))
63
+
64
+
65
+ async def _history(limit: int):
66
+ state = get_state()
67
+ formatter = OutputFormatter(json_mode=state["json_output"])
68
+ config = load_config()
69
+ profile = get_active_profile(config)
70
+ api_url = state.get("api_url") or profile.api_url
71
+ timeout = state.get("timeout") or profile.timeout
72
+
73
+ if not profile.token and profile.auth_type == "jwt":
74
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
75
+ raise typer.Exit(1)
76
+
77
+ async with O2Client(api_url, timeout) as client:
78
+ client.set_jwt(profile.token)
79
+ if profile.api_key_id:
80
+ client.set_api_key(profile.api_key_id, profile.api_secret)
81
+ try:
82
+ data = await client.get_deposit_history(limit)
83
+ formatter.print_raw(data)
84
+ except APIError as e:
85
+ formatter.print_error(str(e), e.code)
86
+ raise typer.Exit(1)
87
+ except ConnectionError as e:
88
+ formatter.print_error(str(e))
89
+ raise typer.Exit(1)
@@ -0,0 +1,73 @@
1
+ """o2 fees commands - Fee information."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.cli import get_state
8
+ from o2_cli.client import O2Client
9
+ from o2_cli.config import load_config, get_active_profile
10
+ from o2_cli.exceptions import APIError, ConnectionError
11
+ from o2_cli.output import OutputFormatter
12
+
13
+ app = typer.Typer(help="Fee information")
14
+
15
+
16
+ @app.command("rates")
17
+ def rates():
18
+ """Show current fee rates (public, no auth required)."""
19
+ asyncio.run(_rates())
20
+
21
+
22
+ async def _rates():
23
+ state = get_state()
24
+ formatter = OutputFormatter(json_mode=state["json_output"])
25
+ config = load_config()
26
+ profile = get_active_profile(config)
27
+ api_url = state.get("api_url") or profile.api_url
28
+ timeout = state.get("timeout") or profile.timeout
29
+
30
+ async with O2Client(api_url, timeout) as client:
31
+ try:
32
+ data = await client.get_fee_rates()
33
+ formatter.print_raw(data)
34
+ except APIError as e:
35
+ formatter.print_error(str(e), e.code)
36
+ raise typer.Exit(1)
37
+ except ConnectionError as e:
38
+ formatter.print_error(str(e))
39
+ raise typer.Exit(1)
40
+
41
+
42
+ @app.command("estimate")
43
+ def estimate(
44
+ amount: str = typer.Option(..., "--amount", "-a", help="Order amount (base units)"),
45
+ price: str = typer.Option(..., "--price", "-p", help="Order price"),
46
+ is_maker: bool = typer.Option(
47
+ False, "--is-maker", help="Calculate as maker order"
48
+ ),
49
+ ):
50
+ """Estimate fee for a hypothetical order."""
51
+ asyncio.run(_estimate(amount, price, is_maker))
52
+
53
+
54
+ async def _estimate(amount: str, price: str, is_maker: bool):
55
+ state = get_state()
56
+ formatter = OutputFormatter(json_mode=state["json_output"])
57
+ config = load_config()
58
+ profile = get_active_profile(config)
59
+ api_url = state.get("api_url") or profile.api_url
60
+ timeout = state.get("timeout") or profile.timeout
61
+
62
+ async with O2Client(api_url, timeout) as client:
63
+ try:
64
+ data = await client.estimate_fee(
65
+ base_amount=amount, price=price, is_maker=is_maker
66
+ )
67
+ formatter.print_raw(data)
68
+ except APIError as e:
69
+ formatter.print_error(str(e), e.code)
70
+ raise typer.Exit(1)
71
+ except ConnectionError as e:
72
+ formatter.print_error(str(e))
73
+ raise typer.Exit(1)
@@ -0,0 +1,129 @@
1
+ """o2 markets commands - Market data (public, no auth required)."""
2
+
3
+ import asyncio
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from o2_cli.cli import get_state
9
+ from o2_cli.client import O2Client
10
+ from o2_cli.config import load_config, get_active_profile
11
+ from o2_cli.exceptions import APIError, ConnectionError
12
+ from o2_cli.output import OutputFormatter
13
+
14
+ app = typer.Typer(help="Market data")
15
+
16
+
17
+ @app.command("list")
18
+ def list_markets():
19
+ """List all available markets."""
20
+ asyncio.run(_list_markets())
21
+
22
+
23
+ async def _list_markets():
24
+ state = get_state()
25
+ formatter = OutputFormatter(json_mode=state["json_output"])
26
+ config = load_config()
27
+ profile = get_active_profile(config)
28
+ api_url = state.get("api_url") or profile.api_url
29
+ timeout = state.get("timeout") or profile.timeout
30
+
31
+ async with O2Client(api_url, timeout) as client:
32
+ try:
33
+ data = await client.get_markets()
34
+ formatter.print_markets(data)
35
+ except APIError as e:
36
+ formatter.print_error(str(e), e.code)
37
+ raise typer.Exit(1)
38
+ except ConnectionError as e:
39
+ formatter.print_error(str(e))
40
+ raise typer.Exit(1)
41
+
42
+
43
+ @app.command("orderbook")
44
+ def orderbook(
45
+ market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
46
+ ):
47
+ """Show order book for a market."""
48
+ asyncio.run(_orderbook(market_id))
49
+
50
+
51
+ async def _orderbook(market_id: int):
52
+ state = get_state()
53
+ formatter = OutputFormatter(json_mode=state["json_output"])
54
+ config = load_config()
55
+ profile = get_active_profile(config)
56
+ api_url = state.get("api_url") or profile.api_url
57
+ timeout = state.get("timeout") or profile.timeout
58
+
59
+ async with O2Client(api_url, timeout) as client:
60
+ try:
61
+ data = await client.get_orderbook(market_id)
62
+ formatter.print_orderbook(data)
63
+ except APIError as e:
64
+ formatter.print_error(str(e), e.code)
65
+ raise typer.Exit(1)
66
+ except ConnectionError as e:
67
+ formatter.print_error(str(e))
68
+ raise typer.Exit(1)
69
+
70
+
71
+ @app.command("trades")
72
+ def trades(
73
+ market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
74
+ limit: int = typer.Option(50, "--limit", "-n", help="Number of trades"),
75
+ ):
76
+ """Show recent trades for a market."""
77
+ asyncio.run(_trades(market_id, limit))
78
+
79
+
80
+ async def _trades(market_id: int, limit: int):
81
+ state = get_state()
82
+ formatter = OutputFormatter(json_mode=state["json_output"])
83
+ config = load_config()
84
+ profile = get_active_profile(config)
85
+ api_url = state.get("api_url") or profile.api_url
86
+ timeout = state.get("timeout") or profile.timeout
87
+
88
+ async with O2Client(api_url, timeout) as client:
89
+ try:
90
+ data = await client.get_market_trades(market_id, limit)
91
+ formatter.print_raw(data)
92
+ except APIError as e:
93
+ formatter.print_error(str(e), e.code)
94
+ raise typer.Exit(1)
95
+ except ConnectionError as e:
96
+ formatter.print_error(str(e))
97
+ raise typer.Exit(1)
98
+
99
+
100
+ @app.command("candles")
101
+ def candles(
102
+ market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
103
+ interval: str = typer.Option(
104
+ "1h", "--interval", "-i", help="Candle interval (1m/5m/15m/1h/4h/1d)"
105
+ ),
106
+ limit: int = typer.Option(500, "--limit", "-n", help="Number of candles"),
107
+ ):
108
+ """Show candlestick data for a market."""
109
+ asyncio.run(_candles(market_id, interval, limit))
110
+
111
+
112
+ async def _candles(market_id: int, interval: str, limit: int):
113
+ state = get_state()
114
+ formatter = OutputFormatter(json_mode=state["json_output"])
115
+ config = load_config()
116
+ profile = get_active_profile(config)
117
+ api_url = state.get("api_url") or profile.api_url
118
+ timeout = state.get("timeout") or profile.timeout
119
+
120
+ async with O2Client(api_url, timeout) as client:
121
+ try:
122
+ data = await client.get_candles(market_id, interval, limit)
123
+ formatter.print_raw(data)
124
+ except APIError as e:
125
+ formatter.print_error(str(e), e.code)
126
+ raise typer.Exit(1)
127
+ except ConnectionError as e:
128
+ formatter.print_error(str(e))
129
+ raise typer.Exit(1)