wayfinder-paths 0.1.29__py3-none-any.whl → 0.1.31__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.
Potentially problematic release.
This version of wayfinder-paths might be problematic. Click here for more details.
- wayfinder_paths/adapters/boros_adapter/adapter.py +313 -12
- wayfinder_paths/adapters/boros_adapter/test_adapter.py +125 -14
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +17 -3
- wayfinder_paths/adapters/hyperliquid_adapter/exchange.py +5 -5
- wayfinder_paths/adapters/hyperliquid_adapter/local_signer.py +2 -33
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1 -1
- wayfinder_paths/adapters/hyperliquid_adapter/test_cancel_order.py +57 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_exchange_mid_prices.py +52 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_hyperliquid_sdk_live.py +64 -0
- wayfinder_paths/adapters/hyperliquid_adapter/util.py +9 -10
- wayfinder_paths/core/clients/PoolClient.py +1 -1
- wayfinder_paths/core/constants/hype_oft_abi.py +151 -0
- wayfinder_paths/core/strategies/Strategy.py +1 -2
- wayfinder_paths/mcp/tools/execute.py +48 -16
- wayfinder_paths/mcp/tools/hyperliquid.py +1 -13
- wayfinder_paths/mcp/tools/quotes.py +38 -124
- wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +24 -0
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +249 -29
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +125 -15
- wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +57 -201
- wayfinder_paths/strategies/boros_hype_strategy/constants.py +1 -152
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +29 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2 -0
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/manifest.yaml +33 -0
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +23 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +2 -0
- wayfinder_paths/tests/test_manifests.py +93 -0
- wayfinder_paths/tests/test_mcp_balances.py +73 -0
- wayfinder_paths/tests/test_mcp_discovery.py +34 -0
- wayfinder_paths/tests/test_mcp_execute.py +146 -0
- wayfinder_paths/tests/test_mcp_hyperliquid_execute.py +69 -0
- wayfinder_paths/tests/test_mcp_idempotency_store.py +14 -0
- wayfinder_paths/tests/test_mcp_quote_swap.py +60 -0
- wayfinder_paths/tests/test_mcp_run_script.py +47 -0
- wayfinder_paths/tests/test_mcp_tokens.py +49 -0
- wayfinder_paths/tests/test_mcp_utils.py +35 -0
- wayfinder_paths/tests/test_mcp_wallets.py +38 -0
- {wayfinder_paths-0.1.29.dist-info → wayfinder_paths-0.1.31.dist-info}/METADATA +2 -2
- {wayfinder_paths-0.1.29.dist-info → wayfinder_paths-0.1.31.dist-info}/RECORD +42 -25
- {wayfinder_paths-0.1.29.dist-info → wayfinder_paths-0.1.31.dist-info}/WHEEL +1 -1
- wayfinder_paths/core/types.py +0 -19
- {wayfinder_paths-0.1.29.dist-info → wayfinder_paths-0.1.31.dist-info}/LICENSE +0 -0
|
@@ -243,7 +243,7 @@ class LegConfirmer:
|
|
|
243
243
|
if asset_id is None:
|
|
244
244
|
continue
|
|
245
245
|
try:
|
|
246
|
-
await self.adapter.cancel_order(asset_id,
|
|
246
|
+
await self.adapter.cancel_order(asset_id, oid_int, address)
|
|
247
247
|
attempted.add(oid_int)
|
|
248
248
|
except Exception as exc:
|
|
249
249
|
logger.info(f"Cancel failed for oid {oid_int}: {exc}")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
from unittest.mock import AsyncMock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdapter
|
|
7
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.exchange import Exchange
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestHyperliquidCancelOrder:
|
|
11
|
+
@pytest.mark.asyncio
|
|
12
|
+
async def test_exchange_cancel_order_uses_int_oid(self):
|
|
13
|
+
ex = Exchange(
|
|
14
|
+
info=SimpleNamespace(),
|
|
15
|
+
util=SimpleNamespace(),
|
|
16
|
+
sign_callback=AsyncMock(return_value="0x"),
|
|
17
|
+
signing_type="eip712",
|
|
18
|
+
)
|
|
19
|
+
ex.sign_and_broadcast_hypecore = AsyncMock(return_value={"status": "ok"})
|
|
20
|
+
|
|
21
|
+
await ex.cancel_order(asset_id=10210, order_id=306356655993, address="0xabc")
|
|
22
|
+
|
|
23
|
+
args, _ = ex.sign_and_broadcast_hypecore.await_args
|
|
24
|
+
action = args[0]
|
|
25
|
+
assert action["type"] == "cancel"
|
|
26
|
+
assert action["cancels"][0]["a"] == 10210
|
|
27
|
+
assert isinstance(action["cancels"][0]["o"], int)
|
|
28
|
+
assert action["cancels"][0]["o"] == 306356655993
|
|
29
|
+
|
|
30
|
+
@pytest.mark.asyncio
|
|
31
|
+
async def test_adapter_cancel_order_parses_string_oid(self):
|
|
32
|
+
adapter = object.__new__(HyperliquidAdapter)
|
|
33
|
+
adapter.simulation = False
|
|
34
|
+
adapter._exchange = SimpleNamespace()
|
|
35
|
+
adapter._exchange.cancel_order = AsyncMock(return_value={"status": "ok"})
|
|
36
|
+
|
|
37
|
+
ok, _ = await adapter.cancel_order(
|
|
38
|
+
asset_id=10210, order_id="306356655993", address="0xabc"
|
|
39
|
+
)
|
|
40
|
+
assert ok is True
|
|
41
|
+
|
|
42
|
+
adapter._exchange.cancel_order.assert_awaited_once_with(
|
|
43
|
+
asset_id=10210, order_id=306356655993, address="0xabc"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@pytest.mark.asyncio
|
|
47
|
+
async def test_adapter_cancel_order_rejects_bad_oid(self):
|
|
48
|
+
adapter = object.__new__(HyperliquidAdapter)
|
|
49
|
+
adapter.simulation = False
|
|
50
|
+
adapter._exchange = SimpleNamespace()
|
|
51
|
+
adapter._exchange.cancel_order = AsyncMock(return_value={"status": "ok"})
|
|
52
|
+
|
|
53
|
+
ok, res = await adapter.cancel_order(
|
|
54
|
+
asset_id=1, order_id="not-a-number", address="0xabc"
|
|
55
|
+
)
|
|
56
|
+
assert ok is False
|
|
57
|
+
assert res["status"] == "err"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
from unittest.mock import AsyncMock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.exchange import Exchange
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class _InfoStub(SimpleNamespace):
|
|
10
|
+
def all_mids(self):
|
|
11
|
+
return {"HYPE": "1.0"}
|
|
12
|
+
|
|
13
|
+
async def all_dex_mid_prices(self): # pragma: no cover
|
|
14
|
+
raise AssertionError(
|
|
15
|
+
"Exchange must use Info.all_mids(), not all_dex_mid_prices"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _UtilStub(SimpleNamespace):
|
|
20
|
+
def get_price_decimals_for_hypecore_asset(self, asset_id: int) -> int:
|
|
21
|
+
return 6
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestExchangeMidPriceFetch:
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_place_market_order_uses_all_mids(self):
|
|
27
|
+
info = _InfoStub(asset_to_coin={7: "HYPE"})
|
|
28
|
+
util = _UtilStub()
|
|
29
|
+
ex = Exchange(
|
|
30
|
+
info=info,
|
|
31
|
+
util=util,
|
|
32
|
+
sign_callback=AsyncMock(return_value="0x"),
|
|
33
|
+
signing_type="eip712",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
async def _no_broadcast(action, address):
|
|
37
|
+
return action
|
|
38
|
+
|
|
39
|
+
ex.sign_and_broadcast_hypecore = _no_broadcast
|
|
40
|
+
|
|
41
|
+
action = await ex.place_market_order(
|
|
42
|
+
asset_id=7,
|
|
43
|
+
is_buy=True,
|
|
44
|
+
slippage=0.01,
|
|
45
|
+
size=1.0,
|
|
46
|
+
address="0xabc",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
assert action["type"] == "order"
|
|
50
|
+
assert action["orders"][0]["a"] == 7
|
|
51
|
+
assert action["orders"][0]["b"] is True
|
|
52
|
+
assert action["orders"][0]["p"] == "1.01"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from unittest.mock import AsyncMock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdapter
|
|
6
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.exchange import Exchange
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def live_adapter():
|
|
11
|
+
return HyperliquidAdapter(config={}, simulation=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestHyperliquidSdkCompat:
|
|
15
|
+
@pytest.mark.asyncio
|
|
16
|
+
async def test_util_mid_prices_live(self, live_adapter):
|
|
17
|
+
mids = await live_adapter.util.get_hypecore_all_dex_mid_prices()
|
|
18
|
+
assert isinstance(mids, dict)
|
|
19
|
+
assert len(mids) > 0
|
|
20
|
+
|
|
21
|
+
# Sanity check some common keys exist (perp coins or spot tickers like @107).
|
|
22
|
+
assert "HYPE" in mids or "BTC" in mids or any(k.startswith("@") for k in mids)
|
|
23
|
+
|
|
24
|
+
@pytest.mark.asyncio
|
|
25
|
+
async def test_util_meta_live(self, live_adapter):
|
|
26
|
+
meta = await live_adapter.util.get_hypecore_all_dex_meta_universe()
|
|
27
|
+
assert isinstance(meta, dict)
|
|
28
|
+
assert "universe" in meta
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestExchangeUsesLiveMids:
|
|
32
|
+
@pytest.mark.asyncio
|
|
33
|
+
async def test_place_market_order_builds_ioc_limit(self, live_adapter):
|
|
34
|
+
# Use a perp id to avoid spot naming edge-cases.
|
|
35
|
+
asset_id = live_adapter.coin_to_asset["HYPE"]
|
|
36
|
+
|
|
37
|
+
ex = Exchange(
|
|
38
|
+
info=live_adapter.info,
|
|
39
|
+
util=live_adapter.util,
|
|
40
|
+
sign_callback=AsyncMock(return_value="0x"),
|
|
41
|
+
signing_type="eip712",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
async def _no_broadcast(action, address):
|
|
45
|
+
return action
|
|
46
|
+
|
|
47
|
+
ex.sign_and_broadcast_hypecore = _no_broadcast
|
|
48
|
+
|
|
49
|
+
action = await ex.place_market_order(
|
|
50
|
+
asset_id=asset_id,
|
|
51
|
+
is_buy=True,
|
|
52
|
+
slippage=0.01,
|
|
53
|
+
size=1.0,
|
|
54
|
+
address="0x0000000000000000000000000000000000000000",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
assert action["type"] == "order"
|
|
58
|
+
assert action["orders"][0]["a"] == asset_id
|
|
59
|
+
assert action["orders"][0]["b"] is True
|
|
60
|
+
|
|
61
|
+
# Price should be at/above current mid for a buy (within rounding tolerance).
|
|
62
|
+
mid = float(live_adapter.info.all_mids()["HYPE"])
|
|
63
|
+
px = float(action["orders"][0]["p"])
|
|
64
|
+
assert px >= mid * 0.999
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import time
|
|
3
2
|
from decimal import ROUND_DOWN, Decimal
|
|
4
3
|
from typing import Any
|
|
@@ -61,10 +60,12 @@ class Util:
|
|
|
61
60
|
return assets.get(asset_name)
|
|
62
61
|
|
|
63
62
|
async def get_hypecore_all_dex_mid_prices(self):
|
|
64
|
-
|
|
63
|
+
# Backwards compatible wrapper: the Hyperliquid SDK now exposes `all_mids()`.
|
|
64
|
+
return self.info.all_mids()
|
|
65
65
|
|
|
66
66
|
async def get_hypecore_all_dex_meta_universe(self):
|
|
67
|
-
|
|
67
|
+
# Backwards compatible wrapper: the Hyperliquid SDK now exposes `meta()`.
|
|
68
|
+
return self.info.meta()
|
|
68
69
|
|
|
69
70
|
def get_size_decimals_for_hypecore_asset(self, asset_id: int):
|
|
70
71
|
return self.info.asset_to_sz_decimals[asset_id]
|
|
@@ -107,11 +108,9 @@ class Util:
|
|
|
107
108
|
return perp_user_state
|
|
108
109
|
|
|
109
110
|
async def get_hypecore_user(self, address):
|
|
110
|
-
perp_user_state
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
self.info.all_dex_open_orders(address),
|
|
114
|
-
)
|
|
111
|
+
perp_user_state = self.info.user_state(address)
|
|
112
|
+
spot_user_state = self.info.spot_user_state(address)
|
|
113
|
+
open_orders = self.info.open_orders(address)
|
|
115
114
|
formatted_perp_state = self._reformat_perp_user_state(perp_user_state)
|
|
116
115
|
state = {
|
|
117
116
|
"perp_user_state": formatted_perp_state,
|
|
@@ -172,7 +171,7 @@ class Util:
|
|
|
172
171
|
out = []
|
|
173
172
|
while True:
|
|
174
173
|
try:
|
|
175
|
-
batch =
|
|
174
|
+
batch = self.info.user_fills_by_time(wallet, start, end, False)
|
|
176
175
|
except Exception as e:
|
|
177
176
|
logger.error(f"Failed to fetch fills via node/public/SDK: {e}")
|
|
178
177
|
break
|
|
@@ -185,7 +184,7 @@ class Util:
|
|
|
185
184
|
return out
|
|
186
185
|
|
|
187
186
|
async def get_hypecore_position(self, address, asset_name):
|
|
188
|
-
perp_user_state =
|
|
187
|
+
perp_user_state = self.info.user_state(address)
|
|
189
188
|
formatted_perp_user_state = self._reformat_perp_user_state(perp_user_state)
|
|
190
189
|
|
|
191
190
|
for pos in formatted_perp_user_state.get("assetPositions", []):
|
|
@@ -99,7 +99,7 @@ class PoolClient(WayfinderClient):
|
|
|
99
99
|
self.api_base_url = get_api_base_url()
|
|
100
100
|
|
|
101
101
|
def _pools_url(self) -> str:
|
|
102
|
-
return f"{self.api_base_url}/
|
|
102
|
+
return f"{self.api_base_url}/blockchain/pools/"
|
|
103
103
|
|
|
104
104
|
async def get_pools(
|
|
105
105
|
self,
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""LayerZero OFT ABI subset used for bridging native HYPE from HyperEVM."""
|
|
2
|
+
|
|
3
|
+
HYPE_OFT_ABI = [
|
|
4
|
+
{
|
|
5
|
+
"inputs": [
|
|
6
|
+
{
|
|
7
|
+
"components": [
|
|
8
|
+
{"internalType": "uint32", "name": "dstEid", "type": "uint32"},
|
|
9
|
+
{"internalType": "bytes32", "name": "to", "type": "bytes32"},
|
|
10
|
+
{"internalType": "uint256", "name": "amountLD", "type": "uint256"},
|
|
11
|
+
{
|
|
12
|
+
"internalType": "uint256",
|
|
13
|
+
"name": "minAmountLD",
|
|
14
|
+
"type": "uint256",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"internalType": "bytes",
|
|
18
|
+
"name": "extraOptions",
|
|
19
|
+
"type": "bytes",
|
|
20
|
+
},
|
|
21
|
+
{"internalType": "bytes", "name": "composeMsg", "type": "bytes"},
|
|
22
|
+
{"internalType": "bytes", "name": "oftCmd", "type": "bytes"},
|
|
23
|
+
],
|
|
24
|
+
"internalType": "struct SendParam",
|
|
25
|
+
"name": "_sendParam",
|
|
26
|
+
"type": "tuple",
|
|
27
|
+
},
|
|
28
|
+
{"internalType": "bool", "name": "_payInLzToken", "type": "bool"},
|
|
29
|
+
],
|
|
30
|
+
"name": "quoteSend",
|
|
31
|
+
"outputs": [
|
|
32
|
+
{
|
|
33
|
+
"components": [
|
|
34
|
+
{"internalType": "uint256", "name": "nativeFee", "type": "uint256"},
|
|
35
|
+
{
|
|
36
|
+
"internalType": "uint256",
|
|
37
|
+
"name": "lzTokenFee",
|
|
38
|
+
"type": "uint256",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
"internalType": "struct MessagingFee",
|
|
42
|
+
"name": "",
|
|
43
|
+
"type": "tuple",
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"stateMutability": "view",
|
|
47
|
+
"type": "function",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"inputs": [
|
|
51
|
+
{
|
|
52
|
+
"components": [
|
|
53
|
+
{"internalType": "uint32", "name": "dstEid", "type": "uint32"},
|
|
54
|
+
{"internalType": "bytes32", "name": "to", "type": "bytes32"},
|
|
55
|
+
{"internalType": "uint256", "name": "amountLD", "type": "uint256"},
|
|
56
|
+
{
|
|
57
|
+
"internalType": "uint256",
|
|
58
|
+
"name": "minAmountLD",
|
|
59
|
+
"type": "uint256",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"internalType": "bytes",
|
|
63
|
+
"name": "extraOptions",
|
|
64
|
+
"type": "bytes",
|
|
65
|
+
},
|
|
66
|
+
{"internalType": "bytes", "name": "composeMsg", "type": "bytes"},
|
|
67
|
+
{"internalType": "bytes", "name": "oftCmd", "type": "bytes"},
|
|
68
|
+
],
|
|
69
|
+
"internalType": "struct SendParam",
|
|
70
|
+
"name": "_sendParam",
|
|
71
|
+
"type": "tuple",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"components": [
|
|
75
|
+
{"internalType": "uint256", "name": "nativeFee", "type": "uint256"},
|
|
76
|
+
{
|
|
77
|
+
"internalType": "uint256",
|
|
78
|
+
"name": "lzTokenFee",
|
|
79
|
+
"type": "uint256",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
"internalType": "struct MessagingFee",
|
|
83
|
+
"name": "_fee",
|
|
84
|
+
"type": "tuple",
|
|
85
|
+
},
|
|
86
|
+
{"internalType": "address", "name": "_refundAddress", "type": "address"},
|
|
87
|
+
],
|
|
88
|
+
"name": "send",
|
|
89
|
+
"outputs": [
|
|
90
|
+
{
|
|
91
|
+
"components": [
|
|
92
|
+
{"internalType": "bytes32", "name": "guid", "type": "bytes32"},
|
|
93
|
+
{"internalType": "uint64", "name": "nonce", "type": "uint64"},
|
|
94
|
+
{
|
|
95
|
+
"components": [
|
|
96
|
+
{
|
|
97
|
+
"internalType": "uint256",
|
|
98
|
+
"name": "nativeFee",
|
|
99
|
+
"type": "uint256",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"internalType": "uint256",
|
|
103
|
+
"name": "lzTokenFee",
|
|
104
|
+
"type": "uint256",
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
"internalType": "struct MessagingFee",
|
|
108
|
+
"name": "fee",
|
|
109
|
+
"type": "tuple",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
"internalType": "struct MessagingReceipt",
|
|
113
|
+
"name": "",
|
|
114
|
+
"type": "tuple",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"components": [
|
|
118
|
+
{
|
|
119
|
+
"internalType": "uint256",
|
|
120
|
+
"name": "amountSentLD",
|
|
121
|
+
"type": "uint256",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"internalType": "uint256",
|
|
125
|
+
"name": "amountReceivedLD",
|
|
126
|
+
"type": "uint256",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
"internalType": "struct OFTReceipt",
|
|
130
|
+
"name": "",
|
|
131
|
+
"type": "tuple",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
"stateMutability": "payable",
|
|
135
|
+
"type": "function",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"inputs": [],
|
|
139
|
+
"name": "sharedDecimals",
|
|
140
|
+
"outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}],
|
|
141
|
+
"stateMutability": "view",
|
|
142
|
+
"type": "function",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"inputs": [],
|
|
146
|
+
"name": "decimalConversionRate",
|
|
147
|
+
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
|
|
148
|
+
"stateMutability": "view",
|
|
149
|
+
"type": "function",
|
|
150
|
+
},
|
|
151
|
+
]
|
|
@@ -8,7 +8,6 @@ from loguru import logger
|
|
|
8
8
|
|
|
9
9
|
from wayfinder_paths.core.clients.TokenClient import TokenDetails
|
|
10
10
|
from wayfinder_paths.core.strategies.descriptors import StratDescriptor
|
|
11
|
-
from wayfinder_paths.core.types import HyperliquidSignCallback
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class StatusDict(TypedDict):
|
|
@@ -52,7 +51,7 @@ class Strategy(ABC):
|
|
|
52
51
|
main_wallet_signing_callback: Callable[[dict], Awaitable[str]] | None = None,
|
|
53
52
|
strategy_wallet_signing_callback: Callable[[dict], Awaitable[str]]
|
|
54
53
|
| None = None,
|
|
55
|
-
strategy_sign_typed_data:
|
|
54
|
+
strategy_sign_typed_data: Callable[[dict], Awaitable[str]] | None = None,
|
|
56
55
|
):
|
|
57
56
|
self.ledger_adapter = None
|
|
58
57
|
self.logger = logger.bind(strategy=self.__class__.__name__)
|
|
@@ -257,19 +257,40 @@ def _compact_quote(
|
|
|
257
257
|
"""Create a compact summary of quote data, stripping verbose nested structures."""
|
|
258
258
|
result: dict[str, Any] = {}
|
|
259
259
|
|
|
260
|
-
# Extract provider list from quotes
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
)
|
|
272
|
-
|
|
260
|
+
# Extract provider list from quotes. BRAP quotes may appear as either:
|
|
261
|
+
# 1) {"quotes": [...], "best_quote": {...}}
|
|
262
|
+
# 2) {"quotes": {"all_quotes": [...], "best_quote": {...}, "quote_count": N}}
|
|
263
|
+
all_quotes: list[dict[str, Any]] = []
|
|
264
|
+
raw_quotes = quote_data.get("quotes", [])
|
|
265
|
+
quote_count = None
|
|
266
|
+
|
|
267
|
+
if isinstance(raw_quotes, list):
|
|
268
|
+
all_quotes = [q for q in raw_quotes if isinstance(q, dict)]
|
|
269
|
+
elif isinstance(raw_quotes, dict):
|
|
270
|
+
nested = raw_quotes.get("all_quotes") or raw_quotes.get("quotes") or []
|
|
271
|
+
if isinstance(nested, list):
|
|
272
|
+
all_quotes = [q for q in nested if isinstance(q, dict)]
|
|
273
|
+
qc = raw_quotes.get("quote_count")
|
|
274
|
+
try:
|
|
275
|
+
quote_count = int(qc) if qc is not None else None
|
|
276
|
+
except (TypeError, ValueError):
|
|
277
|
+
quote_count = None
|
|
278
|
+
|
|
279
|
+
providers: list[str] = []
|
|
280
|
+
seen: set[str] = set()
|
|
281
|
+
for q in all_quotes:
|
|
282
|
+
p = q.get("provider")
|
|
283
|
+
if not p:
|
|
284
|
+
continue
|
|
285
|
+
p_str = str(p)
|
|
286
|
+
if p_str in seen:
|
|
287
|
+
continue
|
|
288
|
+
seen.add(p_str)
|
|
289
|
+
providers.append(p_str)
|
|
290
|
+
|
|
291
|
+
if providers:
|
|
292
|
+
result["providers"] = providers
|
|
293
|
+
result["quote_count"] = quote_count if quote_count is not None else len(all_quotes)
|
|
273
294
|
|
|
274
295
|
# Compact best_quote - only essential fields
|
|
275
296
|
if isinstance(best_quote, dict):
|
|
@@ -511,9 +532,20 @@ async def execute(
|
|
|
511
532
|
store.put(key, tool_input, response)
|
|
512
533
|
return response
|
|
513
534
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
)
|
|
535
|
+
# BRAP quote responses have historically appeared in two shapes:
|
|
536
|
+
# 1) {"quotes": [...], "best_quote": {...}}
|
|
537
|
+
# 2) {"quotes": {"all_quotes": [...], "best_quote": {...}, "quote_count": N}}
|
|
538
|
+
best_quote = None
|
|
539
|
+
if isinstance(quote_data, dict):
|
|
540
|
+
if isinstance(quote_data.get("best_quote"), dict):
|
|
541
|
+
best_quote = quote_data.get("best_quote")
|
|
542
|
+
else:
|
|
543
|
+
quotes_block = quote_data.get("quotes")
|
|
544
|
+
if isinstance(quotes_block, dict) and isinstance(
|
|
545
|
+
quotes_block.get("best_quote"), dict
|
|
546
|
+
):
|
|
547
|
+
best_quote = quotes_block.get("best_quote")
|
|
548
|
+
|
|
517
549
|
if not isinstance(best_quote, dict):
|
|
518
550
|
response = err(
|
|
519
551
|
"quote_error", "No best_quote returned", {"quote": quote_data}
|
|
@@ -5,9 +5,6 @@ import re
|
|
|
5
5
|
from typing import Any, Literal
|
|
6
6
|
|
|
7
7
|
from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdapter
|
|
8
|
-
from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
|
|
9
|
-
LocalHyperliquidExecutor,
|
|
10
|
-
)
|
|
11
8
|
from wayfinder_paths.core.constants.hyperliquid import (
|
|
12
9
|
DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP,
|
|
13
10
|
HYPE_FEE_WALLET,
|
|
@@ -404,16 +401,7 @@ async def hyperliquid_execute(
|
|
|
404
401
|
|
|
405
402
|
effects: list[dict[str, Any]] = []
|
|
406
403
|
|
|
407
|
-
|
|
408
|
-
if not dry:
|
|
409
|
-
try:
|
|
410
|
-
executor = LocalHyperliquidExecutor(config=config, network="mainnet")
|
|
411
|
-
except Exception as exc: # noqa: BLE001
|
|
412
|
-
response = err("executor_error", str(exc))
|
|
413
|
-
store.put(key, tool_input, response)
|
|
414
|
-
return response
|
|
415
|
-
|
|
416
|
-
adapter = HyperliquidAdapter(config=config, simulation=dry, executor=executor)
|
|
404
|
+
adapter = HyperliquidAdapter(config=config, simulation=dry)
|
|
417
405
|
|
|
418
406
|
if action == "withdraw":
|
|
419
407
|
if amount_usdc is None:
|