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.
- o2_cli/__init__.py +30 -0
- o2_cli/__main__.py +6 -0
- o2_cli/cli.py +94 -0
- o2_cli/client.py +307 -0
- o2_cli/commands/__init__.py +1 -0
- o2_cli/commands/_helpers.py +65 -0
- o2_cli/commands/account.py +46 -0
- o2_cli/commands/admin.py +147 -0
- o2_cli/commands/auth.py +140 -0
- o2_cli/commands/balance.py +64 -0
- o2_cli/commands/deposits.py +89 -0
- o2_cli/commands/fees.py +73 -0
- o2_cli/commands/markets.py +129 -0
- o2_cli/commands/mm.py +182 -0
- o2_cli/commands/notifications.py +136 -0
- o2_cli/commands/orders.py +331 -0
- o2_cli/commands/positions.py +158 -0
- o2_cli/commands/settings.py +129 -0
- o2_cli/commands/setup_cmd.py +78 -0
- o2_cli/commands/trades.py +86 -0
- o2_cli/commands/withdrawals.py +175 -0
- o2_cli/config.py +87 -0
- o2_cli/exceptions.py +31 -0
- o2_cli/output.py +224 -0
- o2_cli/setup.py +561 -0
- o2_cli-0.1.0.dist-info/METADATA +141 -0
- o2_cli-0.1.0.dist-info/RECORD +31 -0
- o2_cli-0.1.0.dist-info/WHEEL +5 -0
- o2_cli-0.1.0.dist-info/entry_points.txt +2 -0
- o2_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- o2_cli-0.1.0.dist-info/top_level.txt +1 -0
o2_cli/commands/admin.py
ADDED
|
@@ -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)
|
o2_cli/commands/auth.py
ADDED
|
@@ -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)
|
o2_cli/commands/fees.py
ADDED
|
@@ -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)
|