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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""o2 positions commands - Position management."""
|
|
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="Position management")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("list")
|
|
18
|
+
def list_positions():
|
|
19
|
+
"""List all open positions."""
|
|
20
|
+
asyncio.run(_list_positions())
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def _list_positions():
|
|
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
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
32
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
33
|
+
raise typer.Exit(1)
|
|
34
|
+
|
|
35
|
+
async with O2Client(api_url, timeout) as client:
|
|
36
|
+
client.set_jwt(profile.token)
|
|
37
|
+
if profile.api_key_id:
|
|
38
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
39
|
+
try:
|
|
40
|
+
data = await client.get_positions()
|
|
41
|
+
formatter.print_positions(data)
|
|
42
|
+
except APIError as e:
|
|
43
|
+
formatter.print_error(str(e), e.code)
|
|
44
|
+
raise typer.Exit(1)
|
|
45
|
+
except ConnectionError as e:
|
|
46
|
+
formatter.print_error(str(e))
|
|
47
|
+
raise typer.Exit(1)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command("market")
|
|
51
|
+
def market(
|
|
52
|
+
market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
|
|
53
|
+
):
|
|
54
|
+
"""Show position for a specific market."""
|
|
55
|
+
asyncio.run(_market(market_id))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def _market(market_id: int):
|
|
59
|
+
state = get_state()
|
|
60
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
61
|
+
config = load_config()
|
|
62
|
+
profile = get_active_profile(config)
|
|
63
|
+
api_url = state.get("api_url") or profile.api_url
|
|
64
|
+
timeout = state.get("timeout") or profile.timeout
|
|
65
|
+
|
|
66
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
67
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
|
|
70
|
+
async with O2Client(api_url, timeout) as client:
|
|
71
|
+
client.set_jwt(profile.token)
|
|
72
|
+
if profile.api_key_id:
|
|
73
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
74
|
+
try:
|
|
75
|
+
data = await client.get_position_by_market(market_id)
|
|
76
|
+
formatter.print_positions(data)
|
|
77
|
+
except APIError as e:
|
|
78
|
+
formatter.print_error(str(e), e.code)
|
|
79
|
+
raise typer.Exit(1)
|
|
80
|
+
except ConnectionError as e:
|
|
81
|
+
formatter.print_error(str(e))
|
|
82
|
+
raise typer.Exit(1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.command("close")
|
|
86
|
+
def close(
|
|
87
|
+
position_id: str = typer.Option(
|
|
88
|
+
..., "--position-id", "-i", help="Position ID to close"
|
|
89
|
+
),
|
|
90
|
+
):
|
|
91
|
+
"""Close an open position."""
|
|
92
|
+
asyncio.run(_close(position_id))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def _close(position_id: str):
|
|
96
|
+
state = get_state()
|
|
97
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
98
|
+
config = load_config()
|
|
99
|
+
profile = get_active_profile(config)
|
|
100
|
+
api_url = state.get("api_url") or profile.api_url
|
|
101
|
+
timeout = state.get("timeout") or profile.timeout
|
|
102
|
+
|
|
103
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
104
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
105
|
+
raise typer.Exit(1)
|
|
106
|
+
|
|
107
|
+
async with O2Client(api_url, timeout) as client:
|
|
108
|
+
client.set_jwt(profile.token)
|
|
109
|
+
if profile.api_key_id:
|
|
110
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
111
|
+
try:
|
|
112
|
+
data = await client.close_position(position_id)
|
|
113
|
+
formatter.print_raw(data)
|
|
114
|
+
if not state["json_output"]:
|
|
115
|
+
formatter.print_success(f"Position {position_id} closed")
|
|
116
|
+
except APIError as e:
|
|
117
|
+
formatter.print_error(str(e), e.code)
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
except ConnectionError as e:
|
|
120
|
+
formatter.print_error(str(e))
|
|
121
|
+
raise typer.Exit(1)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@app.command("risk")
|
|
125
|
+
def risk(
|
|
126
|
+
market_id: int = typer.Option(
|
|
127
|
+
..., "--market-id", "-m", help="Market ID to check liquidation risk"
|
|
128
|
+
),
|
|
129
|
+
):
|
|
130
|
+
"""Check liquidation risk for a market position."""
|
|
131
|
+
asyncio.run(_risk(market_id))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def _risk(market_id: int):
|
|
135
|
+
state = get_state()
|
|
136
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
137
|
+
config = load_config()
|
|
138
|
+
profile = get_active_profile(config)
|
|
139
|
+
api_url = state.get("api_url") or profile.api_url
|
|
140
|
+
timeout = state.get("timeout") or profile.timeout
|
|
141
|
+
|
|
142
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
143
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
144
|
+
raise typer.Exit(1)
|
|
145
|
+
|
|
146
|
+
async with O2Client(api_url, timeout) as client:
|
|
147
|
+
client.set_jwt(profile.token)
|
|
148
|
+
if profile.api_key_id:
|
|
149
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
150
|
+
try:
|
|
151
|
+
data = await client.get_liquidation_risk(market_id)
|
|
152
|
+
formatter.print_raw(data)
|
|
153
|
+
except APIError as e:
|
|
154
|
+
formatter.print_error(str(e), e.code)
|
|
155
|
+
raise typer.Exit(1)
|
|
156
|
+
except ConnectionError as e:
|
|
157
|
+
formatter.print_error(str(e))
|
|
158
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""o2 settings commands - User settings 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="User settings")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("get")
|
|
17
|
+
def get():
|
|
18
|
+
"""Show current user settings."""
|
|
19
|
+
asyncio.run(_get())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def _get():
|
|
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_user_settings()
|
|
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("leverage")
|
|
50
|
+
def leverage(
|
|
51
|
+
market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
|
|
52
|
+
leverage: int = typer.Option(..., "--leverage", "-l", help="Leverage value"),
|
|
53
|
+
):
|
|
54
|
+
"""Set leverage for a market."""
|
|
55
|
+
asyncio.run(_leverage(market_id, leverage))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def _leverage(market_id: int, leverage_val: int):
|
|
59
|
+
state = get_state()
|
|
60
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
61
|
+
config = load_config()
|
|
62
|
+
profile = get_active_profile(config)
|
|
63
|
+
api_url = state.get("api_url") or profile.api_url
|
|
64
|
+
timeout = state.get("timeout") or profile.timeout
|
|
65
|
+
|
|
66
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
67
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
|
|
70
|
+
async with O2Client(api_url, timeout) as client:
|
|
71
|
+
client.set_jwt(profile.token)
|
|
72
|
+
if profile.api_key_id:
|
|
73
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
74
|
+
try:
|
|
75
|
+
data = await client.set_leverage(market_id, leverage_val)
|
|
76
|
+
formatter.print_raw(data)
|
|
77
|
+
if not state["json_output"]:
|
|
78
|
+
formatter.print_success(
|
|
79
|
+
f"Leverage set to {leverage_val}x for market {market_id}"
|
|
80
|
+
)
|
|
81
|
+
except APIError as e:
|
|
82
|
+
formatter.print_error(str(e), e.code)
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
except ConnectionError as e:
|
|
85
|
+
formatter.print_error(str(e))
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.command("margin-mode")
|
|
90
|
+
def margin_mode(
|
|
91
|
+
mode: str = typer.Option(
|
|
92
|
+
..., "--mode", "-m", help="Margin mode (cross/isolated)"
|
|
93
|
+
),
|
|
94
|
+
):
|
|
95
|
+
"""Set global margin mode."""
|
|
96
|
+
asyncio.run(_margin_mode(mode))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def _margin_mode(mode: str):
|
|
100
|
+
state = get_state()
|
|
101
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
102
|
+
config = load_config()
|
|
103
|
+
profile = get_active_profile(config)
|
|
104
|
+
api_url = state.get("api_url") or profile.api_url
|
|
105
|
+
timeout = state.get("timeout") or profile.timeout
|
|
106
|
+
|
|
107
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
108
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
|
|
111
|
+
if mode not in ("cross", "isolated"):
|
|
112
|
+
formatter.print_error("Margin mode must be 'cross' or 'isolated'.")
|
|
113
|
+
raise typer.Exit(1)
|
|
114
|
+
|
|
115
|
+
async with O2Client(api_url, timeout) as client:
|
|
116
|
+
client.set_jwt(profile.token)
|
|
117
|
+
if profile.api_key_id:
|
|
118
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
119
|
+
try:
|
|
120
|
+
data = await client.set_margin_mode(mode)
|
|
121
|
+
formatter.print_raw(data)
|
|
122
|
+
if not state["json_output"]:
|
|
123
|
+
formatter.print_success(f"Margin mode set to {mode}")
|
|
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)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""o2 setup commands - Install skill files for vibe coding tools."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from o2_cli.setup import (
|
|
9
|
+
interactive_setup,
|
|
10
|
+
setup_tool,
|
|
11
|
+
update_skills,
|
|
12
|
+
show_skill,
|
|
13
|
+
show_status,
|
|
14
|
+
ALL_TOOLS,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(help="Setup and configure O2 CLI for your coding tool")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.callback(invoke_without_command=True)
|
|
21
|
+
def setup_main(
|
|
22
|
+
tool: Optional[str] = typer.Option(
|
|
23
|
+
None, "--tool", "-t",
|
|
24
|
+
help=f"Tool name ({', '.join(t.name for t in ALL_TOOLS)}). Interactive if omitted."
|
|
25
|
+
),
|
|
26
|
+
scope: str = typer.Option(
|
|
27
|
+
"project", "--scope", "-s",
|
|
28
|
+
help="Install scope: global or project"
|
|
29
|
+
),
|
|
30
|
+
update: bool = typer.Option(
|
|
31
|
+
False, "--update", "-u",
|
|
32
|
+
help="Re-install skill files for all previously configured tools"
|
|
33
|
+
),
|
|
34
|
+
show: bool = typer.Option(
|
|
35
|
+
False, "--show-skill",
|
|
36
|
+
help="Print the full skill content"
|
|
37
|
+
),
|
|
38
|
+
status: bool = typer.Option(
|
|
39
|
+
False, "--status",
|
|
40
|
+
help="Show current setup status"
|
|
41
|
+
),
|
|
42
|
+
):
|
|
43
|
+
"""Setup O2 CLI for your vibe coding tool.
|
|
44
|
+
|
|
45
|
+
\b
|
|
46
|
+
Interactive mode:
|
|
47
|
+
o2 setup
|
|
48
|
+
|
|
49
|
+
\b
|
|
50
|
+
Non-interactive (for agents/CI):
|
|
51
|
+
o2 setup --tool claude-code --scope global
|
|
52
|
+
o2 setup --tool cursor --scope project
|
|
53
|
+
|
|
54
|
+
\b
|
|
55
|
+
Update all configured tools:
|
|
56
|
+
o2 setup --update
|
|
57
|
+
|
|
58
|
+
\b
|
|
59
|
+
Available tools: claude-code, cursor, codex, windsurf, cline, trae
|
|
60
|
+
"""
|
|
61
|
+
if show:
|
|
62
|
+
show_skill()
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
if status:
|
|
66
|
+
show_status()
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if update:
|
|
70
|
+
update_skills()
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if tool:
|
|
74
|
+
setup_tool(tool, scope)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Default: interactive wizard
|
|
78
|
+
interactive_setup()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""o2 trades commands - Trade history."""
|
|
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="Trade history")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("list")
|
|
18
|
+
def list_trades(
|
|
19
|
+
market_id: Optional[int] = typer.Option(
|
|
20
|
+
None, "--market-id", "-m", help="Filter by market ID"
|
|
21
|
+
),
|
|
22
|
+
limit: int = typer.Option(50, "--limit", "-n", help="Number of records"),
|
|
23
|
+
skip: int = typer.Option(0, "--skip", help="Number of records to skip"),
|
|
24
|
+
):
|
|
25
|
+
"""List trade history."""
|
|
26
|
+
asyncio.run(_list_trades(market_id, limit, skip))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _list_trades(market_id: Optional[int], limit: int, skip: int):
|
|
30
|
+
state = get_state()
|
|
31
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
32
|
+
config = load_config()
|
|
33
|
+
profile = get_active_profile(config)
|
|
34
|
+
api_url = state.get("api_url") or profile.api_url
|
|
35
|
+
timeout = state.get("timeout") or profile.timeout
|
|
36
|
+
|
|
37
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
38
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
39
|
+
raise typer.Exit(1)
|
|
40
|
+
|
|
41
|
+
async with O2Client(api_url, timeout) as client:
|
|
42
|
+
client.set_jwt(profile.token)
|
|
43
|
+
if profile.api_key_id:
|
|
44
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
45
|
+
try:
|
|
46
|
+
data = await client.get_trades(skip=skip, limit=limit, market_id=market_id)
|
|
47
|
+
formatter.print_raw(data)
|
|
48
|
+
except APIError as e:
|
|
49
|
+
formatter.print_error(str(e), e.code)
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
except ConnectionError as e:
|
|
52
|
+
formatter.print_error(str(e))
|
|
53
|
+
raise typer.Exit(1)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.command("summary")
|
|
57
|
+
def summary():
|
|
58
|
+
"""Show trade summary statistics."""
|
|
59
|
+
asyncio.run(_summary())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def _summary():
|
|
63
|
+
state = get_state()
|
|
64
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
65
|
+
config = load_config()
|
|
66
|
+
profile = get_active_profile(config)
|
|
67
|
+
api_url = state.get("api_url") or profile.api_url
|
|
68
|
+
timeout = state.get("timeout") or profile.timeout
|
|
69
|
+
|
|
70
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
71
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
72
|
+
raise typer.Exit(1)
|
|
73
|
+
|
|
74
|
+
async with O2Client(api_url, timeout) as client:
|
|
75
|
+
client.set_jwt(profile.token)
|
|
76
|
+
if profile.api_key_id:
|
|
77
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
78
|
+
try:
|
|
79
|
+
data = await client.get_trade_summary()
|
|
80
|
+
formatter.print_raw(data)
|
|
81
|
+
except APIError as e:
|
|
82
|
+
formatter.print_error(str(e), e.code)
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
except ConnectionError as e:
|
|
85
|
+
formatter.print_error(str(e))
|
|
86
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""o2 withdrawals commands - Withdrawal 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="Withdrawal management")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command("create")
|
|
17
|
+
def create(
|
|
18
|
+
amount: str = typer.Option(..., "--amount", "-a", help="Withdrawal amount (USDC)"),
|
|
19
|
+
address: str = typer.Option(
|
|
20
|
+
..., "--address", "-d", help="Destination wallet address"
|
|
21
|
+
),
|
|
22
|
+
chain: str = typer.Option(
|
|
23
|
+
"ethereum", "--chain", "-c", help="Blockchain network (ethereum/base/arbitrum)"
|
|
24
|
+
),
|
|
25
|
+
currency: str = typer.Option(
|
|
26
|
+
"USDC", "--currency", help="Currency to withdraw"
|
|
27
|
+
),
|
|
28
|
+
):
|
|
29
|
+
"""Create a new withdrawal request."""
|
|
30
|
+
asyncio.run(_create(amount, address, chain, currency))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def _create(amount: str, address: str, chain: str, currency: str):
|
|
34
|
+
state = get_state()
|
|
35
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
36
|
+
config = load_config()
|
|
37
|
+
profile = get_active_profile(config)
|
|
38
|
+
api_url = state.get("api_url") or profile.api_url
|
|
39
|
+
timeout = state.get("timeout") or profile.timeout
|
|
40
|
+
|
|
41
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
42
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
|
|
45
|
+
async with O2Client(api_url, timeout) as client:
|
|
46
|
+
client.set_jwt(profile.token)
|
|
47
|
+
if profile.api_key_id:
|
|
48
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
49
|
+
try:
|
|
50
|
+
data = await client.create_withdrawal(
|
|
51
|
+
amount=amount, address=address, chain=chain, currency=currency
|
|
52
|
+
)
|
|
53
|
+
formatter.print_raw(data)
|
|
54
|
+
if not state["json_output"]:
|
|
55
|
+
formatter.print_success(
|
|
56
|
+
f"Withdrawal request created: {amount} {currency} to {address[:10]}..."
|
|
57
|
+
)
|
|
58
|
+
except APIError as e:
|
|
59
|
+
formatter.print_error(str(e), e.code)
|
|
60
|
+
raise typer.Exit(1)
|
|
61
|
+
except ConnectionError as e:
|
|
62
|
+
formatter.print_error(str(e))
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@app.command("status")
|
|
67
|
+
def status(
|
|
68
|
+
id: str = typer.Option(
|
|
69
|
+
..., "--id", "-i", help="Withdrawal ID to check"
|
|
70
|
+
),
|
|
71
|
+
):
|
|
72
|
+
"""Check withdrawal status."""
|
|
73
|
+
asyncio.run(_status(id))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def _status(withdrawal_id: str):
|
|
77
|
+
state = get_state()
|
|
78
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
79
|
+
config = load_config()
|
|
80
|
+
profile = get_active_profile(config)
|
|
81
|
+
api_url = state.get("api_url") or profile.api_url
|
|
82
|
+
timeout = state.get("timeout") or profile.timeout
|
|
83
|
+
|
|
84
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
85
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
|
|
88
|
+
async with O2Client(api_url, timeout) as client:
|
|
89
|
+
client.set_jwt(profile.token)
|
|
90
|
+
if profile.api_key_id:
|
|
91
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
92
|
+
try:
|
|
93
|
+
data = await client.get_withdrawal(withdrawal_id)
|
|
94
|
+
formatter.print_raw(data)
|
|
95
|
+
except APIError as e:
|
|
96
|
+
formatter.print_error(str(e), e.code)
|
|
97
|
+
raise typer.Exit(1)
|
|
98
|
+
except ConnectionError as e:
|
|
99
|
+
formatter.print_error(str(e))
|
|
100
|
+
raise typer.Exit(1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command("cancel")
|
|
104
|
+
def cancel(
|
|
105
|
+
id: str = typer.Option(
|
|
106
|
+
..., "--id", "-i", help="Withdrawal ID to cancel"
|
|
107
|
+
),
|
|
108
|
+
):
|
|
109
|
+
"""Cancel a pending withdrawal."""
|
|
110
|
+
asyncio.run(_cancel(id))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def _cancel(withdrawal_id: str):
|
|
114
|
+
state = get_state()
|
|
115
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
116
|
+
config = load_config()
|
|
117
|
+
profile = get_active_profile(config)
|
|
118
|
+
api_url = state.get("api_url") or profile.api_url
|
|
119
|
+
timeout = state.get("timeout") or profile.timeout
|
|
120
|
+
|
|
121
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
122
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
123
|
+
raise typer.Exit(1)
|
|
124
|
+
|
|
125
|
+
async with O2Client(api_url, timeout) as client:
|
|
126
|
+
client.set_jwt(profile.token)
|
|
127
|
+
if profile.api_key_id:
|
|
128
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
129
|
+
try:
|
|
130
|
+
data = await client.cancel_withdrawal(withdrawal_id)
|
|
131
|
+
formatter.print_raw(data)
|
|
132
|
+
if not state["json_output"]:
|
|
133
|
+
formatter.print_success(f"Withdrawal {withdrawal_id} cancelled")
|
|
134
|
+
except APIError as e:
|
|
135
|
+
formatter.print_error(str(e), e.code)
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
except ConnectionError as e:
|
|
138
|
+
formatter.print_error(str(e))
|
|
139
|
+
raise typer.Exit(1)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.command("list")
|
|
143
|
+
def list_withdrawals(
|
|
144
|
+
limit: int = typer.Option(20, "--limit", "-n", help="Number of records"),
|
|
145
|
+
offset: int = typer.Option(0, "--offset", help="Offset"),
|
|
146
|
+
):
|
|
147
|
+
"""List withdrawal history."""
|
|
148
|
+
asyncio.run(_list_withdrawals(limit, offset))
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def _list_withdrawals(limit: int, offset: int):
|
|
152
|
+
state = get_state()
|
|
153
|
+
formatter = OutputFormatter(json_mode=state["json_output"])
|
|
154
|
+
config = load_config()
|
|
155
|
+
profile = get_active_profile(config)
|
|
156
|
+
api_url = state.get("api_url") or profile.api_url
|
|
157
|
+
timeout = state.get("timeout") or profile.timeout
|
|
158
|
+
|
|
159
|
+
if not profile.token and profile.auth_type == "jwt":
|
|
160
|
+
formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
|
|
161
|
+
raise typer.Exit(1)
|
|
162
|
+
|
|
163
|
+
async with O2Client(api_url, timeout) as client:
|
|
164
|
+
client.set_jwt(profile.token)
|
|
165
|
+
if profile.api_key_id:
|
|
166
|
+
client.set_api_key(profile.api_key_id, profile.api_secret)
|
|
167
|
+
try:
|
|
168
|
+
data = await client.get_withdrawal_history(limit, offset)
|
|
169
|
+
formatter.print_raw(data)
|
|
170
|
+
except APIError as e:
|
|
171
|
+
formatter.print_error(str(e), e.code)
|
|
172
|
+
raise typer.Exit(1)
|
|
173
|
+
except ConnectionError as e:
|
|
174
|
+
formatter.print_error(str(e))
|
|
175
|
+
raise typer.Exit(1)
|