wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.21__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.

Files changed (98) hide show
  1. wayfinder_paths/__init__.py +0 -2
  2. wayfinder_paths/adapters/balance_adapter/README.md +59 -45
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +1 -22
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
  5. wayfinder_paths/adapters/brap_adapter/README.md +61 -184
  6. wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
  7. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -148
  8. wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
  9. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
  10. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +1 -10
  11. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
  12. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
  13. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
  14. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
  15. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
  16. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
  17. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
  18. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
  19. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
  20. wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
  21. wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
  22. wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
  23. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
  24. wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
  25. wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
  26. wayfinder_paths/adapters/moonwell_adapter/adapter.py +10 -122
  27. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
  28. wayfinder_paths/adapters/pool_adapter/README.md +30 -51
  29. wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
  30. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
  31. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
  32. wayfinder_paths/adapters/token_adapter/README.md +41 -49
  33. wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
  34. wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
  35. wayfinder_paths/conftest.py +0 -8
  36. wayfinder_paths/core/__init__.py +0 -2
  37. wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
  38. wayfinder_paths/core/adapters/__init__.py +0 -5
  39. wayfinder_paths/core/adapters/models.py +0 -5
  40. wayfinder_paths/core/analytics/__init__.py +0 -2
  41. wayfinder_paths/core/analytics/bootstrap.py +0 -16
  42. wayfinder_paths/core/analytics/stats.py +0 -7
  43. wayfinder_paths/core/analytics/test_analytics.py +5 -34
  44. wayfinder_paths/core/clients/BRAPClient.py +0 -35
  45. wayfinder_paths/core/clients/ClientManager.py +0 -51
  46. wayfinder_paths/core/clients/HyperlendClient.py +0 -77
  47. wayfinder_paths/core/clients/LedgerClient.py +2 -122
  48. wayfinder_paths/core/clients/PoolClient.py +0 -2
  49. wayfinder_paths/core/clients/TokenClient.py +0 -39
  50. wayfinder_paths/core/clients/WalletClient.py +0 -15
  51. wayfinder_paths/core/clients/WayfinderClient.py +0 -24
  52. wayfinder_paths/core/clients/__init__.py +0 -4
  53. wayfinder_paths/core/clients/protocols.py +25 -98
  54. wayfinder_paths/core/config.py +0 -24
  55. wayfinder_paths/core/constants/__init__.py +0 -7
  56. wayfinder_paths/core/constants/base.py +2 -9
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -5
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
  59. wayfinder_paths/core/constants/moonwell_abi.py +0 -35
  60. wayfinder_paths/core/engine/StrategyJob.py +0 -32
  61. wayfinder_paths/core/strategies/Strategy.py +0 -99
  62. wayfinder_paths/core/strategies/__init__.py +0 -2
  63. wayfinder_paths/core/utils/__init__.py +0 -1
  64. wayfinder_paths/core/utils/evm_helpers.py +0 -50
  65. wayfinder_paths/core/utils/{erc20_service.py → tokens.py} +25 -21
  66. wayfinder_paths/core/utils/transaction.py +0 -1
  67. wayfinder_paths/run_strategy.py +0 -46
  68. wayfinder_paths/scripts/create_strategy.py +0 -17
  69. wayfinder_paths/scripts/make_wallets.py +1 -4
  70. wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
  71. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
  72. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
  73. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
  74. wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
  76. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
  77. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
  78. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
  79. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
  80. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
  81. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
  82. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
  83. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
  84. wayfinder_paths/templates/adapter/README.md +107 -46
  85. wayfinder_paths/templates/adapter/adapter.py +0 -9
  86. wayfinder_paths/templates/adapter/test_adapter.py +0 -19
  87. wayfinder_paths/templates/strategy/README.md +113 -59
  88. wayfinder_paths/templates/strategy/strategy.py +0 -22
  89. wayfinder_paths/templates/strategy/test_strategy.py +0 -28
  90. wayfinder_paths/tests/test_test_coverage.py +2 -12
  91. wayfinder_paths/tests/test_utils.py +1 -31
  92. wayfinder_paths-0.1.21.dist-info/METADATA +355 -0
  93. wayfinder_paths-0.1.21.dist-info/RECORD +129 -0
  94. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/WHEEL +1 -1
  95. wayfinder_paths/core/adapters/base.py +0 -5
  96. wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
  97. wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
  98. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/LICENSE +0 -0
