wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.25__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 (124) hide show
  1. wayfinder_paths/__init__.py +2 -0
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  3. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  5. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  6. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  7. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  8. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  9. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  10. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  11. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  12. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  13. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  14. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  15. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  17. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  19. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  20. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  21. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  22. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  28. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  29. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  30. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  31. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  32. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  33. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  34. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  35. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  36. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  37. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  38. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  39. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  40. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  41. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  42. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  43. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  46. wayfinder_paths/conftest.py +24 -17
  47. wayfinder_paths/core/__init__.py +2 -0
  48. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  49. wayfinder_paths/core/adapters/models.py +17 -7
  50. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  51. wayfinder_paths/core/clients/TokenClient.py +47 -1
  52. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  53. wayfinder_paths/core/clients/protocols.py +21 -22
  54. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  55. wayfinder_paths/core/config.py +12 -0
  56. wayfinder_paths/core/constants/__init__.py +15 -0
  57. wayfinder_paths/core/constants/base.py +6 -1
  58. wayfinder_paths/core/constants/contracts.py +39 -26
  59. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  60. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  61. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  62. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  63. wayfinder_paths/core/engine/manifest.py +66 -0
  64. wayfinder_paths/core/strategies/Strategy.py +0 -61
  65. wayfinder_paths/core/strategies/__init__.py +10 -1
  66. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  67. wayfinder_paths/core/utils/test_transaction.py +289 -0
  68. wayfinder_paths/core/utils/transaction.py +44 -1
  69. wayfinder_paths/core/utils/web3.py +3 -0
  70. wayfinder_paths/mcp/__init__.py +5 -0
  71. wayfinder_paths/mcp/preview.py +185 -0
  72. wayfinder_paths/mcp/scripting.py +84 -0
  73. wayfinder_paths/mcp/server.py +52 -0
  74. wayfinder_paths/mcp/state/profile_store.py +195 -0
  75. wayfinder_paths/mcp/state/store.py +89 -0
  76. wayfinder_paths/mcp/test_scripting.py +267 -0
  77. wayfinder_paths/mcp/tools/__init__.py +0 -0
  78. wayfinder_paths/mcp/tools/balances.py +290 -0
  79. wayfinder_paths/mcp/tools/discovery.py +158 -0
  80. wayfinder_paths/mcp/tools/execute.py +770 -0
  81. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  82. wayfinder_paths/mcp/tools/quotes.py +288 -0
  83. wayfinder_paths/mcp/tools/run_script.py +286 -0
  84. wayfinder_paths/mcp/tools/strategies.py +188 -0
  85. wayfinder_paths/mcp/tools/tokens.py +46 -0
  86. wayfinder_paths/mcp/tools/wallets.py +354 -0
  87. wayfinder_paths/mcp/utils.py +129 -0
  88. wayfinder_paths/policies/hyperliquid.py +1 -1
  89. wayfinder_paths/policies/lifi.py +18 -0
  90. wayfinder_paths/policies/util.py +8 -2
  91. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  92. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  93. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  106. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  107. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  108. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  109. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  110. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  111. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  112. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  113. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  114. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  115. wayfinder_paths/tests/test_test_coverage.py +1 -4
  116. wayfinder_paths-0.1.25.dist-info/METADATA +377 -0
  117. wayfinder_paths-0.1.25.dist-info/RECORD +185 -0
  118. wayfinder_paths/scripts/create_strategy.py +0 -139
  119. wayfinder_paths/scripts/make_wallets.py +0 -142
  120. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  121. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  122. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  123. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/LICENSE +0 -0
  124. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/WHEEL +0 -0
