wayfinder-paths 0.1.3__py3-none-any.whl → 0.1.5__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/CONFIG_GUIDE.md +37 -32
- wayfinder_paths/__init__.py +3 -3
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/README.md +12 -12
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/adapter.py +12 -11
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/examples.json +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/test_adapter.py +12 -6
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/README.md +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/adapter.py +30 -23
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/adapter.py +33 -26
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/README.md +27 -40
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/adapter.py +78 -75
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/examples.json +10 -4
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/test_adapter.py +33 -28
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/README.md +2 -14
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/adapter.py +12 -19
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/adapter.py +8 -4
- wayfinder_paths/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/test_adapter.py +1 -1
- wayfinder_paths/config.example.json +3 -1
- wayfinder_paths/core/__init__.py +3 -3
- wayfinder_paths/core/adapters/BaseAdapter.py +20 -3
- wayfinder_paths/core/adapters/models.py +41 -0
- wayfinder_paths/core/clients/BRAPClient.py +21 -2
- wayfinder_paths/core/clients/ClientManager.py +42 -63
- wayfinder_paths/core/clients/HyperlendClient.py +46 -5
- wayfinder_paths/core/clients/LedgerClient.py +350 -124
- wayfinder_paths/core/clients/PoolClient.py +51 -19
- wayfinder_paths/core/clients/SimulationClient.py +16 -4
- wayfinder_paths/core/clients/TokenClient.py +34 -18
- wayfinder_paths/core/clients/TransactionClient.py +18 -2
- wayfinder_paths/core/clients/WalletClient.py +35 -4
- wayfinder_paths/core/clients/WayfinderClient.py +16 -5
- wayfinder_paths/core/clients/protocols.py +69 -62
- wayfinder_paths/core/clients/sdk_example.py +0 -5
- wayfinder_paths/core/config.py +192 -103
- wayfinder_paths/core/constants/base.py +17 -0
- wayfinder_paths/core/engine/{VaultJob.py → StrategyJob.py} +25 -19
- wayfinder_paths/core/engine/__init__.py +2 -2
- wayfinder_paths/core/engine/manifest.py +1 -1
- wayfinder_paths/core/services/base.py +6 -4
- wayfinder_paths/core/services/local_evm_txn.py +3 -2
- wayfinder_paths/core/settings.py +2 -2
- wayfinder_paths/core/strategies/Strategy.py +123 -37
- wayfinder_paths/core/utils/evm_helpers.py +12 -10
- wayfinder_paths/core/wallets/README.md +3 -3
- wayfinder_paths/core/wallets/WalletManager.py +3 -3
- wayfinder_paths/{vaults/policies → policies}/enso.py +1 -1
- wayfinder_paths/{vaults/policies → policies}/hyper_evm.py +2 -2
- wayfinder_paths/{vaults/policies → policies}/hyperlend.py +1 -1
- wayfinder_paths/{vaults/policies → policies}/moonwell.py +1 -1
- wayfinder_paths/{vaults/policies → policies}/prjx.py +1 -1
- wayfinder_paths/run_strategy.py +29 -27
- wayfinder_paths/scripts/create_strategy.py +3 -3
- wayfinder_paths/scripts/make_wallets.py +6 -6
- wayfinder_paths/scripts/validate_manifests.py +2 -2
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/README.md +10 -9
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/strategy.py +47 -167
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/test_strategy.py +10 -8
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/README.md +15 -14
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/manifest.yaml +2 -2
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/strategy.py +97 -97
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/test_strategy.py +8 -8
- wayfinder_paths/{vaults/templates → templates}/adapter/README.md +5 -5
- wayfinder_paths/{vaults/templates → templates}/adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/templates → templates}/adapter/test_adapter.py +1 -1
- wayfinder_paths/{vaults/templates → templates}/strategy/README.md +10 -9
- wayfinder_paths/{vaults/templates → templates}/strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/templates → templates}/strategy/test_strategy.py +8 -8
- wayfinder_paths/tests/test_test_coverage.py +5 -5
- {wayfinder_paths-0.1.3.dist-info → wayfinder_paths-0.1.5.dist-info}/METADATA +146 -69
- wayfinder_paths-0.1.5.dist-info/RECORD +126 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +0 -7
- wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +0 -11
- wayfinder_paths/vaults/adapters/token_adapter/examples.json +0 -26
- wayfinder_paths/vaults/strategies/__init__.py +0 -0
- wayfinder_paths-0.1.3.dist-info/RECORD +0 -126
- /wayfinder_paths/{vaults → adapters}/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/policies → policies}/erc20.py +0 -0
- /wayfinder_paths/{vaults/policies → policies}/evm.py +0 -0
- /wayfinder_paths/{vaults/policies → policies}/hyperliquid.py +0 -0
- /wayfinder_paths/{vaults/policies → policies}/util.py +0 -0
- /wayfinder_paths/{vaults/adapters → strategies}/__init__.py +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/config.py +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/strategy/strategy.py +0 -0
- {wayfinder_paths-0.1.3.dist-info → wayfinder_paths-0.1.5.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.3.dist-info → wayfinder_paths-0.1.5.dist-info}/WHEEL +0 -0
|
@@ -1,86 +1,244 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, NotRequired, Required, TypedDict
|
|
6
9
|
|
|
10
|
+
from wayfinder_paths.core.adapters.models import Operation
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
|
|
13
|
+
class StrategyTransaction(TypedDict):
|
|
14
|
+
"""Individual strategy transaction structure"""
|
|
15
|
+
|
|
16
|
+
id: Required[str]
|
|
17
|
+
operation: Required[str]
|
|
18
|
+
timestamp: Required[str]
|
|
19
|
+
amount: Required[str]
|
|
20
|
+
token_address: Required[str]
|
|
21
|
+
usd_value: Required[str]
|
|
22
|
+
strategy_name: NotRequired[str | None]
|
|
23
|
+
chain_id: NotRequired[int | None]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class StrategyTransactionList(TypedDict):
|
|
27
|
+
"""Vault transaction list response structure"""
|
|
28
|
+
|
|
29
|
+
transactions: Required[list[StrategyTransaction]]
|
|
30
|
+
total: Required[int]
|
|
31
|
+
limit: Required[int]
|
|
32
|
+
offset: Required[int]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class NetDeposit(TypedDict):
|
|
36
|
+
"""Net deposit response structure"""
|
|
37
|
+
|
|
38
|
+
net_deposit: Required[str]
|
|
39
|
+
total_deposits: Required[str]
|
|
40
|
+
total_withdrawals: Required[str]
|
|
41
|
+
wallet_address: NotRequired[str | None]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TransactionRecord(TypedDict):
|
|
45
|
+
"""Transaction record response structure"""
|
|
46
|
+
|
|
47
|
+
transaction_id: Required[str]
|
|
48
|
+
status: Required[str]
|
|
49
|
+
timestamp: Required[str]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LedgerClient:
|
|
9
53
|
"""
|
|
10
|
-
Client for
|
|
54
|
+
Client for strategy transaction history and bookkeeping operations using local JSON files.
|
|
11
55
|
|
|
12
56
|
Supports:
|
|
13
|
-
- GET
|
|
14
|
-
- GET
|
|
15
|
-
- GET
|
|
57
|
+
- GET strategy transactions
|
|
58
|
+
- GET strategy net deposit
|
|
59
|
+
- GET strategy latest transactions
|
|
16
60
|
- POST add deposit
|
|
17
61
|
- POST add withdraw
|
|
18
62
|
- POST add operation
|
|
19
|
-
- POST
|
|
63
|
+
- POST record snapshot
|
|
20
64
|
"""
|
|
21
65
|
|
|
22
|
-
def __init__(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
66
|
+
def __init__(
|
|
67
|
+
self, api_key: str | None = None, ledger_dir: Path | str | None = None
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Initialize the ledger client.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
api_key: Unused, kept for backward compatibility
|
|
74
|
+
ledger_dir: Directory to store ledger JSON files. Defaults to .ledger in project root
|
|
75
|
+
"""
|
|
76
|
+
if ledger_dir is None:
|
|
77
|
+
# Default to .ledger directory in project root
|
|
78
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
79
|
+
ledger_dir = project_root / ".ledger"
|
|
80
|
+
|
|
81
|
+
self.ledger_dir = Path(ledger_dir)
|
|
82
|
+
self.ledger_dir.mkdir(parents=True, exist_ok=True)
|
|
83
|
+
|
|
84
|
+
self.transactions_file = self.ledger_dir / "transactions.json"
|
|
85
|
+
self.snapshots_file = self.ledger_dir / "snapshots.json"
|
|
86
|
+
|
|
87
|
+
# File locks for thread-safe operations
|
|
88
|
+
self._transactions_lock = asyncio.Lock()
|
|
89
|
+
self._snapshots_lock = asyncio.Lock()
|
|
90
|
+
|
|
91
|
+
# Initialize files if they don't exist
|
|
92
|
+
self._initialize_files()
|
|
93
|
+
|
|
94
|
+
def _initialize_files(self) -> None:
|
|
95
|
+
"""Create initial JSON files if they don't exist."""
|
|
96
|
+
if not self.transactions_file.exists():
|
|
97
|
+
self.transactions_file.write_text(
|
|
98
|
+
json.dumps({"transactions": []}, indent=2)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if not self.snapshots_file.exists():
|
|
102
|
+
self.snapshots_file.write_text(json.dumps({"snapshots": []}, indent=2))
|
|
103
|
+
|
|
104
|
+
async def _read_transactions(self) -> dict[str, Any]:
|
|
105
|
+
"""Read transactions from file with lock."""
|
|
106
|
+
async with self._transactions_lock:
|
|
107
|
+
if not self.transactions_file.exists():
|
|
108
|
+
return {"transactions": []}
|
|
109
|
+
try:
|
|
110
|
+
content = self.transactions_file.read_text()
|
|
111
|
+
return json.loads(content)
|
|
112
|
+
except json.JSONDecodeError:
|
|
113
|
+
return {"transactions": []}
|
|
114
|
+
|
|
115
|
+
async def _write_transactions(self, data: dict[str, Any]) -> None:
|
|
116
|
+
"""Write transactions to file with lock."""
|
|
117
|
+
async with self._transactions_lock:
|
|
118
|
+
self.transactions_file.write_text(json.dumps(data, indent=2))
|
|
119
|
+
|
|
120
|
+
async def _read_snapshots(self) -> dict[str, Any]:
|
|
121
|
+
"""Read snapshots from file with lock."""
|
|
122
|
+
async with self._snapshots_lock:
|
|
123
|
+
if not self.snapshots_file.exists():
|
|
124
|
+
return {"snapshots": []}
|
|
125
|
+
try:
|
|
126
|
+
content = self.snapshots_file.read_text()
|
|
127
|
+
return json.loads(content)
|
|
128
|
+
except json.JSONDecodeError:
|
|
129
|
+
return {"snapshots": []}
|
|
130
|
+
|
|
131
|
+
async def _write_snapshots(self, data: dict[str, Any]) -> None:
|
|
132
|
+
"""Write snapshots to file with lock."""
|
|
133
|
+
async with self._snapshots_lock:
|
|
134
|
+
self.snapshots_file.write_text(json.dumps(data, indent=2))
|
|
26
135
|
|
|
27
136
|
# ===================== Read Endpoints =====================
|
|
28
137
|
|
|
29
|
-
async def
|
|
138
|
+
async def get_strategy_transactions(
|
|
30
139
|
self,
|
|
31
140
|
*,
|
|
32
141
|
wallet_address: str,
|
|
33
|
-
limit: int =
|
|
142
|
+
limit: int = 100,
|
|
34
143
|
offset: int = 0,
|
|
35
|
-
) ->
|
|
144
|
+
) -> StrategyTransactionList:
|
|
36
145
|
"""
|
|
37
|
-
Fetch a paginated list of transactions for a given
|
|
146
|
+
Fetch a paginated list of transactions for a given strategy wallet address.
|
|
38
147
|
|
|
39
|
-
|
|
148
|
+
Args:
|
|
149
|
+
wallet_address: The strategy wallet address to filter by
|
|
150
|
+
limit: Maximum number of transactions to return
|
|
151
|
+
offset: Number of transactions to skip
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
StrategyTransactionList with transactions, total, limit, and offset
|
|
40
155
|
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
156
|
+
data = await self._read_transactions()
|
|
157
|
+
all_transactions = data.get("transactions", [])
|
|
158
|
+
|
|
159
|
+
# Filter by wallet_address
|
|
160
|
+
filtered = [
|
|
161
|
+
tx
|
|
162
|
+
for tx in all_transactions
|
|
163
|
+
if tx.get("wallet_address", "").lower() == wallet_address.lower()
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
# Sort by timestamp descending (most recent first)
|
|
167
|
+
filtered.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
|
|
168
|
+
|
|
169
|
+
total = len(filtered)
|
|
170
|
+
paginated = filtered[offset : offset + limit]
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"transactions": paginated,
|
|
174
|
+
"total": total,
|
|
175
|
+
"limit": limit,
|
|
176
|
+
"offset": offset,
|
|
46
177
|
}
|
|
47
|
-
response = await self._authed_request("GET", url, params=params)
|
|
48
|
-
data = response.json()
|
|
49
|
-
return data.get("data", data)
|
|
50
178
|
|
|
51
|
-
async def
|
|
179
|
+
async def get_strategy_net_deposit(self, *, wallet_address: str) -> NetDeposit:
|
|
52
180
|
"""
|
|
53
|
-
|
|
181
|
+
Calculate the net deposit (deposits - withdrawals) for a strategy wallet.
|
|
54
182
|
|
|
55
|
-
|
|
183
|
+
Args:
|
|
184
|
+
wallet_address: The strategy wallet address
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
NetDeposit with net_deposit, total_deposits, total_withdrawals
|
|
56
188
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
189
|
+
data = await self._read_transactions()
|
|
190
|
+
all_transactions = data.get("transactions", [])
|
|
191
|
+
|
|
192
|
+
# Filter by wallet_address
|
|
193
|
+
filtered = [
|
|
194
|
+
tx
|
|
195
|
+
for tx in all_transactions
|
|
196
|
+
if tx.get("wallet_address", "").lower() == wallet_address.lower()
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
total_deposits = 0.0
|
|
200
|
+
total_withdrawals = 0.0
|
|
201
|
+
|
|
202
|
+
for tx in filtered:
|
|
203
|
+
operation = tx.get("operation", "").upper()
|
|
204
|
+
usd_value = float(tx.get("usd_value", 0))
|
|
205
|
+
|
|
206
|
+
if operation == "DEPOSIT":
|
|
207
|
+
total_deposits += usd_value
|
|
208
|
+
elif operation == "WITHDRAW":
|
|
209
|
+
total_withdrawals += usd_value
|
|
210
|
+
|
|
211
|
+
net_deposit = total_deposits - total_withdrawals
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
"net_deposit": str(net_deposit),
|
|
215
|
+
"total_deposits": str(total_deposits),
|
|
216
|
+
"total_withdrawals": str(total_withdrawals),
|
|
59
217
|
"wallet_address": wallet_address,
|
|
60
218
|
}
|
|
61
|
-
response = await self._authed_request("GET", url, params=params)
|
|
62
|
-
data = response.json()
|
|
63
|
-
return data.get("data", data)
|
|
64
219
|
|
|
65
|
-
async def
|
|
66
|
-
self, *, wallet_address: str
|
|
67
|
-
) ->
|
|
220
|
+
async def get_strategy_latest_transactions(
|
|
221
|
+
self, *, wallet_address: str, limit: int = 10
|
|
222
|
+
) -> StrategyTransactionList:
|
|
68
223
|
"""
|
|
69
|
-
Fetch the
|
|
224
|
+
Fetch the most recent transactions for a strategy wallet.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
wallet_address: The strategy wallet address
|
|
228
|
+
limit: Maximum number of transactions to return (default 10)
|
|
70
229
|
|
|
71
|
-
|
|
230
|
+
Returns:
|
|
231
|
+
StrategyTransactionList with the latest transactions
|
|
72
232
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
data = response.json()
|
|
79
|
-
return data.get("data", data)
|
|
233
|
+
return await self.get_strategy_transactions(
|
|
234
|
+
wallet_address=wallet_address,
|
|
235
|
+
limit=limit,
|
|
236
|
+
offset=0,
|
|
237
|
+
)
|
|
80
238
|
|
|
81
239
|
# ===================== Write Endpoints =====================
|
|
82
240
|
|
|
83
|
-
async def
|
|
241
|
+
async def add_strategy_deposit(
|
|
84
242
|
self,
|
|
85
243
|
*,
|
|
86
244
|
wallet_address: str,
|
|
@@ -90,28 +248,53 @@ class LedgerClient(WayfinderClient):
|
|
|
90
248
|
usd_value: str | float,
|
|
91
249
|
data: dict[str, Any] | None = None,
|
|
92
250
|
strategy_name: str | None = None,
|
|
93
|
-
) ->
|
|
251
|
+
) -> TransactionRecord:
|
|
94
252
|
"""
|
|
95
|
-
Record a deposit for a
|
|
253
|
+
Record a deposit for a strategy.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
wallet_address: The strategy wallet address
|
|
257
|
+
chain_id: The blockchain chain ID
|
|
258
|
+
token_address: The token contract address
|
|
259
|
+
token_amount: Amount of tokens deposited
|
|
260
|
+
usd_value: USD value of the deposit
|
|
261
|
+
data: Additional metadata
|
|
262
|
+
strategy_name: Name of the strategy
|
|
96
263
|
|
|
97
|
-
|
|
264
|
+
Returns:
|
|
265
|
+
TransactionRecord with transaction_id, status, and timestamp
|
|
98
266
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
267
|
+
transaction_id = str(uuid.uuid4())
|
|
268
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
269
|
+
|
|
270
|
+
transaction = {
|
|
271
|
+
"id": transaction_id,
|
|
101
272
|
"wallet_address": wallet_address,
|
|
273
|
+
"operation": "DEPOSIT",
|
|
274
|
+
"timestamp": timestamp,
|
|
102
275
|
"chain_id": chain_id,
|
|
103
276
|
"token_address": token_address,
|
|
104
277
|
"token_amount": str(token_amount),
|
|
278
|
+
"amount": str(token_amount), # For backward compatibility
|
|
105
279
|
"usd_value": str(usd_value),
|
|
106
280
|
"data": data or {},
|
|
107
281
|
}
|
|
282
|
+
|
|
108
283
|
if strategy_name is not None:
|
|
109
|
-
|
|
110
|
-
response = await self._authed_request("POST", url, json=payload)
|
|
111
|
-
data_resp = response.json()
|
|
112
|
-
return data_resp.get("data", data_resp)
|
|
284
|
+
transaction["strategy_name"] = strategy_name
|
|
113
285
|
|
|
114
|
-
|
|
286
|
+
# Add to transactions
|
|
287
|
+
file_data = await self._read_transactions()
|
|
288
|
+
file_data["transactions"].append(transaction)
|
|
289
|
+
await self._write_transactions(file_data)
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
"transaction_id": transaction_id,
|
|
293
|
+
"status": "success",
|
|
294
|
+
"timestamp": timestamp,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async def add_strategy_withdraw(
|
|
115
298
|
self,
|
|
116
299
|
*,
|
|
117
300
|
wallet_address: str,
|
|
@@ -121,102 +304,145 @@ class LedgerClient(WayfinderClient):
|
|
|
121
304
|
usd_value: str | float,
|
|
122
305
|
data: dict[str, Any] | None = None,
|
|
123
306
|
strategy_name: str | None = None,
|
|
124
|
-
) ->
|
|
307
|
+
) -> TransactionRecord:
|
|
125
308
|
"""
|
|
126
|
-
Record a withdrawal for a
|
|
309
|
+
Record a withdrawal for a strategy.
|
|
127
310
|
|
|
128
|
-
|
|
311
|
+
Args:
|
|
312
|
+
wallet_address: The strategy wallet address
|
|
313
|
+
chain_id: The blockchain chain ID
|
|
314
|
+
token_address: The token contract address
|
|
315
|
+
token_amount: Amount of tokens withdrawn
|
|
316
|
+
usd_value: USD value of the withdrawal
|
|
317
|
+
data: Additional metadata
|
|
318
|
+
strategy_name: Name of the strategy
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
TransactionRecord with transaction_id, status, and timestamp
|
|
129
322
|
"""
|
|
130
|
-
|
|
131
|
-
|
|
323
|
+
transaction_id = str(uuid.uuid4())
|
|
324
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
325
|
+
|
|
326
|
+
transaction = {
|
|
327
|
+
"id": transaction_id,
|
|
132
328
|
"wallet_address": wallet_address,
|
|
329
|
+
"operation": "WITHDRAW",
|
|
330
|
+
"timestamp": timestamp,
|
|
133
331
|
"chain_id": chain_id,
|
|
134
332
|
"token_address": token_address,
|
|
135
333
|
"token_amount": str(token_amount),
|
|
334
|
+
"amount": str(token_amount), # For backward compatibility
|
|
136
335
|
"usd_value": str(usd_value),
|
|
137
336
|
"data": data or {},
|
|
138
337
|
}
|
|
338
|
+
|
|
139
339
|
if strategy_name is not None:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
340
|
+
transaction["strategy_name"] = strategy_name
|
|
341
|
+
|
|
342
|
+
# Add to transactions
|
|
343
|
+
file_data = await self._read_transactions()
|
|
344
|
+
file_data["transactions"].append(transaction)
|
|
345
|
+
await self._write_transactions(file_data)
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
"transaction_id": transaction_id,
|
|
349
|
+
"status": "success",
|
|
350
|
+
"timestamp": timestamp,
|
|
351
|
+
}
|
|
144
352
|
|
|
145
|
-
async def
|
|
353
|
+
async def add_strategy_operation(
|
|
146
354
|
self,
|
|
147
355
|
*,
|
|
148
356
|
wallet_address: str,
|
|
149
|
-
operation_data:
|
|
357
|
+
operation_data: Operation,
|
|
150
358
|
usd_value: str | float,
|
|
151
359
|
strategy_name: str | None = None,
|
|
152
|
-
) ->
|
|
360
|
+
) -> TransactionRecord:
|
|
153
361
|
"""
|
|
154
|
-
Record a
|
|
362
|
+
Record a strategy operation (e.g., swaps, rebalances) for bookkeeping.
|
|
155
363
|
|
|
156
|
-
|
|
364
|
+
Args:
|
|
365
|
+
wallet_address: Strategy wallet address
|
|
366
|
+
operation_data: Operation model (SWAP, LEND, UNLEND, etc.)
|
|
367
|
+
usd_value: USD value of the operation
|
|
368
|
+
strategy_name: Optional strategy name
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
TransactionRecord with transaction_id, status, and timestamp
|
|
157
372
|
"""
|
|
158
|
-
|
|
159
|
-
|
|
373
|
+
transaction_id = str(uuid.uuid4())
|
|
374
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
375
|
+
|
|
376
|
+
op_dict = operation_data.model_dump(mode="json")
|
|
377
|
+
operation_type = op_dict.get("type", "OPERATION")
|
|
378
|
+
|
|
379
|
+
transaction = {
|
|
380
|
+
"id": transaction_id,
|
|
160
381
|
"wallet_address": wallet_address,
|
|
161
|
-
"
|
|
382
|
+
"operation": operation_type,
|
|
383
|
+
"timestamp": timestamp,
|
|
162
384
|
"usd_value": str(usd_value),
|
|
385
|
+
"op_data": op_dict,
|
|
386
|
+
"data": {},
|
|
163
387
|
}
|
|
388
|
+
|
|
389
|
+
# Extract relevant fields from operation data for easier querying
|
|
390
|
+
if operation_type == "SWAP":
|
|
391
|
+
transaction["token_address"] = op_dict.get("to_token_id", "")
|
|
392
|
+
transaction["amount"] = op_dict.get("to_amount", "0")
|
|
393
|
+
elif operation_type in ("LEND", "UNLEND"):
|
|
394
|
+
transaction["token_address"] = op_dict.get("contract", "")
|
|
395
|
+
transaction["amount"] = str(op_dict.get("amount", 0))
|
|
396
|
+
|
|
164
397
|
if strategy_name is not None:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
398
|
+
transaction["strategy_name"] = strategy_name
|
|
399
|
+
|
|
400
|
+
# Add to transactions
|
|
401
|
+
file_data = await self._read_transactions()
|
|
402
|
+
file_data["transactions"].append(transaction)
|
|
403
|
+
await self._write_transactions(file_data)
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
"transaction_id": transaction_id,
|
|
407
|
+
"status": "success",
|
|
408
|
+
"timestamp": timestamp,
|
|
409
|
+
}
|
|
169
410
|
|
|
170
|
-
async def
|
|
411
|
+
async def strategy_snapshot(
|
|
171
412
|
self,
|
|
172
|
-
*,
|
|
173
413
|
wallet_address: str,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
) ->
|
|
414
|
+
strat_portfolio_value: float,
|
|
415
|
+
net_deposit: float,
|
|
416
|
+
strategy_status: dict,
|
|
417
|
+
gas_available: float,
|
|
418
|
+
gassed_up: bool,
|
|
419
|
+
) -> None:
|
|
180
420
|
"""
|
|
181
|
-
Record a
|
|
182
|
-
|
|
183
|
-
POST /api/v1/public/vaults/cashflows/
|
|
421
|
+
Record a periodic snapshot of strategy state.
|
|
184
422
|
|
|
185
423
|
Args:
|
|
186
|
-
wallet_address:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"""
|
|
196
|
-
valid_descriptions = [
|
|
197
|
-
"interest",
|
|
198
|
-
"funding",
|
|
199
|
-
"reward",
|
|
200
|
-
"fee",
|
|
201
|
-
"lend",
|
|
202
|
-
"unlend",
|
|
203
|
-
"borrow",
|
|
204
|
-
]
|
|
205
|
-
if description not in valid_descriptions:
|
|
206
|
-
raise ValueError(
|
|
207
|
-
f"Invalid description '{description}'. Must be one of: {valid_descriptions}"
|
|
208
|
-
)
|
|
424
|
+
wallet_address: Strategy wallet address
|
|
425
|
+
strat_portfolio_value: Total portfolio value in USD
|
|
426
|
+
net_deposit: Net deposit amount in USD
|
|
427
|
+
strategy_status: Arbitrary strategy status data
|
|
428
|
+
gas_available: Available gas tokens
|
|
429
|
+
gassed_up: Whether strategy has sufficient gas
|
|
430
|
+
"""
|
|
431
|
+
snapshot_id = str(uuid.uuid4())
|
|
432
|
+
timestamp = datetime.now(UTC).isoformat()
|
|
209
433
|
|
|
210
|
-
|
|
211
|
-
|
|
434
|
+
snapshot = {
|
|
435
|
+
"id": snapshot_id,
|
|
212
436
|
"wallet_address": wallet_address,
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
"
|
|
216
|
-
"
|
|
437
|
+
"timestamp": timestamp,
|
|
438
|
+
"portfolio_value": strat_portfolio_value,
|
|
439
|
+
"net_deposit": net_deposit,
|
|
440
|
+
"gas_available": gas_available,
|
|
441
|
+
"gassed_up": gassed_up,
|
|
442
|
+
"strategy_status": strategy_status,
|
|
217
443
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
444
|
+
|
|
445
|
+
# Add to snapshots
|
|
446
|
+
file_data = await self._read_snapshots()
|
|
447
|
+
file_data["snapshots"].append(snapshot)
|
|
448
|
+
await self._write_snapshots(file_data)
|
|
@@ -3,13 +3,59 @@ Pool Client
|
|
|
3
3
|
Provides read-only access to pool metadata and analytics via public endpoints.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any, NotRequired, Required, TypedDict
|
|
7
9
|
|
|
8
10
|
from wayfinder_paths.core.clients.AuthClient import AuthClient
|
|
9
11
|
from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
10
12
|
from wayfinder_paths.core.settings import settings
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
class PoolData(TypedDict):
|
|
16
|
+
"""Individual pool data structure"""
|
|
17
|
+
|
|
18
|
+
id: Required[str]
|
|
19
|
+
name: Required[str]
|
|
20
|
+
symbol: Required[str]
|
|
21
|
+
address: Required[str]
|
|
22
|
+
chain_id: Required[int]
|
|
23
|
+
chain_code: Required[str]
|
|
24
|
+
apy: NotRequired[float]
|
|
25
|
+
tvl: NotRequired[float]
|
|
26
|
+
llama_apy_pct: NotRequired[float | None]
|
|
27
|
+
llama_tvl_usd: NotRequired[float | None]
|
|
28
|
+
llama_stablecoin: NotRequired[bool | None]
|
|
29
|
+
llama_il_risk: NotRequired[str | None]
|
|
30
|
+
network: NotRequired[str | None]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PoolList(TypedDict):
|
|
34
|
+
"""Pool list response structure"""
|
|
35
|
+
|
|
36
|
+
pools: Required[list[PoolData]]
|
|
37
|
+
total: NotRequired[int | None]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LlamaMatch(TypedDict):
|
|
41
|
+
"""Llama match data structure"""
|
|
42
|
+
|
|
43
|
+
id: Required[str]
|
|
44
|
+
llama_apy_pct: Required[float]
|
|
45
|
+
llama_tvl_usd: Required[float]
|
|
46
|
+
llama_stablecoin: Required[bool]
|
|
47
|
+
llama_il_risk: Required[str]
|
|
48
|
+
network: Required[str]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class LlamaReport(TypedDict):
|
|
52
|
+
"""Llama report data structure"""
|
|
53
|
+
|
|
54
|
+
identifier: Required[str]
|
|
55
|
+
apy: NotRequired[float | None]
|
|
56
|
+
tvl: NotRequired[float | None]
|
|
57
|
+
|
|
58
|
+
|
|
13
59
|
class PoolClient(WayfinderClient):
|
|
14
60
|
"""Client for pool-related read operations"""
|
|
15
61
|
|
|
@@ -23,7 +69,7 @@ class PoolClient(WayfinderClient):
|
|
|
23
69
|
*,
|
|
24
70
|
pool_ids: str,
|
|
25
71
|
merge_external: bool | None = None,
|
|
26
|
-
) ->
|
|
72
|
+
) -> PoolList:
|
|
27
73
|
"""
|
|
28
74
|
Fetch pools by comma-separated pool ids.
|
|
29
75
|
|
|
@@ -39,9 +85,7 @@ class PoolClient(WayfinderClient):
|
|
|
39
85
|
data = response.json()
|
|
40
86
|
return data.get("data", data)
|
|
41
87
|
|
|
42
|
-
async def get_all_pools(
|
|
43
|
-
self, *, merge_external: bool | None = None
|
|
44
|
-
) -> dict[str, Any]:
|
|
88
|
+
async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
|
|
45
89
|
"""
|
|
46
90
|
Fetch all pools.
|
|
47
91
|
|
|
@@ -57,19 +101,7 @@ class PoolClient(WayfinderClient):
|
|
|
57
101
|
data = response.json()
|
|
58
102
|
return data.get("data", data)
|
|
59
103
|
|
|
60
|
-
async def
|
|
61
|
-
"""
|
|
62
|
-
Fetch combined pool reports.
|
|
63
|
-
|
|
64
|
-
GET /api/v1/public/pools/combined/
|
|
65
|
-
"""
|
|
66
|
-
url = f"{self.api_base_url}/public/pools/combined/"
|
|
67
|
-
response = await self._request("GET", url, headers={})
|
|
68
|
-
response.raise_for_status()
|
|
69
|
-
data = response.json()
|
|
70
|
-
return data.get("data", data)
|
|
71
|
-
|
|
72
|
-
async def get_llama_matches(self) -> dict[str, Any]:
|
|
104
|
+
async def get_llama_matches(self) -> dict[str, LlamaMatch]:
|
|
73
105
|
"""
|
|
74
106
|
Fetch Llama matches for pools.
|
|
75
107
|
|
|
@@ -81,7 +113,7 @@ class PoolClient(WayfinderClient):
|
|
|
81
113
|
data = response.json()
|
|
82
114
|
return data.get("data", data)
|
|
83
115
|
|
|
84
|
-
async def get_llama_reports(self, *, identifiers: str) -> dict[str,
|
|
116
|
+
async def get_llama_reports(self, *, identifiers: str) -> dict[str, LlamaReport]:
|
|
85
117
|
"""
|
|
86
118
|
Fetch Llama reports using identifiers (token ids, address_network, or pool ids).
|
|
87
119
|
|