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.
- wayfinder_paths/__init__.py +2 -0
- wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
- wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
- wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
- wayfinder_paths/adapters/boros_adapter/client.py +476 -0
- wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
- wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
- wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
- wayfinder_paths/adapters/boros_adapter/types.py +70 -0
- wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
- wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
- wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
- wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
- wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
- wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
- wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
- wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
- wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
- wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
- wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/examples.json +0 -4
- wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
- wayfinder_paths/conftest.py +24 -17
- wayfinder_paths/core/__init__.py +2 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
- wayfinder_paths/core/adapters/models.py +17 -7
- wayfinder_paths/core/clients/BRAPClient.py +1 -1
- wayfinder_paths/core/clients/TokenClient.py +47 -1
- wayfinder_paths/core/clients/WayfinderClient.py +1 -2
- wayfinder_paths/core/clients/protocols.py +21 -22
- wayfinder_paths/core/clients/test_ledger_client.py +448 -0
- wayfinder_paths/core/config.py +12 -0
- wayfinder_paths/core/constants/__init__.py +15 -0
- wayfinder_paths/core/constants/base.py +6 -1
- wayfinder_paths/core/constants/contracts.py +39 -26
- wayfinder_paths/core/constants/erc20_abi.py +0 -1
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
- wayfinder_paths/core/constants/hyperliquid.py +16 -0
- wayfinder_paths/core/constants/moonwell_abi.py +0 -15
- wayfinder_paths/core/engine/manifest.py +66 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -61
- wayfinder_paths/core/strategies/__init__.py +10 -1
- wayfinder_paths/core/strategies/opa_loop.py +167 -0
- wayfinder_paths/core/utils/test_transaction.py +289 -0
- wayfinder_paths/core/utils/transaction.py +44 -1
- wayfinder_paths/core/utils/web3.py +3 -0
- wayfinder_paths/mcp/__init__.py +5 -0
- wayfinder_paths/mcp/preview.py +185 -0
- wayfinder_paths/mcp/scripting.py +84 -0
- wayfinder_paths/mcp/server.py +52 -0
- wayfinder_paths/mcp/state/profile_store.py +195 -0
- wayfinder_paths/mcp/state/store.py +89 -0
- wayfinder_paths/mcp/test_scripting.py +267 -0
- wayfinder_paths/mcp/tools/__init__.py +0 -0
- wayfinder_paths/mcp/tools/balances.py +290 -0
- wayfinder_paths/mcp/tools/discovery.py +158 -0
- wayfinder_paths/mcp/tools/execute.py +770 -0
- wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
- wayfinder_paths/mcp/tools/quotes.py +288 -0
- wayfinder_paths/mcp/tools/run_script.py +286 -0
- wayfinder_paths/mcp/tools/strategies.py +188 -0
- wayfinder_paths/mcp/tools/tokens.py +46 -0
- wayfinder_paths/mcp/tools/wallets.py +354 -0
- wayfinder_paths/mcp/utils.py +129 -0
- wayfinder_paths/policies/hyperliquid.py +1 -1
- wayfinder_paths/policies/lifi.py +18 -0
- wayfinder_paths/policies/util.py +8 -2
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
- wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
- wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
- wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
- wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
- wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
- wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
- wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
- wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
- wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
- wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
- wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
- wayfinder_paths/tests/test_test_coverage.py +1 -4
- wayfinder_paths-0.1.25.dist-info/METADATA +377 -0
- wayfinder_paths-0.1.25.dist-info/RECORD +185 -0
- wayfinder_paths/scripts/create_strategy.py +0 -139
- wayfinder_paths/scripts/make_wallets.py +0 -142
- wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
- wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
- /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
- {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/LICENSE +0 -0
- {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
|
wayfinder_paths/core/config.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 =
|
|
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
|
-
|
|
25
|
-
PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
|
|
37
|
+
ARBITRUM_USDC = to_checksum_address("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
HYPERLIQUID_BRIDGE = to_checksum_address("0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7")
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
BOROS_ROUTER = to_checksum_address("0x8080808080dab95efed788a9214e400ba552def6")
|
|
42
|
+
BOROS_MARKET_HUB = to_checksum_address("0x1080808080f145b14228443212e62447c112adad")
|
|
30
43
|
|
|
31
|
-
USDT_ETHEREUM = "
|
|
32
|
-
USDT_POLYGON = "
|
|
33
|
-
USDT_BSC = "
|
|
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
|
-
# 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
|
+
}
|