@@ -0,0 +1,448 @@
1
+ import asyncio
2
+ import json
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from wayfinder_paths.core.adapters.models import LEND, SWAP, UNLEND
9
+ from wayfinder_paths.core.clients.LedgerClient import LedgerClient
10
+
11
+
12
+ @pytest.fixture
13
+ def temp_ledger_dir():
14
+ with tempfile.TemporaryDirectory() as tmpdir:
15
+ yield Path(tmpdir)
16
+
17
+
18
+ @pytest.fixture
19
+ def ledger_client(temp_ledger_dir):
20
+ return LedgerClient(ledger_dir=temp_ledger_dir)
21
+
22
+
23
+ @pytest.fixture
24
+ def test_wallet_address():
25
+ return "0x1234567890abcdef1234567890abcdef12345678"
26
+
27
+
28
+ class TestLedgerClientInitialization:
29
+ def test_creates_ledger_directory(self, temp_ledger_dir):
30
+ assert not (temp_ledger_dir / "transactions.json").exists()
31
+
32
+ LedgerClient(ledger_dir=temp_ledger_dir)
33
+
34
+ assert (temp_ledger_dir / "transactions.json").exists()
35
+ assert (temp_ledger_dir / "snapshots.json").exists()
36
+
37
+ def test_initializes_empty_json_files(self, ledger_client, temp_ledger_dir):
38
+ transactions_data = json.loads(
39
+ (temp_ledger_dir / "transactions.json").read_text()
40
+ )
41
+ snapshots_data = json.loads((temp_ledger_dir / "snapshots.json").read_text())
42
+
43
+ assert transactions_data == {"transactions": []}
44
+ assert snapshots_data == {"snapshots": []}
45
+
46
+
47
+ class TestDepositOperations:
48
+ @pytest.mark.asyncio
49
+ async def test_add_deposit(self, ledger_client, test_wallet_address):
50
+ result = await ledger_client.add_strategy_deposit(
51
+ wallet_address=test_wallet_address,
52
+ chain_id=1,
53
+ token_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
54
+ token_amount="1000.0",
55
+ usd_value="1000.0",
56
+ strategy_name="Test Strategy",
57
+ data={"note": "Test deposit"},
58
+ )
59
+
60
+ assert result["status"] == "success"
61
+ assert "transaction_id" in result
62
+ assert "timestamp" in result
63
+
64
+ @pytest.mark.asyncio
65
+ async def test_deposit_creates_transaction_record(
66
+ self, ledger_client, test_wallet_address, temp_ledger_dir
67
+ ):
68
+ await ledger_client.add_strategy_deposit(
69
+ wallet_address=test_wallet_address,
70
+ chain_id=1,
71
+ token_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
72
+ token_amount="1000.0",
73
+ usd_value="1000.0",
74
+ strategy_name="Test Strategy",
75
+ )
76
+
77
+ data = json.loads((temp_ledger_dir / "transactions.json").read_text())
78
+ transactions = data["transactions"]
79
+
80
+ assert len(transactions) == 1
81
+ assert transactions[0]["operation"] == "DEPOSIT"
82
+ assert transactions[0]["wallet_address"] == test_wallet_address
83
+ assert transactions[0]["usd_value"] == "1000.0"
84
+ assert transactions[0]["strategy_name"] == "Test Strategy"
85
+
86
+
87
+ class TestWithdrawalOperations:
88
+ @pytest.mark.asyncio
89
+ async def test_add_withdrawal(self, ledger_client, test_wallet_address):
90
+ result = await ledger_client.add_strategy_withdraw(
91
+ wallet_address=test_wallet_address,
92
+ chain_id=1,
93
+ token_address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
94
+ token_amount="500.0",
95
+ usd_value="500.0",
96
+ strategy_name="Test Strategy",
97
+ )
98
+
99
+ assert result["status"] == "success"
100
+ assert "transaction_id" in result
101
+
102
+
103
+ class TestOperationRecording:
104
+ @pytest.mark.asyncio
105
+ async def test_add_swap_operation(self, ledger_client, test_wallet_address):
106
+ swap_op = SWAP(
107
+ adapter="TestAdapter",
108
+ from_token_id="usd-coin-base",
109
+ to_token_id="aerodrome-usdc-base",
110
+ from_amount="1000000000",
111
+ to_amount="1000000000",
112
+ from_amount_usd=1000.0,
113
+ to_amount_usd=1000.0,
114
+ transaction_hash="0xabc123",
115
+ transaction_chain_id=8453,
116
+ )
117
+
118
+ result = await ledger_client.add_strategy_operation(
119
+ wallet_address=test_wallet_address,
120
+ operation_data=swap_op.model_dump(mode="json"),
121
+ usd_value="1000.0",
122
+ strategy_name="Test Strategy",
123
+ )
124
+
125
+ assert result["status"] == "success"
126
+
127
+ @pytest.mark.asyncio
128
+ async def test_add_lend_operation(self, ledger_client, test_wallet_address):
129
+ lend_op = LEND(
130
+ adapter="TestAdapter",
131
+ token_address="0xTokenAddress",
132
+ pool_address="0xPoolContract",
133
+ amount="1000000000",
134
+ amount_usd=1000.0,
135
+ transaction_hash="0xdef456",
136
+ transaction_chain_id=8453,
137
+ )
138
+
139
+ result = await ledger_client.add_strategy_operation(
140
+ wallet_address=test_wallet_address,
141
+ operation_data=lend_op.model_dump(mode="json"),
142
+ usd_value="1000.0",
143
+ )
144
+
145
+ assert result["status"] == "success"
146
+
147
+ @pytest.mark.asyncio
148
+ async def test_add_unlend_operation(self, ledger_client, test_wallet_address):
149
+ unlend_op = UNLEND(
150
+ adapter="TestAdapter",
151
+ token_address="0xTokenAddress",
152
+ pool_address="0xPoolContract",
153
+ amount="1000000000",
154
+ amount_usd=1000.0,
155
+ transaction_hash="0xghi789",
156
+ transaction_chain_id=8453,
157
+ )
158
+
159
+ result = await ledger_client.add_strategy_operation(
160
+ wallet_address=test_wallet_address,
161
+ operation_data=unlend_op.model_dump(mode="json"),
162
+ usd_value="1000.0",
163
+ )
164
+
165
+ assert result["status"] == "success"
166
+
167
+ @pytest.mark.asyncio
168
+ async def test_operation_stores_op_data(
169
+ self, ledger_client, test_wallet_address, temp_ledger_dir
170
+ ):
171
+ swap_op = SWAP(
172
+ adapter="TestAdapter",
173
+ from_token_id="token-a",
174
+ to_token_id="token-b",
175
+ from_amount="100",
176
+ to_amount="95",
177
+ from_amount_usd=100.0,
178
+ to_amount_usd=95.0,
179
+ transaction_hash="0xjkl012",
180
+ transaction_chain_id=8453,
181
+ )
182
+
183
+ await ledger_client.add_strategy_operation(
184
+ wallet_address=test_wallet_address,
185
+ operation_data=swap_op.model_dump(mode="json"),
186
+ usd_value="100.0",
187
+ )
188
+
189
+ data = json.loads((temp_ledger_dir / "transactions.json").read_text())
190
+ transaction = data["transactions"][0]
191
+
192
+ assert transaction["operation"] == "STRAT_OP"
193
+ assert "data" in transaction
194
+ assert "op_data" in transaction["data"]
195
+ assert transaction["data"]["op_data"]["type"] == "SWAP"
196
+ assert transaction["usd_value"] == "100.0"
197
+ assert "id" in transaction
198
+ assert "timestamp" in transaction
199
+ assert "wallet_address" in transaction
200
+ # Stored minimal; amount/token_address derived when formatting
201
+ assert transaction["amount"] == "0"
202
+ assert transaction["token_address"] == ""
203
+
204
+ # Formatted output derives amount/token_address from op_data
205
+ list_result = await ledger_client.get_strategy_transactions(
206
+ wallet_address=test_wallet_address
207
+ )
208
+ txn = list_result["transactions"][0]
209
+ assert txn["amount"] == "95"
210
+ assert txn["token_address"] == "token-b"
211
+
212
+
213
+ class TestTransactionRetrieval:
214
+ @pytest.mark.asyncio
215
+ async def test_get_empty_transactions(self, ledger_client, test_wallet_address):
216
+ result = await ledger_client.get_strategy_transactions(
217
+ wallet_address=test_wallet_address
218
+ )
219
+
220
+ assert result["transactions"] == []
221
+ assert result["total"] == 0
222
+
223
+ @pytest.mark.asyncio
224
+ async def test_get_transactions_filters_by_wallet(
225
+ self, ledger_client, test_wallet_address
226
+ ):
227
+ await ledger_client.add_strategy_deposit(
228
+ wallet_address=test_wallet_address,
229
+ chain_id=1,
230
+ token_address="0xTest",
231
+ token_amount="100",
232
+ usd_value="100",
233
+ )
234
+
235
+ await ledger_client.add_strategy_deposit(
236
+ wallet_address="0xDifferentWallet",
237
+ chain_id=1,
238
+ token_address="0xTest",
239
+ token_amount="200",
240
+ usd_value="200",
241
+ )
242
+
243
+ result = await ledger_client.get_strategy_transactions(
244
+ wallet_address=test_wallet_address
245
+ )
246
+
247
+ assert result["total"] == 1
248
+ # Return shape is StrategyTransaction (no wallet_address in list items)
249
+ tx = result["transactions"][0]
250
+ assert tx["operation"] == "DEPOSIT"
251
+ assert tx["amount"] == "100"
252
+ assert tx["token_address"] == "0xTest"
253
+ assert tx["usd_value"] == "100"
254
+
255
+ @pytest.mark.asyncio
256
+ async def test_get_transactions_pagination(
257
+ self, ledger_client, test_wallet_address
258
+ ):
259
+ for i in range(5):
260
+ await ledger_client.add_strategy_deposit(
261
+ wallet_address=test_wallet_address,
262
+ chain_id=1,
263
+ token_address="0xTest",
264
+ token_amount=str(100 * (i + 1)),
265
+ usd_value=str(100 * (i + 1)),
266
+ )
267
+
268
+ result = await ledger_client.get_strategy_transactions(
269
+ wallet_address=test_wallet_address, limit=2, offset=0
270
+ )
271
+
272
+ assert result["total"] == 5
273
+ assert len(result["transactions"]) == 2
274
+ assert result["limit"] == 2
275
+ assert result["offset"] == 0
276
+
277
+ result = await ledger_client.get_strategy_transactions(
278
+ wallet_address=test_wallet_address, limit=2, offset=2
279
+ )
280
+
281
+ assert len(result["transactions"]) == 2
282
+ assert result["offset"] == 2
283
+
284
+ @pytest.mark.asyncio
285
+ async def test_get_latest_transactions(self, ledger_client, test_wallet_address):
286
+ # get_strategy_latest_transactions returns only STRAT_OP (limit 80), matching vault
287
+ for i in range(3):
288
+ await ledger_client.add_strategy_operation(
289
+ wallet_address=test_wallet_address,
290
+ operation_data={"type": "SWAP", "to_token_id": f"token-{i}"},
291
+ usd_value=str(i),
292
+ )
293
+
294
+ result = await ledger_client.get_strategy_latest_transactions(
295
+ wallet_address=test_wallet_address
296
+ )
297
+
298
+ assert len(result["transactions"]) == 3
299
+ assert result["limit"] == 80
300
+ assert result["offset"] == 0
301
+ assert result["total"] == 3
302
+ # Should be sorted by timestamp descending (most recent first)
303
+
304
+
305
+ class TestNetDepositCalculation:
306
+ @pytest.mark.asyncio
307
+ async def test_net_deposit_empty(self, ledger_client, test_wallet_address):
308
+ result = await ledger_client.get_strategy_net_deposit(
309
+ wallet_address=test_wallet_address
310
+ )
311
+
312
+ assert result == 0.0
313
+
314
+ @pytest.mark.asyncio
315
+ async def test_net_deposit_only_deposits(self, ledger_client, test_wallet_address):
316
+ await ledger_client.add_strategy_deposit(
317
+ wallet_address=test_wallet_address,
318
+ chain_id=1,
319
+ token_address="0xTest",
320
+ token_amount="1000",
321
+ usd_value="1000",
322
+ )
323
+
324
+ await ledger_client.add_strategy_deposit(
325
+ wallet_address=test_wallet_address,
326
+ chain_id=1,
327
+ token_address="0xTest",
328
+ token_amount="500",
329
+ usd_value="500",
330
+ )
331
+
332
+ result = await ledger_client.get_strategy_net_deposit(
333
+ wallet_address=test_wallet_address
334
+ )
335
+
336
+ assert result == 1500.0
337
+
338
+ @pytest.mark.asyncio
339
+ async def test_net_deposit_with_withdrawals(
340
+ self, ledger_client, test_wallet_address
341
+ ):
342
+ await ledger_client.add_strategy_deposit(
343
+ wallet_address=test_wallet_address,
344
+ chain_id=1,
345
+ token_address="0xTest",
346
+ token_amount="1000",
347
+ usd_value="1000",
348
+ )
349
+
350
+ await ledger_client.add_strategy_withdraw(
351
+ wallet_address=test_wallet_address,
352
+ chain_id=1,
353
+ token_address="0xTest",
354
+ token_amount="300",
355
+ usd_value="300",
356
+ )
357
+
358
+ result = await ledger_client.get_strategy_net_deposit(
359
+ wallet_address=test_wallet_address
360
+ )
361
+
362
+ assert result == 700.0
363
+
364
+
365
+ class TestSnapshotRecording:
366
+ @pytest.mark.asyncio
367
+ async def test_record_snapshot(self, ledger_client, test_wallet_address):
368
+ await ledger_client.strategy_snapshot(
369
+ wallet_address=test_wallet_address,
370
+ strat_portfolio_value=1050.0,
371
+ net_deposit=1000.0,
372
+ strategy_status={"current_pool": "test-pool", "apy": "5.2%"},
373
+ gas_available=0.01,
374
+ gassed_up=True,
375
+ )
376
+
377
+ # Verify snapshot was saved (no return value)
378
+ # We verify by reading the file
379
+ # This is done indirectly through the client
380
+
381
+ @pytest.mark.asyncio
382
+ async def test_snapshot_creates_record(
383
+ self, ledger_client, test_wallet_address, temp_ledger_dir
384
+ ):
385
+ await ledger_client.strategy_snapshot(
386
+ wallet_address=test_wallet_address,
387
+ strat_portfolio_value=1050.0,
388
+ net_deposit=1000.0,
389
+ strategy_status={"pool": "test"},
390
+ gas_available=0.01,
391
+ gassed_up=True,
392
+ )
393
+
394
+ data = json.loads((temp_ledger_dir / "snapshots.json").read_text())
395
+ snapshots = data["snapshots"]
396
+
397
+ assert len(snapshots) == 1
398
+ assert snapshots[0]["wallet_address"] == test_wallet_address
399
+ assert snapshots[0]["portfolio_value"] == 1050.0
400
+ assert snapshots[0]["net_deposit"] == 1000.0
401
+ assert snapshots[0]["gas_available"] == 0.01
402
+ assert snapshots[0]["gassed_up"] is True
403
+ assert snapshots[0]["strategy_status"] == {"pool": "test"}
404
+
405
+ @pytest.mark.asyncio
406
+ async def test_multiple_snapshots(
407
+ self, ledger_client, test_wallet_address, temp_ledger_dir
408
+ ):
409
+ for i in range(3):
410
+ await ledger_client.strategy_snapshot(
411
+ wallet_address=test_wallet_address,
412
+ strat_portfolio_value=1000.0 + (i * 10),
413
+ net_deposit=1000.0,
414
+ strategy_status={"iteration": i},
415
+ gas_available=0.01,
416
+ gassed_up=True,
417
+ )
418
+
419
+ data = json.loads((temp_ledger_dir / "snapshots.json").read_text())
420
+ snapshots = data["snapshots"]
421
+
422
+ assert len(snapshots) == 3
423
+ assert snapshots[0]["portfolio_value"] == 1000.0
424
+ assert snapshots[1]["portfolio_value"] == 1010.0
425
+ assert snapshots[2]["portfolio_value"] == 1020.0
426
+
427
+
428
+ class TestConcurrency:
429
+ @pytest.mark.asyncio
430
+ async def test_concurrent_writes(self, ledger_client, test_wallet_address):
431
+ async def add_deposit(amount):
432
+ await ledger_client.add_strategy_deposit(
433
+ wallet_address=test_wallet_address,
434
+ chain_id=1,
435
+ token_address="0xTest",
436
+ token_amount=str(amount),
437
+ usd_value=str(amount),
438
+ )
439
+
440
+ # Execute multiple deposits concurrently
441
+ await asyncio.gather(*[add_deposit(i * 100) for i in range(5)])
442
+
443
+ result = await ledger_client.get_strategy_transactions(
444
+ wallet_address=test_wallet_address
445
+ )
446
+
447
+ # All 5 transactions should be recorded
448
+ assert result["total"] == 5
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import os
2
3
  from pathlib import Path
