@agentlayer.tech/wallet 0.1.15 → 0.1.16
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.
- package/.openclaw/extensions/agent-wallet/dist/index.js +117 -1
- package/.openclaw/extensions/agent-wallet/index.ts +117 -1
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +4 -0
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/.openclaw/extensions/pay-bridge/package.json +1 -1
- package/CHANGELOG.md +14 -0
- package/RELEASING.md +97 -0
- package/agent-wallet/.env.example +5 -0
- package/agent-wallet/README.md +24 -0
- package/agent-wallet/agent_wallet/config.py +4 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +504 -0
- package/agent-wallet/agent_wallet/providers/flash.py +186 -0
- package/agent-wallet/agent_wallet/providers/flash_sdk_bridge.py +251 -0
- package/agent-wallet/agent_wallet/transaction_policy.py +79 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +78 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +623 -1
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/flash-sdk-bridge/README.md +33 -0
- package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +1179 -0
- package/agent-wallet/scripts/flash-sdk-bridge/package-lock.json +2377 -0
- package/agent-wallet/scripts/flash-sdk-bridge/package.json +12 -0
- package/agent-wallet/scripts/install_agent_wallet.py +46 -11
- package/agent-wallet/scripts/install_openclaw_local_config.py +4 -0
- package/package.json +2 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Flash Trade provider helpers for perps market and position reads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from agent_wallet.config import settings
|
|
11
|
+
from agent_wallet.exceptions import ProviderError
|
|
12
|
+
from agent_wallet.http_client import get_client
|
|
13
|
+
|
|
14
|
+
PROVIDER_NAME = "flash-trade"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _headers() -> dict[str, str]:
|
|
18
|
+
return {"Accept": "application/json"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _gateway_base_url() -> str:
|
|
22
|
+
return os.getenv("PROVIDER_GATEWAY_URL", settings.provider_gateway_url).strip().rstrip("/")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _gateway_headers() -> dict[str, str]:
|
|
26
|
+
headers = {"Accept": "application/json"}
|
|
27
|
+
bearer = os.getenv(
|
|
28
|
+
"PROVIDER_GATEWAY_BEARER_TOKEN",
|
|
29
|
+
settings.provider_gateway_bearer_token,
|
|
30
|
+
).strip()
|
|
31
|
+
if bearer:
|
|
32
|
+
headers["Authorization"] = f"Bearer {bearer}"
|
|
33
|
+
return headers
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _gateway_enabled() -> bool:
|
|
37
|
+
return bool(_gateway_base_url())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _direct_base_url() -> str:
|
|
41
|
+
return os.getenv("FLASH_API_BASE_URL", settings.flash_api_base_url).strip().rstrip("/")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _direct_enabled() -> bool:
|
|
45
|
+
return bool(_direct_base_url())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _route_missing(status_code: int, payload: Any) -> bool:
|
|
49
|
+
if status_code == 404:
|
|
50
|
+
return True
|
|
51
|
+
if isinstance(payload, dict):
|
|
52
|
+
message = str(payload.get("error") or payload.get("message") or "").lower()
|
|
53
|
+
if "not found" in message or "unknown route" in message:
|
|
54
|
+
return True
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def _request_json(
|
|
59
|
+
url: str,
|
|
60
|
+
*,
|
|
61
|
+
params: dict[str, Any] | None,
|
|
62
|
+
headers: dict[str, str],
|
|
63
|
+
) -> tuple[int, Any]:
|
|
64
|
+
client = get_client()
|
|
65
|
+
try:
|
|
66
|
+
response = await client.get(url, params=params, headers=headers)
|
|
67
|
+
except httpx.HTTPError as exc:
|
|
68
|
+
raise ProviderError(
|
|
69
|
+
PROVIDER_NAME,
|
|
70
|
+
f"HTTP request failed: {exc}",
|
|
71
|
+
) from exc
|
|
72
|
+
if not response.content:
|
|
73
|
+
return response.status_code, {}
|
|
74
|
+
try:
|
|
75
|
+
return response.status_code, response.json()
|
|
76
|
+
except ValueError:
|
|
77
|
+
return response.status_code, response.text[:500]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _unwrap_response(
|
|
81
|
+
status_code: int,
|
|
82
|
+
payload: Any,
|
|
83
|
+
*,
|
|
84
|
+
operation: str,
|
|
85
|
+
) -> Any:
|
|
86
|
+
if isinstance(payload, dict) and payload.get("ok") is False:
|
|
87
|
+
message = str(payload.get("error") or payload.get("message") or f"{operation} failed.")
|
|
88
|
+
raise ProviderError(PROVIDER_NAME, f"{operation} failed: {message}")
|
|
89
|
+
|
|
90
|
+
if status_code != 200:
|
|
91
|
+
message = payload
|
|
92
|
+
if isinstance(payload, dict):
|
|
93
|
+
message = payload.get("error") or payload.get("message") or payload
|
|
94
|
+
raise ProviderError(PROVIDER_NAME, f"{operation} failed: {message}")
|
|
95
|
+
|
|
96
|
+
return payload
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def _get_with_fallback(
|
|
100
|
+
path: str,
|
|
101
|
+
*,
|
|
102
|
+
params: dict[str, Any] | None,
|
|
103
|
+
operation: str,
|
|
104
|
+
) -> Any:
|
|
105
|
+
if _gateway_enabled():
|
|
106
|
+
status_code, payload = await _request_json(
|
|
107
|
+
f"{_gateway_base_url()}{path}",
|
|
108
|
+
params=params,
|
|
109
|
+
headers=_gateway_headers(),
|
|
110
|
+
)
|
|
111
|
+
if not _route_missing(status_code, payload):
|
|
112
|
+
return _unwrap_response(status_code, payload, operation=operation)
|
|
113
|
+
|
|
114
|
+
if _direct_enabled():
|
|
115
|
+
direct_variants = [path]
|
|
116
|
+
if path == "/v1/flash/perps/markets":
|
|
117
|
+
direct_variants.append("/markets")
|
|
118
|
+
elif path == "/v1/flash/perps/positions":
|
|
119
|
+
direct_variants.append("/positions")
|
|
120
|
+
last_status = 404
|
|
121
|
+
last_payload: Any = {"error": "not found"}
|
|
122
|
+
for direct_path in direct_variants:
|
|
123
|
+
status_code, payload = await _request_json(
|
|
124
|
+
f"{_direct_base_url()}{direct_path}",
|
|
125
|
+
params=params,
|
|
126
|
+
headers=_headers(),
|
|
127
|
+
)
|
|
128
|
+
last_status = status_code
|
|
129
|
+
last_payload = payload
|
|
130
|
+
if _route_missing(status_code, payload) and direct_path != direct_variants[-1]:
|
|
131
|
+
continue
|
|
132
|
+
return _unwrap_response(status_code, payload, operation=operation)
|
|
133
|
+
return _unwrap_response(last_status, last_payload, operation=operation)
|
|
134
|
+
|
|
135
|
+
raise ProviderError(
|
|
136
|
+
PROVIDER_NAME,
|
|
137
|
+
(
|
|
138
|
+
f"{operation} is not configured. "
|
|
139
|
+
"Expose Flash routes on PROVIDER_GATEWAY_URL or set FLASH_API_BASE_URL."
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _normalize_named_list_response(
|
|
145
|
+
data: Any,
|
|
146
|
+
*,
|
|
147
|
+
key: str,
|
|
148
|
+
) -> dict[str, Any]:
|
|
149
|
+
if isinstance(data, list):
|
|
150
|
+
return {key: data}
|
|
151
|
+
if isinstance(data, dict):
|
|
152
|
+
items = data.get(key)
|
|
153
|
+
if isinstance(items, list):
|
|
154
|
+
return data
|
|
155
|
+
fallback = data.get("data")
|
|
156
|
+
if isinstance(fallback, list):
|
|
157
|
+
normalized = dict(data)
|
|
158
|
+
normalized[key] = fallback
|
|
159
|
+
return normalized
|
|
160
|
+
raise ProviderError(PROVIDER_NAME, f"Unexpected {key} response from Flash Trade.")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def fetch_markets(*, pool_name: str | None = None) -> dict[str, Any]:
|
|
164
|
+
params = {"pool_name": pool_name} if pool_name else None
|
|
165
|
+
data = await _get_with_fallback(
|
|
166
|
+
"/v1/flash/perps/markets",
|
|
167
|
+
params=params,
|
|
168
|
+
operation="Flash Trade market lookup",
|
|
169
|
+
)
|
|
170
|
+
return _normalize_named_list_response(data, key="markets")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def fetch_positions(
|
|
174
|
+
*,
|
|
175
|
+
owner: str,
|
|
176
|
+
pool_name: str | None = None,
|
|
177
|
+
) -> dict[str, Any]:
|
|
178
|
+
params: dict[str, Any] = {"owner": owner}
|
|
179
|
+
if pool_name:
|
|
180
|
+
params["pool_name"] = pool_name
|
|
181
|
+
data = await _get_with_fallback(
|
|
182
|
+
"/v1/flash/perps/positions",
|
|
183
|
+
params=params,
|
|
184
|
+
operation="Flash Trade position lookup",
|
|
185
|
+
)
|
|
186
|
+
return _normalize_named_list_response(data, key="positions")
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Local bridge contract for Flash SDK-backed preview generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shlex
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from agent_wallet.config import resolve_solana_rpc_url, settings
|
|
12
|
+
from agent_wallet.exceptions import ProviderError
|
|
13
|
+
|
|
14
|
+
PROVIDER_NAME = "flash-sdk-bridge"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _bridge_command() -> list[str]:
|
|
18
|
+
raw = os.getenv("FLASH_SDK_BRIDGE_COMMAND", settings.flash_sdk_bridge_command).strip()
|
|
19
|
+
if not raw:
|
|
20
|
+
raise ProviderError(
|
|
21
|
+
PROVIDER_NAME,
|
|
22
|
+
"FLASH_SDK_BRIDGE_COMMAND is not configured.",
|
|
23
|
+
)
|
|
24
|
+
try:
|
|
25
|
+
command = shlex.split(raw)
|
|
26
|
+
except ValueError as exc:
|
|
27
|
+
raise ProviderError(PROVIDER_NAME, f"Invalid FLASH_SDK_BRIDGE_COMMAND: {exc}") from exc
|
|
28
|
+
if not command:
|
|
29
|
+
raise ProviderError(PROVIDER_NAME, "FLASH_SDK_BRIDGE_COMMAND is empty.")
|
|
30
|
+
return command
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _bridge_timeout_seconds() -> float:
|
|
34
|
+
raw = os.getenv(
|
|
35
|
+
"FLASH_SDK_BRIDGE_TIMEOUT_SECONDS",
|
|
36
|
+
str(settings.flash_sdk_bridge_timeout_seconds),
|
|
37
|
+
).strip()
|
|
38
|
+
try:
|
|
39
|
+
timeout = float(raw)
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
raise ProviderError(
|
|
42
|
+
PROVIDER_NAME,
|
|
43
|
+
"FLASH_SDK_BRIDGE_TIMEOUT_SECONDS must be numeric.",
|
|
44
|
+
) from exc
|
|
45
|
+
return max(timeout, 1.0)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _bridge_env(payload: dict[str, Any]) -> dict[str, str]:
|
|
49
|
+
env = os.environ.copy()
|
|
50
|
+
|
|
51
|
+
bridge_mode = env.get("FLASH_SDK_BRIDGE_MODE", "").strip() or settings.flash_sdk_bridge_mode.strip()
|
|
52
|
+
if bridge_mode:
|
|
53
|
+
env["FLASH_SDK_BRIDGE_MODE"] = bridge_mode
|
|
54
|
+
|
|
55
|
+
if not env.get("SOLANA_RPC_URL", "").strip() and not env.get("RPC_URL", "").strip():
|
|
56
|
+
network = str(payload.get("network") or settings.solana_network or "mainnet").strip()
|
|
57
|
+
resolved_rpc_url = resolve_solana_rpc_url(network, settings.solana_rpc_url)
|
|
58
|
+
if resolved_rpc_url:
|
|
59
|
+
env["SOLANA_RPC_URL"] = resolved_rpc_url
|
|
60
|
+
|
|
61
|
+
return env
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _unwrap_bridge_payload(payload: Any, *, operation: str) -> dict[str, Any]:
|
|
65
|
+
if not isinstance(payload, dict):
|
|
66
|
+
raise ProviderError(PROVIDER_NAME, f"{operation} returned a non-object response.")
|
|
67
|
+
if payload.get("ok") is False:
|
|
68
|
+
message = str(payload.get("error") or f"{operation} failed.")
|
|
69
|
+
raise ProviderError(PROVIDER_NAME, message)
|
|
70
|
+
data = payload.get("preview")
|
|
71
|
+
if isinstance(data, dict):
|
|
72
|
+
return data
|
|
73
|
+
data = payload.get("prepared")
|
|
74
|
+
if isinstance(data, dict):
|
|
75
|
+
return data
|
|
76
|
+
if isinstance(payload.get("data"), dict):
|
|
77
|
+
return dict(payload["data"])
|
|
78
|
+
return dict(payload)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def _call_bridge(payload: dict[str, Any]) -> dict[str, Any]:
|
|
82
|
+
command = _bridge_command()
|
|
83
|
+
env = _bridge_env(payload)
|
|
84
|
+
try:
|
|
85
|
+
process = await asyncio.create_subprocess_exec(
|
|
86
|
+
*command,
|
|
87
|
+
stdin=asyncio.subprocess.PIPE,
|
|
88
|
+
stdout=asyncio.subprocess.PIPE,
|
|
89
|
+
stderr=asyncio.subprocess.PIPE,
|
|
90
|
+
env=env,
|
|
91
|
+
)
|
|
92
|
+
except OSError as exc:
|
|
93
|
+
raise ProviderError(PROVIDER_NAME, f"Could not start Flash SDK bridge: {exc}") from exc
|
|
94
|
+
|
|
95
|
+
stdin_bytes = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
96
|
+
timeout = _bridge_timeout_seconds()
|
|
97
|
+
try:
|
|
98
|
+
stdout, stderr = await asyncio.wait_for(
|
|
99
|
+
process.communicate(stdin_bytes),
|
|
100
|
+
timeout=timeout,
|
|
101
|
+
)
|
|
102
|
+
except asyncio.TimeoutError as exc:
|
|
103
|
+
process.kill()
|
|
104
|
+
await process.wait()
|
|
105
|
+
raise ProviderError(
|
|
106
|
+
PROVIDER_NAME,
|
|
107
|
+
f"Flash SDK bridge timed out after {timeout:.1f}s.",
|
|
108
|
+
) from exc
|
|
109
|
+
|
|
110
|
+
if process.returncode != 0:
|
|
111
|
+
message = stderr.decode("utf-8", errors="replace").strip() or stdout.decode(
|
|
112
|
+
"utf-8",
|
|
113
|
+
errors="replace",
|
|
114
|
+
).strip()
|
|
115
|
+
raise ProviderError(
|
|
116
|
+
PROVIDER_NAME,
|
|
117
|
+
f"Flash SDK bridge failed with exit code {process.returncode}: {message[:500]}",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
decoded = json.loads(stdout.decode("utf-8"))
|
|
122
|
+
except json.JSONDecodeError as exc:
|
|
123
|
+
raise ProviderError(
|
|
124
|
+
PROVIDER_NAME,
|
|
125
|
+
"Flash SDK bridge returned invalid JSON.",
|
|
126
|
+
) from exc
|
|
127
|
+
return decoded
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def preview_open_position_same_collateral(
|
|
131
|
+
*,
|
|
132
|
+
owner: str,
|
|
133
|
+
pool_name: str,
|
|
134
|
+
market_symbol: str,
|
|
135
|
+
collateral_symbol: str,
|
|
136
|
+
collateral_amount_raw: str,
|
|
137
|
+
leverage: str,
|
|
138
|
+
side: str,
|
|
139
|
+
network: str,
|
|
140
|
+
) -> dict[str, Any]:
|
|
141
|
+
payload = {
|
|
142
|
+
"action": "preview_open_position_same_collateral",
|
|
143
|
+
"owner": owner,
|
|
144
|
+
"pool_name": pool_name,
|
|
145
|
+
"market_symbol": market_symbol,
|
|
146
|
+
"collateral_symbol": collateral_symbol,
|
|
147
|
+
"collateral_amount_raw": collateral_amount_raw,
|
|
148
|
+
"leverage": leverage,
|
|
149
|
+
"side": side,
|
|
150
|
+
"network": network,
|
|
151
|
+
}
|
|
152
|
+
response = await _call_bridge(payload)
|
|
153
|
+
return _unwrap_bridge_payload(response, operation="Flash open-position preview")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
async def get_markets(
|
|
157
|
+
*,
|
|
158
|
+
pool_name: str | None,
|
|
159
|
+
network: str,
|
|
160
|
+
) -> dict[str, Any]:
|
|
161
|
+
payload: dict[str, Any] = {
|
|
162
|
+
"action": "get_markets",
|
|
163
|
+
"network": network,
|
|
164
|
+
}
|
|
165
|
+
if pool_name:
|
|
166
|
+
payload["pool_name"] = pool_name
|
|
167
|
+
response = await _call_bridge(payload)
|
|
168
|
+
return _unwrap_bridge_payload(response, operation="Flash market lookup")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
async def get_positions(
|
|
172
|
+
*,
|
|
173
|
+
owner: str,
|
|
174
|
+
pool_name: str | None,
|
|
175
|
+
network: str,
|
|
176
|
+
) -> dict[str, Any]:
|
|
177
|
+
payload: dict[str, Any] = {
|
|
178
|
+
"action": "get_positions",
|
|
179
|
+
"owner": owner,
|
|
180
|
+
"network": network,
|
|
181
|
+
}
|
|
182
|
+
if pool_name:
|
|
183
|
+
payload["pool_name"] = pool_name
|
|
184
|
+
response = await _call_bridge(payload)
|
|
185
|
+
return _unwrap_bridge_payload(response, operation="Flash position lookup")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
async def preview_close_position_same_collateral(
|
|
189
|
+
*,
|
|
190
|
+
owner: str,
|
|
191
|
+
pool_name: str,
|
|
192
|
+
market_symbol: str,
|
|
193
|
+
side: str,
|
|
194
|
+
network: str,
|
|
195
|
+
) -> dict[str, Any]:
|
|
196
|
+
payload = {
|
|
197
|
+
"action": "preview_close_position_same_collateral",
|
|
198
|
+
"owner": owner,
|
|
199
|
+
"pool_name": pool_name,
|
|
200
|
+
"market_symbol": market_symbol,
|
|
201
|
+
"side": side,
|
|
202
|
+
"network": network,
|
|
203
|
+
}
|
|
204
|
+
response = await _call_bridge(payload)
|
|
205
|
+
return _unwrap_bridge_payload(response, operation="Flash close-position preview")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def prepare_open_position_same_collateral(
|
|
209
|
+
*,
|
|
210
|
+
owner: str,
|
|
211
|
+
pool_name: str,
|
|
212
|
+
market_symbol: str,
|
|
213
|
+
collateral_symbol: str,
|
|
214
|
+
collateral_amount_raw: str,
|
|
215
|
+
leverage: str,
|
|
216
|
+
side: str,
|
|
217
|
+
network: str,
|
|
218
|
+
) -> dict[str, Any]:
|
|
219
|
+
payload = {
|
|
220
|
+
"action": "prepare_open_position_same_collateral",
|
|
221
|
+
"owner": owner,
|
|
222
|
+
"pool_name": pool_name,
|
|
223
|
+
"market_symbol": market_symbol,
|
|
224
|
+
"collateral_symbol": collateral_symbol,
|
|
225
|
+
"collateral_amount_raw": collateral_amount_raw,
|
|
226
|
+
"leverage": leverage,
|
|
227
|
+
"side": side,
|
|
228
|
+
"network": network,
|
|
229
|
+
}
|
|
230
|
+
response = await _call_bridge(payload)
|
|
231
|
+
return _unwrap_bridge_payload(response, operation="Flash open-position prepare")
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
async def prepare_close_position_same_collateral(
|
|
235
|
+
*,
|
|
236
|
+
owner: str,
|
|
237
|
+
pool_name: str,
|
|
238
|
+
market_symbol: str,
|
|
239
|
+
side: str,
|
|
240
|
+
network: str,
|
|
241
|
+
) -> dict[str, Any]:
|
|
242
|
+
payload = {
|
|
243
|
+
"action": "prepare_close_position_same_collateral",
|
|
244
|
+
"owner": owner,
|
|
245
|
+
"pool_name": pool_name,
|
|
246
|
+
"market_symbol": market_symbol,
|
|
247
|
+
"side": side,
|
|
248
|
+
"network": network,
|
|
249
|
+
}
|
|
250
|
+
response = await _call_bridge(payload)
|
|
251
|
+
return _unwrap_bridge_payload(response, operation="Flash close-position prepare")
|
|
@@ -767,3 +767,82 @@ def verify_provider_kamino_lend_transaction(
|
|
|
767
767
|
"action": action,
|
|
768
768
|
"verified": True,
|
|
769
769
|
}
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def verify_provider_flash_transaction(
|
|
773
|
+
message: Any,
|
|
774
|
+
*,
|
|
775
|
+
wallet_address: str,
|
|
776
|
+
market_address: str,
|
|
777
|
+
target_custody_address: str,
|
|
778
|
+
collateral_custody_address: str,
|
|
779
|
+
action: str,
|
|
780
|
+
expected_program_ids: list[str],
|
|
781
|
+
position_address: str | None = None,
|
|
782
|
+
collateral_mint: str | None = None,
|
|
783
|
+
loaded_addresses: list[str] | None = None,
|
|
784
|
+
) -> dict[str, Any]:
|
|
785
|
+
binding = _assert_basic_wallet_binding(
|
|
786
|
+
message,
|
|
787
|
+
wallet_address=wallet_address,
|
|
788
|
+
loaded_addresses=loaded_addresses,
|
|
789
|
+
)
|
|
790
|
+
keys = binding["account_keys"]
|
|
791
|
+
if market_address not in keys:
|
|
792
|
+
raise WalletBackendError(
|
|
793
|
+
f"{action} transaction does not reference the expected Flash market account."
|
|
794
|
+
)
|
|
795
|
+
if target_custody_address not in keys:
|
|
796
|
+
raise WalletBackendError(
|
|
797
|
+
f"{action} transaction does not reference the expected Flash target custody."
|
|
798
|
+
)
|
|
799
|
+
if collateral_custody_address not in keys:
|
|
800
|
+
raise WalletBackendError(
|
|
801
|
+
f"{action} transaction does not reference the expected Flash collateral custody."
|
|
802
|
+
)
|
|
803
|
+
if position_address and position_address not in keys:
|
|
804
|
+
raise WalletBackendError(
|
|
805
|
+
f"{action} transaction does not reference the expected Flash position account."
|
|
806
|
+
)
|
|
807
|
+
if collateral_mint and collateral_mint not in keys:
|
|
808
|
+
raise WalletBackendError(
|
|
809
|
+
f"{action} transaction does not reference the expected Flash collateral mint."
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
allowed_programs = CORE_PROGRAM_IDS | {pid for pid in expected_program_ids if pid}
|
|
813
|
+
program_ids = _program_ids(message, loaded_addresses)
|
|
814
|
+
unknown_program_ids = _assert_program_allowlist(
|
|
815
|
+
program_ids,
|
|
816
|
+
allowed_programs=allowed_programs,
|
|
817
|
+
label=action,
|
|
818
|
+
reject_unknown=False,
|
|
819
|
+
)
|
|
820
|
+
recognized_flash_program_ids = [
|
|
821
|
+
pid for pid in program_ids if pid in expected_program_ids
|
|
822
|
+
]
|
|
823
|
+
if not recognized_flash_program_ids:
|
|
824
|
+
raise WalletBackendError(
|
|
825
|
+
f"{action} transaction does not include the expected Flash program."
|
|
826
|
+
)
|
|
827
|
+
return {
|
|
828
|
+
"wallet_address": wallet_address,
|
|
829
|
+
"fee_payer": binding["fee_payer"],
|
|
830
|
+
"required_signer_keys": binding["required_signer_keys"],
|
|
831
|
+
"required_signature_count": binding["required_signature_count"],
|
|
832
|
+
"wallet_signer_index": binding["wallet_signer_index"],
|
|
833
|
+
"sponsored_fee_payer": binding["sponsored_fee_payer"],
|
|
834
|
+
"program_ids": program_ids,
|
|
835
|
+
"unknown_program_ids": unknown_program_ids,
|
|
836
|
+
"recognized_flash_program_ids": recognized_flash_program_ids,
|
|
837
|
+
"has_recognized_flash_program": True,
|
|
838
|
+
"non_core_program_ids": [pid for pid in program_ids if pid not in CORE_PROGRAM_IDS],
|
|
839
|
+
"account_key_count": len(keys),
|
|
840
|
+
"instruction_count": len(_compiled_instructions(message)),
|
|
841
|
+
"market_address": market_address,
|
|
842
|
+
"target_custody_address": target_custody_address,
|
|
843
|
+
"collateral_custody_address": collateral_custody_address,
|
|
844
|
+
"position_address": position_address,
|
|
845
|
+
"collateral_mint": collateral_mint,
|
|
846
|
+
"action": action,
|
|
847
|
+
"verified": True,
|
|
848
|
+
}
|
|
@@ -425,6 +425,84 @@ class AgentWalletBackend(ABC):
|
|
|
425
425
|
) -> dict[str, Any]:
|
|
426
426
|
raise WalletBackendError(f"{self.name} does not support Jupiter Earn earnings.")
|
|
427
427
|
|
|
428
|
+
async def get_flash_trade_markets(
|
|
429
|
+
self,
|
|
430
|
+
pool_name: str | None = None,
|
|
431
|
+
) -> dict[str, Any]:
|
|
432
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade market lookup.")
|
|
433
|
+
|
|
434
|
+
async def get_flash_trade_positions(
|
|
435
|
+
self,
|
|
436
|
+
owner: str | None = None,
|
|
437
|
+
pool_name: str | None = None,
|
|
438
|
+
) -> dict[str, Any]:
|
|
439
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position lookup.")
|
|
440
|
+
|
|
441
|
+
async def preview_flash_trade_open_position(
|
|
442
|
+
self,
|
|
443
|
+
*,
|
|
444
|
+
pool_name: str,
|
|
445
|
+
market_symbol: str,
|
|
446
|
+
collateral_symbol: str,
|
|
447
|
+
collateral_amount_raw: str,
|
|
448
|
+
leverage: str,
|
|
449
|
+
side: str,
|
|
450
|
+
) -> dict[str, Any]:
|
|
451
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position-open previews.")
|
|
452
|
+
|
|
453
|
+
async def preview_flash_trade_close_position(
|
|
454
|
+
self,
|
|
455
|
+
*,
|
|
456
|
+
pool_name: str,
|
|
457
|
+
market_symbol: str,
|
|
458
|
+
side: str,
|
|
459
|
+
) -> dict[str, Any]:
|
|
460
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position-close previews.")
|
|
461
|
+
|
|
462
|
+
async def prepare_flash_trade_open_position(
|
|
463
|
+
self,
|
|
464
|
+
*,
|
|
465
|
+
pool_name: str,
|
|
466
|
+
market_symbol: str,
|
|
467
|
+
collateral_symbol: str,
|
|
468
|
+
collateral_amount_raw: str,
|
|
469
|
+
leverage: str,
|
|
470
|
+
side: str,
|
|
471
|
+
) -> dict[str, Any]:
|
|
472
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position-open prepare.")
|
|
473
|
+
|
|
474
|
+
async def prepare_flash_trade_close_position(
|
|
475
|
+
self,
|
|
476
|
+
*,
|
|
477
|
+
pool_name: str,
|
|
478
|
+
market_symbol: str,
|
|
479
|
+
side: str,
|
|
480
|
+
) -> dict[str, Any]:
|
|
481
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position-close prepare.")
|
|
482
|
+
|
|
483
|
+
async def execute_flash_trade_open_position(
|
|
484
|
+
self,
|
|
485
|
+
*,
|
|
486
|
+
pool_name: str,
|
|
487
|
+
market_symbol: str,
|
|
488
|
+
collateral_symbol: str,
|
|
489
|
+
collateral_amount_raw: str,
|
|
490
|
+
leverage: str,
|
|
491
|
+
side: str,
|
|
492
|
+
approved_preview: dict[str, Any] | None = None,
|
|
493
|
+
) -> dict[str, Any]:
|
|
494
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position-open execute.")
|
|
495
|
+
|
|
496
|
+
async def execute_flash_trade_close_position(
|
|
497
|
+
self,
|
|
498
|
+
*,
|
|
499
|
+
pool_name: str,
|
|
500
|
+
market_symbol: str,
|
|
501
|
+
side: str,
|
|
502
|
+
approved_preview: dict[str, Any] | None = None,
|
|
503
|
+
) -> dict[str, Any]:
|
|
504
|
+
raise WalletBackendError(f"{self.name} does not support Flash Trade position-close execute.")
|
|
505
|
+
|
|
428
506
|
async def get_kamino_lend_markets(self) -> dict[str, Any]:
|
|
429
507
|
raise WalletBackendError(f"{self.name} does not support Kamino market lookup.")
|
|
430
508
|
|