@@ -1,14 +1,3 @@
1
- """
2
- Live API tests for HyperliquidAdapter.
3
-
4
- These tests hit the real Hyperliquid API to verify:
5
- - Spot asset ID resolution (PURR, ETH, BTC, HYPE)
6
- - Perp asset ID resolution
7
- - API connectivity
8
-
9
- Run with: pytest wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py -v
10
- """
11
-
12
1
  import pytest
13
2
 
14
3
  from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdapter
@@ -16,16 +5,12 @@ from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdap
16
5
 
17
6
  @pytest.fixture
18
7
  def live_adapter():
19
- """Create adapter connected to real Hyperliquid API."""
20
8
  return HyperliquidAdapter(config={})
21
9
 
22
10
 
23
11
  class TestSpotAssetIDs:
24
- """Test spot asset ID resolution against live API."""
25
-
26
12
  @pytest.mark.asyncio
27
13
  async def test_get_spot_assets_returns_dict(self, live_adapter):
28
- """Verify get_spot_assets returns a populated dict."""
29
14
  success, spot_assets = await live_adapter.get_spot_assets()
30
15
 
31
16
  assert success
@@ -34,7 +19,6 @@ class TestSpotAssetIDs:
34
19
 
35
20
  @pytest.mark.asyncio
36
21
  async def test_purr_spot_asset_id(self, live_adapter):
37
- """PURR/USDC should be the first spot pair (index 0 + 10000 = 10000)."""
38
22
  success, spot_assets = await live_adapter.get_spot_assets()
39
23
 
40
24
  assert success
@@ -43,7 +27,6 @@ class TestSpotAssetIDs:
43
27
 
44
28
  @pytest.mark.asyncio
45
29
  async def test_hype_spot_asset_id(self, live_adapter):
46
- """HYPE/USDC should have asset ID 10107."""
47
30
  success, spot_assets = await live_adapter.get_spot_assets()
48
31
 
49
32
  assert success
@@ -53,7 +36,6 @@ class TestSpotAssetIDs:
53
36
 
54
37
  @pytest.mark.asyncio
55
38
  async def test_eth_spot_exists(self, live_adapter):
56
- """ETH/USDC spot pair should exist."""
57
39
  success, spot_assets = await live_adapter.get_spot_assets()
58
40
 
59
41
  assert success
@@ -65,7 +47,6 @@ class TestSpotAssetIDs:
65
47
 
66
48
  @pytest.mark.asyncio
67
49
  async def test_btc_spot_exists(self, live_adapter):
68
- """BTC/USDC spot pair should exist."""
69
50
  success, spot_assets = await live_adapter.get_spot_assets()
70
51
 
71
52
  assert success
@@ -77,7 +58,6 @@ class TestSpotAssetIDs:
77
58
 
78
59
  @pytest.mark.asyncio
79
60
  async def test_spot_asset_ids_are_valid(self, live_adapter):
80
- """All spot asset IDs should be >= 10000."""
81
61
  success, spot_assets = await live_adapter.get_spot_assets()
82
62
 
83
63
  assert success
@@ -86,7 +66,6 @@ class TestSpotAssetIDs:
86
66
 
87
67
  @pytest.mark.asyncio
88
68
  async def test_get_spot_asset_id_helper(self, live_adapter):
89
- """Test synchronous helper after cache is populated."""
90
69
  # First populate cache
91
70
  success, _ = await live_adapter.get_spot_assets()
92
71
  assert success
@@ -104,11 +83,8 @@ class TestSpotAssetIDs:
104
83
 
105
84
 
106
85
  class TestPerpAssetIDs:
107
- """Test perp asset ID resolution against live API."""
108
-
109
86
  @pytest.mark.asyncio
110
87
  async def test_coin_to_asset_mapping(self, live_adapter):
111
- """Verify perp coin to asset mapping is populated."""
112
88
  coin_to_asset = live_adapter.coin_to_asset
113
89
 
114
90
  assert isinstance(coin_to_asset, dict)
@@ -116,7 +92,6 @@ class TestPerpAssetIDs:
116
92
 
117
93
  @pytest.mark.asyncio
118
94
  async def test_btc_perp_asset_id(self, live_adapter):
119
- """BTC perp should be asset_id 0."""
120
95
  coin_to_asset = live_adapter.coin_to_asset
121
96
 
122
97
  assert "BTC" in coin_to_asset
@@ -124,7 +99,6 @@ class TestPerpAssetIDs:
124
99
 