3
4
  from typing import Any
4
5
 
@@ -37,3 +38,14 @@ def get_api_base_url() -> str:
37
38
  if api_url and isinstance(api_url, str):
38
39
  return api_url.strip()
39
40
  return "https://wayfinder.ai/api/v1"
41
+
42
+
43
+ def get_api_key() -> str | None:
44
+ """Get API key from config or environment."""
45
+ # Check config first
46
+ system = CONFIG.get("system", {}) if isinstance(CONFIG, dict) else {}
47
+ api_key = system.get("api_key")
48
+ if api_key and isinstance(api_key, str):
49
+ return api_key.strip()
50
+ # Fall back to environment variable
51
+ return os.environ.get("WAYFINDER_API_KEY")
@@ -37,6 +37,15 @@ from wayfinder_paths.core.constants.contracts import (
37
37
  ZERO_ADDRESS,
38
38
  )
39
39
 
40
+ from .hyperliquid import (
41
+ ARBITRUM_USDC_ADDRESS,
42
+ ARBITRUM_USDC_TOKEN_ID,
43
+ DEFAULT_HYPERLIQUID_BUILDER_FEE,
44
+ DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP,
45
+ HYPE_FEE_WALLET,
46
+ HYPERLIQUID_BRIDGE_ADDRESS,
47
+ )
48
+
40
49
  __all__ = [
41
50
  "NATIVE_TOKEN_SENTINEL",
42
51
  "ZERO_ADDRESS",
@@ -70,4 +79,10 @@ __all__ = [
70
79
  "SUPPORTED_CHAINS",
71
80
  "POA_MIDDLEWARE_CHAIN_IDS",
72
81
  "PRE_EIP_1559_CHAIN_IDS",
82
+ "HYPERLIQUID_BRIDGE_ADDRESS",
83
+ "ARBITRUM_USDC_ADDRESS",
84
+ "ARBITRUM_USDC_TOKEN_ID",
85
+ "HYPE_FEE_WALLET",
86
+ "DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP",
87
+ "DEFAULT_HYPERLIQUID_BUILDER_FEE",
73
88
  ]
@@ -6,7 +6,12 @@ SUGGESTED_GAS_PRICE_MULTIPLIER = 1.5
6
6
 
7
7
  DEFAULT_SLIPPAGE = 0.005
8
8
 
9
- DEFAULT_HTTP_TIMEOUT = 30.0
9
+ # Timeout constants (seconds)
10
+ # Base L2 (and some RPC providers) can occasionally take >2 minutes to index/return receipts,
11
+ # even if the transaction is eventually mined. A longer timeout reduces false negatives that
12
+ # can lead to unsafe retry behavior (nonce gaps, duplicate swaps, etc.).
13
+ DEFAULT_HTTP_TIMEOUT = 30.0 # HTTP client timeout
14
+ DEFAULT_TRANSACTION_TIMEOUT = 180 # Transaction receipt timeout (seconds)
10
15
 
11
16
  ADAPTER_BALANCE = "BALANCE"
12
17
  ADAPTER_BRAP = "BRAP"
@@ -1,36 +1,49 @@
1
- ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
2
- NATIVE_TOKEN_SENTINEL = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
3
-
4
- BASE_WETH = "0x4200000000000000000000000000000000000006"
5
- BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
6
- BASE_WSTETH = "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452"
7
-
8
- MOONWELL_M_USDC = "0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22"
9
- MOONWELL_M_WETH = "0x628ff693426583D9a7FB391E54366292F509D457"
10
- MOONWELL_M_WSTETH = "0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b"
11
- MOONWELL_COMPTROLLER = "0xfbb21d0380bee3312b33c4353c8936a0f13ef26c"
12
- MOONWELL_REWARD_DISTRIBUTOR = "0xe9005b078701e2a0948d2eac43010d35870ad9d2"
13
- MOONWELL_WELL_TOKEN = "0xA88594D404727625A9437C3f886C7643872296AE"
1
+ from eth_utils import to_checksum_address
14
2
 
15
- ENSO_ROUTER = "0xf75584ef6673ad213a685a1b58cc0330b8ea22cf"
16
-
17
- HYPEREVM_WHYPE = "0x5555555555555555555555555555555555555555"
18
- HYPERCORE_SENTINEL_ADDRESS = "0x2222222222222222222222222222222222222222"
3
+ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
4
+ NATIVE_TOKEN_SENTINEL = to_checksum_address(
5
+ "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
6
+ )
7
+
8
+ BASE_WETH = to_checksum_address("0x4200000000000000000000000000000000000006")
9
+ BASE_USDC = to_checksum_address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
10
+ BASE_WSTETH = to_checksum_address("0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452")
11
+
12
+ MOONWELL_M_USDC = to_checksum_address("0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22")
13
+ MOONWELL_M_WETH = to_checksum_address("0x628ff693426583D9a7FB391E54366292F509D457")
14
+ MOONWELL_M_WSTETH = to_checksum_address("0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b")
15
+ MOONWELL_COMPTROLLER = to_checksum_address("0xfBb21d0380beE3312B33c4353c8936a0F13EF26C")
16
+ MOONWELL_REWARD_DISTRIBUTOR = to_checksum_address(
17
+ "0xe9005b078701e2A0948D2EaC43010D35870Ad9d2"
18
+ )
19
+ MOONWELL_WELL_TOKEN = to_checksum_address("0xA88594D404727625A9437C3f886C7643872296AE")
20
+
21
+ ENSO_ROUTER = to_checksum_address("0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf")
22
+
23
+ HYPEREVM_WHYPE = to_checksum_address("0x5555555555555555555555555555555555555555")
24
+ HYPERCORE_SENTINEL_ADDRESS = to_checksum_address(
25
+ "0x2222222222222222222222222222222222222222"
26
+ )
19
27
  HYPERCORE_SENTINEL_VALUE = 100_000_000_000
20
28
 
21
- HYPERLEND_POOL = "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b"
22
- HYPERLEND_WRAPPED_TOKEN_GATEWAY = "0x49558c794ea2aC8974C9F27886DDfAa951E99171"
29
+ HYPERLEND_POOL = to_checksum_address("0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b")
30
+ HYPERLEND_WRAPPED_TOKEN_GATEWAY = to_checksum_address(
31
+ "0x49558c794ea2aC8974C9F27886DDfAa951E99171"
32
+ )
33
+
34
+ PRJX_ROUTER = to_checksum_address("0x1EbDFC75FfE3ba3de61E7138a3E8706aC841Af9B")
35
+ PRJX_NPM = to_checksum_address("0xeaD19AE861c29bBb2101E834922B2FEee69B9091")
23
36
 
24
- PRJX_ROUTER = "0x1ebdfc75ffe3ba3de61e7138a3e8706ac841af9b"
25
- PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
37
+ ARBITRUM_USDC = to_checksum_address("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")
26
38
 
27
- ARBITRUM_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
39
+ HYPERLIQUID_BRIDGE = to_checksum_address("0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7")
28
40
 
29
- HYPERLIQUID_BRIDGE = "0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7"
41
+ BOROS_ROUTER = to_checksum_address("0x8080808080dab95efed788a9214e400ba552def6")
42
+ BOROS_MARKET_HUB = to_checksum_address("0x1080808080f145b14228443212e62447c112adad")
30
43
 
31
- USDT_ETHEREUM = "0xdac17f958d2ee523a2206206994597c13d831ec7"
32
- USDT_POLYGON = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"
33
- USDT_BSC = "0x55d398326f99059ff775485246999027b3197955"
44
+ USDT_ETHEREUM = to_checksum_address("0xdAC17F958D2ee523a2206206994597C13D831ec7")
45
+ USDT_POLYGON = to_checksum_address("0xc2132D05D31c914a87C6611C10748AEb04B58e8F")
46
+ USDT_BSC = to_checksum_address("0x55d398326f99059fF775485246999027B3197955")
34
47
 
35
48
  TOKENS_REQUIRING_APPROVAL_RESET: set[tuple[int, str]] = {
36
49
  (1, USDT_ETHEREUM),
@@ -1,4 +1,3 @@
1
- # Standard ERC20 ABI - includes common functions needed for token operations
2
1
  ERC20_ABI = [
3
2
  {
4
3
  "constant": True,
@@ -1,4 +1,3 @@
1
- # Minimal Pool ABI for supply and deposit operations
2
1
  POOL_ABI = [
3
2
  {
4
3
  "name": "supply",
@@ -37,7 +36,6 @@ POOL_ABI = [
37
36
  },
38
37
  ]
39
38
 
40
- # Protocol Data Provider ABI for reserve token addresses and user data
41
39
  PROTOCOL_DATA_PROVIDER_ABI = [
42
40
  {
43
41
  "type": "function",
@@ -71,7 +69,6 @@ PROTOCOL_DATA_PROVIDER_ABI = [
71
69
  },
72
70
  ]
73
71
 
74
- # Wrapped Token Gateway ABI for native token operations
75
72
  WRAPPED_TOKEN_GATEWAY_ABI = [
76
73
  {
77
74
  "type": "function",
@@ -126,7 +123,6 @@ WRAPPED_TOKEN_GATEWAY_ABI = [
126
123
  },
127
124
  ]
128
125
 
129
- # WETH ABI for native token wrapping
130
126
  WETH_ABI = [
131
127
  {
132
128
  "inputs": [],
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ HYPERLIQUID_BRIDGE_ADDRESS: str = "0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7"
6
+ ARBITRUM_USDC_ADDRESS: str = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
7
+ ARBITRUM_USDC_TOKEN_ID: str = "usd-coin-arbitrum"
8
+ HYPE_FEE_WALLET: str = "0xaA1D89f333857eD78F8434CC4f896A9293EFE65c"
9
+
10
+ # Tenths of a basis point: 30 -> 0.030% (3 bps)
11
+ DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP: int = 30
12
+
13
+ DEFAULT_HYPERLIQUID_BUILDER_FEE: dict[str, Any] = {
14
+ "b": HYPE_FEE_WALLET,
15
+ "f": DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP,
16
+ }