125
100
  @pytest.mark.asyncio
126
101
  async def test_eth_perp_asset_id(self, live_adapter):
127
- """ETH perp should be asset_id 1."""
128
102
  coin_to_asset = live_adapter.coin_to_asset
129
103
 
130
104
  assert "ETH" in coin_to_asset
@@ -132,19 +106,15 @@ class TestPerpAssetIDs:
132
106
 
133
107
  @pytest.mark.asyncio
134
108
  async def test_hype_perp_exists(self, live_adapter):
135
- """HYPE perp should exist with valid asset_id."""
136
109
  coin_to_asset = live_adapter.coin_to_asset
137
110
 
138
111
  assert "HYPE" in coin_to_asset
139
- assert coin_to_asset["HYPE"] < 10000 # Perp IDs are < 10000
112
+ assert coin_to_asset["HYPE"] < 10000
140
113
 
141
114
 
142
115
  class TestSpotMetaStructure:
143
- """Test spot_meta API response structure."""
144
-
145
116
  @pytest.mark.asyncio
146
117
  async def test_spot_meta_has_tokens(self, live_adapter):
147
- """Spot meta should have tokens array."""
148
118
  success, spot_meta = await live_adapter.get_spot_meta()
149
119
 
150
120
  assert success
@@ -154,7 +124,6 @@ class TestSpotMetaStructure:
154
124
 
155
125
  @pytest.mark.asyncio
156
126
  async def test_spot_meta_has_universe(self, live_adapter):
157
- """Spot meta should have universe array with pairs."""
158
127
  success, spot_meta = await live_adapter.get_spot_meta()
159
128
 
160
129
  assert success
@@ -164,22 +133,18 @@ class TestSpotMetaStructure:
164
133
 
165
134
  @pytest.mark.asyncio
166
135
  async def test_spot_universe_pair_structure(self, live_adapter):
167
- """Each spot universe entry should have tokens and index."""
168
136
  success, spot_meta = await live_adapter.get_spot_meta()
169
137
 
170
138
  assert success
171
- for pair in spot_meta["universe"][:5]: # Check first 5
139
+ for pair in spot_meta["universe"][:5]:
172
140
  assert "tokens" in pair, f"Missing tokens in {pair}"
173
141
  assert "index" in pair, f"Missing index in {pair}"
174
142
  assert len(pair["tokens"]) >= 2, f"Invalid tokens in {pair}"
175
143
 
176
144
 
177
145
  class TestL2BookResolution:
178
- """Test that spot asset IDs work with L2 book API."""
179
-
180
146
  @pytest.mark.asyncio
181
147
  async def test_purr_spot_l2_book(self, live_adapter):
182
- """PURR/USDC (10000) should return valid L2 book."""
183
148
  success, book = await live_adapter.get_spot_l2_book(10000)
184
149
 
185
150
  assert success
@@ -187,7 +152,6 @@ class TestL2BookResolution:
187
152
 
188
153
  @pytest.mark.asyncio
189
154
  async def test_hype_spot_l2_book(self, live_adapter):
190
- """HYPE/USDC (10107) should return valid L2 book."""
191
155
  success, book = await live_adapter.get_spot_l2_book(10107)
192
156
 
193
157
  assert success
@@ -195,11 +159,8 @@ class TestL2BookResolution:
195
159
 
196
160
 
197
161
  class TestSzDecimals:
198
- """Test size decimals resolution for spot assets."""
199
-
200
162
  @pytest.mark.asyncio
201
163
  async def test_spot_sz_decimals(self, live_adapter):
202
- """Spot assets should have valid sz_decimals."""
203
164
  # HYPE spot = 10107
204
165
  decimals = live_adapter.get_sz_decimals(10107)
205
166
  assert isinstance(decimals, int)
@@ -207,7 +168,6 @@ class TestSzDecimals:
207
168
 
208
169
  @pytest.mark.asyncio
209
170
  async def test_perp_sz_decimals(self, live_adapter):
210
- """Perp assets should have valid sz_decimals."""
211
171
  # BTC perp = 0
212
172
  decimals = live_adapter.get_sz_decimals(0)
213
173
  assert isinstance(decimals, int)
@@ -1,5 +1,3 @@
1
- """Tests for LocalHyperliquidExecutor."""
2
-
3
1
  from types import SimpleNamespace
4
2
  from unittest.mock import MagicMock, patch
5
3
 
@@ -7,16 +5,12 @@ import pytest
7
5
 
8
6
 
9
7
  class TestLocalHyperliquidExecutor:
10
- """Tests for LocalHyperliquidExecutor."""
11
-
12
8
  @pytest.fixture
13
9
  def mock_info_without_asset_to_coin(self):
14
- """Mock Info that only exposes coin_to_asset (no asset_to_coin)."""
15
10
  return SimpleNamespace(coin_to_asset={"HYPE": 7})
16
11
 
17
12
  @pytest.fixture
18
13
  def mock_exchange(self):
19
- """Mock Exchange client."""
20
14
  mock = MagicMock()
21
15
  mock.update_leverage.return_value = {"status": "ok"}
22
16
  mock.market_open.return_value = {"status": "ok"}
@@ -26,7 +20,6 @@ class TestLocalHyperliquidExecutor:
26
20
  async def test_update_leverage_works_without_asset_to_coin(
27
21
  self, mock_info_without_asset_to_coin, mock_exchange
28
22
  ):
29
- """update_leverage should map asset_id->coin via coin_to_asset inversion."""
30
23
  dummy_wallet = SimpleNamespace(address="0xabc")
31
24
 
32
25
  with patch(
@@ -66,7 +59,6 @@ class TestLocalHyperliquidExecutor:
66
59
  async def test_place_market_order_perp_works_without_asset_to_coin(
67
60
  self, mock_info_without_asset_to_coin, mock_exchange
68
61
  ):
69
- """place_market_order should map asset_id->coin via coin_to_asset inversion."""
70
62
  dummy_wallet = SimpleNamespace(address="0xabc")
71
63
 
72
64
  with patch(
@@ -95,7 +87,7 @@ class TestLocalHyperliquidExecutor:
95
87
  size=1.0,
96
88
  address="0xabc",
97
89
  reduce_only=False,
98
- cloid=MagicMock(), # Avoid Cloid conversion
90
+ cloid=MagicMock(),
99
91
  )
100
92
 
101
93
  assert resp.get("status") == "ok"
@@ -1,5 +1,3 @@
1
- """Tests for Hyperliquid adapter utility functions."""
2
-
3
1
  import pytest
4
2
 
5
3
  from wayfinder_paths.adapters.hyperliquid_adapter.utils import (
@@ -13,16 +11,12 @@ from wayfinder_paths.adapters.hyperliquid_adapter.utils import (
13
11
 
14
12
 
15
13
  class TestSpotIndexFromAssetId:
16
- """Tests for spot_index_from_asset_id function."""
17
-
18
14
  def test_valid_spot_id(self):
19
- """Valid spot asset IDs (>=10000) should return index."""
20
15
  assert spot_index_from_asset_id(10000) == 0
21
16
  assert spot_index_from_asset_id(10001) == 1
22
17
  assert spot_index_from_asset_id(10107) == 107
23
18
 
24
19
  def test_rejects_perp_id(self):
25
- """Perp asset IDs (<10000) should raise ValueError."""
26
20
  with pytest.raises(ValueError, match="Expected spot asset_id >= 10000"):
27
21
  spot_index_from_asset_id(0)
28
22
 
@@ -31,14 +25,11 @@ class TestSpotIndexFromAssetId:
31
25
 
32
26
 
33
27
  class TestNormalizeL2Book:
34
- """Tests for normalize_l2_book function."""
35
-
36
28
  def test_levels_format(self):
37
- """Should handle Hyperliquid 'levels' format (nested arrays)."""
38
29
  raw = {
39
30
  "levels": [
40
- [{"px": "100.5", "sz": "10"}], # bids
41
- [{"px": "101.0", "sz": "5"}], # asks
31
+ [{"px": "100.5", "sz": "10"}],
32
+ [{"px": "101.0", "sz": "5"}],
42
33
  ],
43
34
  "midPx": "100.75",
44
35
  }
@@ -49,7 +40,6 @@ class TestNormalizeL2Book:
49
40
  assert result["midPx"] == 100.75
50
41
 
51
42
  def test_bids_asks_format(self):
52
- """Should handle flat bids/asks format."""
53
43
  raw = {
54
44
  "bids": [{"px": "100.5", "sz": "10"}],
55
45
  "asks": [{"px": "101.0", "sz": "5"}],
@@ -60,7 +50,6 @@ class TestNormalizeL2Book:
60
50
  assert result["asks"] == [(101.0, 5.0)]
61
51
 
62
52
  def test_calculates_mid_from_bids_asks(self):
63
- """Should calculate mid price from best bid/ask when not provided."""
64
53
  raw = {
65
54
  "levels": [
66
55
  [{"px": "100.0", "sz": "10"}],
@@ -69,38 +58,35 @@ class TestNormalizeL2Book:
69
58
  }
70
59
  result = normalize_l2_book(raw)
71
60
 
72
- assert result["midPx"] == 101.0 # (100 + 102) / 2
61
+ assert result["midPx"] == 101.0
73
62
 
74
63
  def test_fallback_mid(self):
75
- """Should use fallback_mid when no mid can be calculated."""
76
64
  raw = {"levels": [[], []]}
77
65
  result = normalize_l2_book(raw, fallback_mid=99.0)
78
66
 
79
67
  assert result["midPx"] == 99.0
80
68
 
81
69
  def test_invalid_levels_skipped(self):
82
- """Invalid level entries should be skipped."""
83
70
  raw = {
84
71
  "levels": [
85
72
  [
86
- {"px": "100.0", "sz": "10"}, # valid
87
- {"px": "invalid", "sz": "5"}, # invalid - non-numeric
88
- {"px": "0", "sz": "5"}, # invalid - zero price
89
- {"px": "50", "sz": "0"}, # invalid - zero size
73
+ {"px": "100.0", "sz": "10"},
74
+ {"px": "invalid", "sz": "5"},
75
+ {"px": "0", "sz": "5"},
76
+ {"px": "50", "sz": "0"},
90
77
  ],
91
78
  [{"px": "101.0", "sz": "5"}],
92
79
  ],
93
80
  }
94
81
  result = normalize_l2_book(raw)
95
82
 
96
- assert result["bids"] == [(100.0, 10.0)] # Only valid entry
83
+ assert result["bids"] == [(100.0, 10.0)]
97
84
 
98
85
  def test_tuple_format_levels(self):
99
- """Should handle tuple/list format levels."""
100
86
  raw = {
101
87
  "levels": [
102
- [[100.0, 10.0]], # bids as tuples
103
- [[101.0, 5.0]], # asks as tuples
88
+ [[100.0, 10.0]],
89
+ [[101.0, 5.0]],
104
90
  ],
105
91
  }
106
92
  result = normalize_l2_book(raw)
@@ -110,13 +96,10 @@ class TestNormalizeL2Book:
110
96
 
111
97
 
112
98
  class TestUsdDepthInBand:
113
- """Tests for usd_depth_in_band function."""
114
-
115
99
  def test_buy_side(self):
116
- """Buy side should measure ask depth within band."""
117
100
  book = {
118
- "bids": [(99.0, 10.0), (98.0, 20.0)], # $990 + $1960 = $2950
119
- "asks": [(101.0, 5.0), (102.0, 10.0)], # $505 + $1020 = $1525
101
+ "bids": [(99.0, 10.0), (98.0, 20.0)],
102
+ "asks": [(101.0, 5.0), (102.0, 10.0)],
120
103
  "midPx": 100.0,
121
104
  }
122
105
  # 100 bps = 1% band, so hi = 101.0
@@ -127,7 +110,6 @@ class TestUsdDepthInBand:
127
110
  assert depth == 505.0
128
111
 
129
112
  def test_sell_side(self):
130
- """Sell side should measure bid depth within band."""
131
113
  book = {
132
114
  "bids": [(99.0, 10.0), (98.0, 20.0)],
133
115
  "asks": [(101.0, 5.0), (102.0, 10.0)],
@@ -141,7 +123,6 @@ class TestUsdDepthInBand:
141
123
  assert depth == 990.0
142
124
 
143
125
  def test_zero_mid(self):
144
- """Zero mid price should return zero depth."""
145
126
  book = {"bids": [(99.0, 10.0)], "asks": [(101.0, 5.0)], "midPx": 0.0}
146
127
  depth, mid = usd_depth_in_band(book, band_bps=100, side="buy")
147
128
 
@@ -150,10 +131,7 @@ class TestUsdDepthInBand:
150
131
 
151
132
 
152
133
  class TestSzDecimalsForAsset:
153
- """Tests for sz_decimals_for_asset function."""
154
-
155
134
  def test_known_asset(self):
156
- """Should return decimals for known asset."""
157
135
  asset_to_sz = {0: 4, 1: 3, 10000: 6}
158
136
 
159
137
  assert sz_decimals_for_asset(asset_to_sz, 0) == 4
@@ -161,7 +139,6 @@ class TestSzDecimalsForAsset:
161
139
  assert sz_decimals_for_asset(asset_to_sz, 10000) == 6
162
140
 
163
141
  def test_unknown_raises(self):
164
- """Unknown asset should raise ValueError."""
165
142
  asset_to_sz = {0: 4}
166
143
 
167
144
  with pytest.raises(ValueError, match="Unknown asset_id 999"):
@@ -169,10 +146,7 @@ class TestSzDecimalsForAsset:
169
146
 
170
147
 
171
148
  class TestSizeStep:
172
- """Tests for size_step function."""
173
-
174
149
  def test_size_step_calculation(self):
175
- """Size step should be 10^(-decimals)."""
176
150
  from decimal import Decimal
177
151
 
178
152
  asset_to_sz = {0: 4, 1: 2}
@@ -182,26 +156,21 @@ class TestSizeStep:
182
156
 
183
157
 
184
158
  class TestRoundSizeForAsset:
185
- """Tests for round_size_for_asset function."""
186
-
187
159
  def test_floors_correctly(self):
188
- """Should floor to size step."""
189
- asset_to_sz = {0: 4} # Step = 0.0001
160
+ asset_to_sz = {0: 4}
190
161
 
191
162
  # 1.23456789 should floor to 1.2345
192
163
  result = round_size_for_asset(asset_to_sz, 0, 1.23456789)
193
164
  assert result == 1.2345
194
165
 
195
166
  def test_zero_returns_zero(self):
196
- """Zero or negative size returns 0."""
197
167
  asset_to_sz = {0: 4}
198
168
 
199
169
  assert round_size_for_asset(asset_to_sz, 0, 0.0) == 0.0
200
170
  assert round_size_for_asset(asset_to_sz, 0, -1.0) == 0.0
201
171
 
202
172
  def test_ensure_min_step(self):
203
- """With ensure_min_step=True, tiny values become one step."""
204
- asset_to_sz = {0: 4} # Step = 0.0001
173
+ asset_to_sz = {0: 4}
205
174
 
206
175
  # Without ensure_min_step: 0.00001 floors to 0
207
176
  result = round_size_for_asset(asset_to_sz, 0, 0.00001, ensure_min_step=False)
@@ -212,8 +181,7 @@ class TestRoundSizeForAsset:
212
181
  assert result == 0.0001
213
182
 
214
183
  def test_preserves_precision(self):
215
- """Should not introduce float precision errors."""
216
- asset_to_sz = {0: 2} # Step = 0.01
184
+ asset_to_sz = {0: 2}
217
185
 
218
186
  # 0.1 + 0.2 = 0.30000000000000004 in float
219
187
  result = round_size_for_asset(asset_to_sz, 0, 0.1 + 0.2)
@@ -6,7 +6,6 @@ from typing import Any
6
6
 
7
7
 
8
8
  def spot_index_from_asset_id(spot_asset_id: int) -> int:
9
- """Convert Hyperliquid spot asset_id (>=10000) to spot index."""
10
9
  if spot_asset_id < 10000:
11
10
  raise ValueError(f"Expected spot asset_id >= 10000, got {spot_asset_id}")
12
11
  return int(spot_asset_id) - 10000
@@ -17,8 +16,6 @@ def normalize_l2_book(
17
16
  *,
18
17
  fallback_mid: float | None = None,
19
18
  ) -> dict[str, Any]:
20
- """Normalize Hyperliquid L2 into bids/asks lists with floats."""
21
-
22
19
  def coerce_levels(levels: Any) -> list[tuple[float, float]]:
23
20
  normalized: list[tuple[float, float]] = []
24
21
  if not isinstance(levels, list):
@@ -74,7 +71,6 @@ def normalize_l2_book(
74
71
  def usd_depth_in_band(
75
72
  book: dict[str, Any], band_bps: int, side: str
76
73
  ) -> tuple[float, float]:
77
- """Return USD depth inside a +/- band (bps) around mid price for a side."""
78
74
  bids = book.get("bids") or []
79
75
  asks = book.get("asks") or []
80
76
  mid = float(book.get("midPx") or 0.0)
@@ -102,14 +98,12 @@ def usd_depth_in_band(
102
98
  def sz_decimals_for_asset(
103
99
  asset_to_sz_decimals: Mapping[int, int], asset_id: int
104
100
  ) -> int:
105
- """Return Hyperliquid size decimals (szDecimals) for an asset_id."""
106
101
  if asset_id not in asset_to_sz_decimals:
107
102
  raise ValueError(f"Unknown asset_id {asset_id}: missing szDecimals")
108
103
  return int(asset_to_sz_decimals[asset_id])
109
104
 
110
105
 
111
106
  def size_step(asset_to_sz_decimals: Mapping[int, int], asset_id: int) -> Decimal:
112
- """Return the size increment step for an asset_id."""
113
107
  return Decimal(10) ** (-sz_decimals_for_asset(asset_to_sz_decimals, asset_id))
114
108
 
115
109
 
@@ -120,7 +114,6 @@ def round_size_for_asset(
120
114
  *,
121
115
  ensure_min_step: bool = False,
122
116
  ) -> float:
123
- """Floor to size step using Decimal to avoid float issues."""
124
117
  size_d = size if isinstance(size, Decimal) else Decimal(str(size))
125
118
  if size_d <= 0:
126
119
  return 0.0
@@ -1,145 +1,125 @@
1
1
  # Ledger Adapter
2
2
 
3
- A Wayfinder adapter that provides high-level operations for strategy transaction history and bookkeeping. This adapter wraps the `LedgerClient` to offer strategy-friendly methods for recording and retrieving strategy operations.
3
+ Adapter for strategy transaction recording and bookkeeping.
4
4
 
5
- ## Capabilities
5
+ - **Type**: `LEDGER`
6
+ - **Module**: `wayfinder_paths.adapters.ledger_adapter.adapter.LedgerAdapter`
6
7
 
7
- - `ledger.read`: Read strategy transaction data and net deposits
8
- - `ledger.write`: Record deposits, withdrawals, and operations
9
- - `strategy.transactions`: Access strategy transaction history
10
- - `strategy.deposits`: Record deposit transactions
11
- - `strategy.withdrawals`: Record withdrawal transactions
12
- - `strategy.operations`: Record strategy operations (swaps, rebalances, etc.)
8
+ ## Overview
13
9
 
14
- ## Configuration
15
-
16
- The adapter uses the LedgerClient which automatically handles authentication and API configuration through the Wayfinder settings. No additional configuration is required.
17
-
18
- The LedgerClient will automatically:
19
- - Use the WAYFINDER_API_URL from settings
20
- - Handle authentication via config.json
21
- - Manage token refresh and retry logic
10
+ The LedgerAdapter provides:
11
+ - Transaction history tracking
12
+ - Net deposit calculations
13
+ - Deposit/withdrawal recording
14
+ - Strategy operation logging
22
15
 
23
16
  ## Usage
24
17
 
25
- ### Initialize the Adapter
26
-
27
18
  ```python
28
19
  from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
29
20
 
30
- # No configuration needed - uses LedgerClient with automatic settings
31
21
  adapter = LedgerAdapter()
32
22
  ```
33
23
 
34
- ### Get Strategy Transaction History
24
+ ## Methods
25
+
26
+ ### get_strategy_transactions
27
+
28
+ Get transaction history for a strategy wallet.
35
29
 
36
30
  ```python
37
31
  success, data = await adapter.get_strategy_transactions(
38
- wallet_address="0x1234567890123456789012345678901234567890",
32
+ wallet_address="0x...",
39
33
  limit=10,
40
- offset=0
34
+ offset=0,
41
35
  )
42
36
  if success:
43
37
  transactions = data.get("transactions", [])
44
- print(f"Found {len(transactions)} transactions")
45
- else:
46
- print(f"Error: {data}")
47
38
  ```
48
39
 
49
- ### Get Net Deposit Amount
40
+ ### get_strategy_net_deposit
41
+
42
+ Get the net deposit amount for a strategy.
50
43
 
51
44
  ```python
52
45
  success, data = await adapter.get_strategy_net_deposit(
53
- wallet_address="0x1234567890123456789012345678901234567890"
46
+ wallet_address="0x..."
54
47
  )
55
48
  if success:
56
49
  net_deposit = data.get("net_deposit", 0)
57
- print(f"Net deposit: {net_deposit} USDC")
58
- else:
59
- print(f"Error: {data}")
60
50
  ```
61
51
 
62
- ### Record a Deposit
52
+ ### record_deposit
53
+
54
+ Record a deposit transaction.
63
55
 
64
56
  ```python
65
57
  success, data = await adapter.record_deposit(
66
- wallet_address="0x1234567890123456789012345678901234567890",
58
+ wallet_address="0x...",
67
59
  chain_id=8453,
68
- token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
69
- token_amount="1000000000000000000",
60
+ token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
61
+ token_amount="1000000000",
70
62
  usd_value="1000.00",
71
- strategy_name="StablecoinYieldStrategy"
63
+ strategy_name="my_strategy",
72
64
  )
73
- if success:
74
- print(f"Deposit recorded: {data.get('transaction_id')}")
75
- else:
76
- print(f"Error: {data}")
77
65
  ```
78
66
 
79
- ### Record a Withdrawal
67
+ ### record_withdrawal
68
+
69
+ Record a withdrawal transaction.
80
70
 
81
71
  ```python
82
72
  success, data = await adapter.record_withdrawal(
83
- wallet_address="0x1234567890123456789012345678901234567890",
73
+ wallet_address="0x...",
84
74
  chain_id=8453,
85
- token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
86
- token_amount="500000000000000000",
75
+ token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
76
+ token_amount="500000000",
87
77
  usd_value="500.00",
88
- strategy_name="StablecoinYieldStrategy"
78
+ strategy_name="my_strategy",
89
79
  )
90
- if success:
91
- print(f"Withdrawal recorded: {data.get('transaction_id')}")
92
- else:
93
- print(f"Error: {data}")
94
80
  ```
95
81
 
96
- ### Record an Operation
82
+ ### record_operation
83
+
84
+ Record a strategy operation (swap, rebalance, etc.).
97
85
 
98
86
  ```python
99
87
  from wayfinder_paths.adapters.ledger_adapter.models import SWAP
100
88
 
101
89
  operation = SWAP(
102
- from_token_id="0xA0b86...",
103
- to_token_id="0xB1c97...",
104
- from_amount="1000000000000000000",
105
- to_amount="995000000000000000",
90
+ from_token_id="0x...",
91
+ to_token_id="0x...",
92
+ from_amount="1000000000",
93
+ to_amount="995000000",
106
94
  from_amount_usd=1000.0,
107
95
  to_amount_usd=995.0,
108
96
  )
109
97
 
110
- success, op = await adapter.record_operation(
111
- wallet_address=strategy_address,
98
+ success, data = await adapter.record_operation(
99
+ wallet_address="0x...",
112
100
  operation_data=operation,
113
101
  usd_value="1000.00",
114
- strategy_name="StablecoinYieldStrategy",
102
+ strategy_name="my_strategy",
115
103
  )
116
-
117
104
  ```
118
105
 
119
- ### Latest Transactions and Summaries
106
+ ### record_strategy_snapshot
107
+
108
+ Record a strategy status snapshot.
120
109
 
121
110
  ```python
122
- success, latest = await adapter.get_strategy_latest_transactions(wallet_address=strategy_address)
123
- success, summary = await adapter.get_transaction_summary(wallet_address=strategy_address, limit=5)
124
- if success:
125
- print(f"Total transactions: {summary.get('total_transactions')}")
111
+ success, data = await adapter.record_strategy_snapshot(
112
+ wallet_address="0x...",
113
+ strategy_status={"portfolio_value": 1000.0, ...},
114
+ )
126
115
  ```
127
116
 
128
- ## Error Handling
117
+ ## Dependencies
129
118
 
130
- All methods return a tuple of `(success: bool, data: Any)` where:
131
- - `success` is `True` if the operation succeeded
132
- - `data` contains the response data on success or error message on failure
119
+ - `LedgerClient` - Low-level API client
133
120
 
134
121
  ## Testing
135
122
 
136
- Run the adapter tests:
137
-
138
123
  ```bash
139
- pytest wayfinder_paths/adapters/ledger_adapter/test_adapter.py -v
124
+ poetry run pytest wayfinder_paths/adapters/ledger_adapter/ -v
140
125
  ```
141
-
142
- ## Dependencies
143
-
144
- - `LedgerClient` - Low-level API client for ledger operations
145
- - `BaseAdapter` - Base adapter class with common functionality
@@ -1,7 +1,3 @@
1
- """
2
- Ledger Adapter Package
3
- """
4
-
5
1
  from .adapter import LedgerAdapter
6
2
 
7
3
  __all__ = ["LedgerAdapter"]