wayfinder-paths 0.1.13__py3-none-any.whl → 0.1.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wayfinder-paths might be problematic. Click here for more details.
- wayfinder_paths/adapters/balance_adapter/README.md +13 -14
- wayfinder_paths/adapters/balance_adapter/adapter.py +33 -32
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
- wayfinder_paths/adapters/brap_adapter/README.md +11 -16
- wayfinder_paths/adapters/brap_adapter/adapter.py +78 -63
- wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +16 -14
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
- wayfinder_paths/adapters/pool_adapter/README.md +9 -10
- wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
- wayfinder_paths/adapters/token_adapter/README.md +2 -14
- wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
- wayfinder_paths/adapters/token_adapter/examples.json +4 -8
- wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
- wayfinder_paths/core/clients/BRAPClient.py +102 -61
- wayfinder_paths/core/clients/ClientManager.py +1 -68
- wayfinder_paths/core/clients/HyperlendClient.py +125 -64
- wayfinder_paths/core/clients/LedgerClient.py +1 -4
- wayfinder_paths/core/clients/PoolClient.py +122 -48
- wayfinder_paths/core/clients/TokenClient.py +91 -36
- wayfinder_paths/core/clients/WalletClient.py +26 -56
- wayfinder_paths/core/clients/WayfinderClient.py +28 -160
- wayfinder_paths/core/clients/__init__.py +0 -2
- wayfinder_paths/core/clients/protocols.py +35 -46
- wayfinder_paths/core/clients/sdk_example.py +37 -22
- wayfinder_paths/core/engine/StrategyJob.py +7 -55
- wayfinder_paths/core/services/local_evm_txn.py +6 -6
- wayfinder_paths/core/services/local_token_txn.py +1 -1
- wayfinder_paths/core/strategies/Strategy.py +0 -2
- wayfinder_paths/core/utils/evm_helpers.py +2 -2
- wayfinder_paths/run_strategy.py +8 -19
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +10 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +40 -25
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +3 -3
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +88 -56
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
- wayfinder_paths/templates/strategy/README.md +3 -3
- wayfinder_paths/templates/strategy/test_strategy.py +3 -2
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +14 -49
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +46 -47
- wayfinder_paths/core/clients/AuthClient.py +0 -83
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
|
@@ -34,31 +34,31 @@ class TestHyperlendAdapter:
|
|
|
34
34
|
async def test_get_stable_markets_success(self, adapter, mock_hyperlend_client):
|
|
35
35
|
"""Test successful stable markets retrieval"""
|
|
36
36
|
mock_response = {
|
|
37
|
-
"markets":
|
|
38
|
-
{
|
|
39
|
-
"chain_id": 999,
|
|
40
|
-
"underlying_token": "0x1234...",
|
|
37
|
+
"markets": {
|
|
38
|
+
"0x1234...": {
|
|
41
39
|
"symbol": "USDT",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
40
|
+
"symbol_canonical": "usdt",
|
|
41
|
+
"display_symbol": "USDT",
|
|
42
|
+
"reserve": {},
|
|
43
|
+
"decimals": 6,
|
|
44
|
+
"headroom": 1000000000000,
|
|
45
|
+
"supply_cap": 5000000000000,
|
|
46
46
|
},
|
|
47
|
-
{
|
|
48
|
-
"chain_id": 999,
|
|
49
|
-
"underlying_token": "0x5678...",
|
|
47
|
+
"0x5678...": {
|
|
50
48
|
"symbol": "USDC",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
49
|
+
"symbol_canonical": "usdc",
|
|
50
|
+
"display_symbol": "USDC",
|
|
51
|
+
"reserve": {},
|
|
52
|
+
"decimals": 6,
|
|
53
|
+
"headroom": 2000000000000,
|
|
54
|
+
"supply_cap": 10000000000000,
|
|
55
55
|
},
|
|
56
|
-
|
|
56
|
+
},
|
|
57
|
+
"notes": [],
|
|
57
58
|
}
|
|
58
59
|
mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
|
|
59
60
|
|
|
60
61
|
success, data = await adapter.get_stable_markets(
|
|
61
|
-
chain_id=999,
|
|
62
62
|
required_underlying_tokens=1000.0,
|
|
63
63
|
buffer_bps=100,
|
|
64
64
|
min_buffer_tokens=100.0,
|
|
@@ -67,11 +67,9 @@ class TestHyperlendAdapter:
|
|
|
67
67
|
assert success is True
|
|
68
68
|
assert data == mock_response
|
|
69
69
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
70
|
-
chain_id=999,
|
|
71
70
|
required_underlying_tokens=1000.0,
|
|
72
71
|
buffer_bps=100,
|
|
73
72
|
min_buffer_tokens=100.0,
|
|
74
|
-
is_stable_symbol=None,
|
|
75
73
|
)
|
|
76
74
|
|
|
77
75
|
@pytest.mark.asyncio
|
|
@@ -80,27 +78,29 @@ class TestHyperlendAdapter:
|
|
|
80
78
|
):
|
|
81
79
|
"""Test stable markets retrieval with only required chain_id"""
|
|
82
80
|
mock_response = {
|
|
83
|
-
"markets":
|
|
84
|
-
{
|
|
85
|
-
"chain_id": 999,
|
|
86
|
-
"underlying_token": "0x1234...",
|
|
81
|
+
"markets": {
|
|
82
|
+
"0x1234...": {
|
|
87
83
|
"symbol": "USDT",
|
|
88
|
-
"
|
|
84
|
+
"symbol_canonical": "usdt",
|
|
85
|
+
"display_symbol": "USDT",
|
|
86
|
+
"reserve": {},
|
|
87
|
+
"decimals": 6,
|
|
88
|
+
"headroom": 1000000000000,
|
|
89
|
+
"supply_cap": 5000000000000,
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
},
|
|
92
|
+
"notes": [],
|
|
91
93
|
}
|
|
92
94
|
mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
|
|
93
95
|
|
|
94
|
-
success, data = await adapter.get_stable_markets(
|
|
96
|
+
success, data = await adapter.get_stable_markets()
|
|
95
97
|
|
|
96
98
|
assert success is True
|
|
97
99
|
assert data == mock_response
|
|
98
100
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
99
|
-
chain_id=999,
|
|
100
101
|
required_underlying_tokens=None,
|
|
101
102
|
buffer_bps=None,
|
|
102
103
|
min_buffer_tokens=None,
|
|
103
|
-
is_stable_symbol=None,
|
|
104
104
|
)
|
|
105
105
|
|
|
106
106
|
@pytest.mark.asyncio
|
|
@@ -108,21 +108,19 @@ class TestHyperlendAdapter:
|
|
|
108
108
|
self, adapter, mock_hyperlend_client
|
|
109
109
|
):
|
|
110
110
|
"""Test stable markets retrieval with partial optional parameters"""
|
|
111
|
-
mock_response = {"markets": []}
|
|
111
|
+
mock_response = {"markets": {}, "notes": []}
|
|
112
112
|
mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
|
|
113
113
|
|
|
114
114
|
success, data = await adapter.get_stable_markets(
|
|
115
|
-
|
|
115
|
+
required_underlying_tokens=500.0
|
|
116
116
|
)
|
|
117
117
|
|
|
118
118
|
assert success is True
|
|
119
119
|
assert data == mock_response
|
|
120
120
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
121
|
-
chain_id=999,
|
|
122
121
|
required_underlying_tokens=500.0,
|
|
123
122
|
buffer_bps=None,
|
|
124
123
|
min_buffer_tokens=None,
|
|
125
|
-
is_stable_symbol=None,
|
|
126
124
|
)
|
|
127
125
|
|
|
128
126
|
@pytest.mark.asyncio
|
|
@@ -132,7 +130,7 @@ class TestHyperlendAdapter:
|
|
|
132
130
|
side_effect=Exception("API Error: Connection timeout")
|
|
133
131
|
)
|
|
134
132
|
|
|
135
|
-
success, data = await adapter.get_stable_markets(
|
|
133
|
+
success, data = await adapter.get_stable_markets()
|
|
136
134
|
|
|
137
135
|
assert success is False
|
|
138
136
|
assert "API Error: Connection timeout" in data
|
|
@@ -144,7 +142,7 @@ class TestHyperlendAdapter:
|
|
|
144
142
|
side_effect=Exception("HTTP 404 Not Found")
|
|
145
143
|
)
|
|
146
144
|
|
|
147
|
-
success, data = await adapter.get_stable_markets(
|
|
145
|
+
success, data = await adapter.get_stable_markets()
|
|
148
146
|
|
|
149
147
|
assert success is False
|
|
150
148
|
assert "404" in data or "Not Found" in data
|
|
@@ -154,14 +152,14 @@ class TestHyperlendAdapter:
|
|
|
154
152
|
self, adapter, mock_hyperlend_client
|
|
155
153
|
):
|
|
156
154
|
"""Test stable markets retrieval with empty response"""
|
|
157
|
-
mock_response = {"markets": []}
|
|
155
|
+
mock_response = {"markets": {}, "notes": []}
|
|
158
156
|
mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
|
|
159
157
|
|
|
160
|
-
success, data = await adapter.get_stable_markets(
|
|
158
|
+
success, data = await adapter.get_stable_markets()
|
|
161
159
|
|
|
162
160
|
assert success is True
|
|
163
161
|
assert data == mock_response
|
|
164
|
-
assert len(data.get("markets",
|
|
162
|
+
assert len(data.get("markets", {})) == 0
|
|
165
163
|
|
|
166
164
|
def test_adapter_type(self, adapter):
|
|
167
165
|
"""Test adapter has adapter_type"""
|
|
@@ -186,59 +184,95 @@ class TestHyperlendAdapter:
|
|
|
186
184
|
async def test_get_stable_markets_with_is_stable_symbol(
|
|
187
185
|
self, adapter, mock_hyperlend_client
|
|
188
186
|
):
|
|
189
|
-
"""Test stable markets retrieval with is_stable_symbol parameter"""
|
|
187
|
+
"""Test stable markets retrieval with is_stable_symbol parameter (ignored by API)"""
|
|
190
188
|
mock_response = {
|
|
191
|
-
"markets":
|
|
192
|
-
{
|
|
193
|
-
"chain_id": 999,
|
|
194
|
-
"underlying_token": "0x1234...",
|
|
189
|
+
"markets": {
|
|
190
|
+
"0x1234...": {
|
|
195
191
|
"symbol": "USDT",
|
|
196
|
-
"
|
|
192
|
+
"symbol_canonical": "usdt",
|
|
193
|
+
"display_symbol": "USDT",
|
|
194
|
+
"reserve": {},
|
|
195
|
+
"decimals": 6,
|
|
196
|
+
"headroom": 1000000000000,
|
|
197
|
+
"supply_cap": 5000000000000,
|
|
197
198
|
}
|
|
198
|
-
|
|
199
|
+
},
|
|
200
|
+
"notes": [],
|
|
199
201
|
}
|
|
200
202
|
mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
|
|
201
203
|
|
|
202
|
-
success, data = await adapter.get_stable_markets(
|
|
203
|
-
chain_id=999, is_stable_symbol=True
|
|
204
|
-
)
|
|
204
|
+
success, data = await adapter.get_stable_markets()
|
|
205
205
|
|
|
206
206
|
assert success is True
|
|
207
207
|
assert data == mock_response
|
|
208
208
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
209
|
-
chain_id=999,
|
|
210
209
|
required_underlying_tokens=None,
|
|
211
210
|
buffer_bps=None,
|
|
212
211
|
min_buffer_tokens=None,
|
|
213
|
-
is_stable_symbol=True,
|
|
214
212
|
)
|
|
215
213
|
|
|
216
214
|
@pytest.mark.asyncio
|
|
217
215
|
async def test_get_assets_view_success(self, adapter, mock_hyperlend_client):
|
|
218
216
|
"""Test successful assets view retrieval"""
|
|
219
217
|
mock_response = {
|
|
218
|
+
"block_number": 12345,
|
|
219
|
+
"user": "0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
220
|
+
"native_balance_wei": 0,
|
|
221
|
+
"native_balance": 0.0,
|
|
220
222
|
"assets": [
|
|
221
223
|
{
|
|
222
|
-
"
|
|
224
|
+
"underlying": "0x1234...",
|
|
223
225
|
"symbol": "USDT",
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"
|
|
226
|
+
"symbol_canonical": "usdt",
|
|
227
|
+
"symbol_display": "USDT",
|
|
228
|
+
"decimals": 6,
|
|
229
|
+
"a_token": "0x...",
|
|
230
|
+
"variable_debt_token": "0x...",
|
|
231
|
+
"usage_as_collateral_enabled": True,
|
|
232
|
+
"borrowing_enabled": True,
|
|
233
|
+
"is_active": True,
|
|
234
|
+
"is_frozen": False,
|
|
235
|
+
"is_paused": False,
|
|
236
|
+
"is_siloed_borrowing": False,
|
|
237
|
+
"is_stablecoin": True,
|
|
238
|
+
"underlying_wallet_balance": 1000.0,
|
|
239
|
+
"underlying_wallet_balance_wei": 1000000000,
|
|
240
|
+
"price_usd": 1.0,
|
|
241
|
+
"supply": 500.0,
|
|
242
|
+
"variable_borrow": 0.0,
|
|
243
|
+
"supply_usd": 500.0,
|
|
244
|
+
"variable_borrow_usd": 0.0,
|
|
245
|
+
"supply_apr": 0.05,
|
|
246
|
+
"supply_apy": 0.05,
|
|
247
|
+
"variable_borrow_apr": 0.07,
|
|
248
|
+
"variable_borrow_apy": 0.07,
|
|
227
249
|
}
|
|
228
250
|
],
|
|
229
|
-
"
|
|
251
|
+
"account_data": {
|
|
252
|
+
"total_collateral_base": 500,
|
|
253
|
+
"total_debt_base": 0,
|
|
254
|
+
"available_borrows_base": 400,
|
|
255
|
+
"current_liquidation_threshold": 8000,
|
|
256
|
+
"ltv": 7500,
|
|
257
|
+
"health_factor_wad": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
|
|
258
|
+
"health_factor": 1.157920892373162e59,
|
|
259
|
+
},
|
|
260
|
+
"base_currency_info": {
|
|
261
|
+
"marketReferenceCurrencyUnit": 100000000,
|
|
262
|
+
"marketReferenceCurrencyPriceInUsd": 100000000,
|
|
263
|
+
"networkBaseTokenPriceInUsd": 0,
|
|
264
|
+
"networkBaseTokenPriceDecimals": 8,
|
|
265
|
+
},
|
|
230
266
|
}
|
|
231
267
|
mock_hyperlend_client.get_assets_view = AsyncMock(return_value=mock_response)
|
|
232
268
|
|
|
233
269
|
success, data = await adapter.get_assets_view(
|
|
234
|
-
chain_id=999,
|
|
235
270
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
236
271
|
)
|
|
237
272
|
|
|
238
273
|
assert success is True
|
|
239
274
|
assert data == mock_response
|
|
240
275
|
mock_hyperlend_client.get_assets_view.assert_called_once_with(
|
|
241
|
-
chain_id=999,
|
|
242
276
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
243
277
|
)
|
|
244
278
|
|
|
@@ -250,7 +284,6 @@ class TestHyperlendAdapter:
|
|
|
250
284
|
)
|
|
251
285
|
|
|
252
286
|
success, data = await adapter.get_assets_view(
|
|
253
|
-
chain_id=999,
|
|
254
287
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
255
288
|
)
|
|
256
289
|
|
|
@@ -260,15 +293,36 @@ class TestHyperlendAdapter:
|
|
|
260
293
|
@pytest.mark.asyncio
|
|
261
294
|
async def test_get_assets_view_empty_response(self, adapter, mock_hyperlend_client):
|
|
262
295
|
"""Test assets view retrieval with empty response"""
|
|
263
|
-
mock_response = {
|
|
296
|
+
mock_response = {
|
|
297
|
+
"block_number": 12345,
|
|
298
|
+
"user": "0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
299
|
+
"native_balance_wei": 0,
|
|
300
|
+
"native_balance": 0.0,
|
|
301
|
+
"assets": [],
|
|
302
|
+
"account_data": {
|
|
303
|
+
"total_collateral_base": 0,
|
|
304
|
+
"total_debt_base": 0,
|
|
305
|
+
"available_borrows_base": 0,
|
|
306
|
+
"current_liquidation_threshold": 0,
|
|
307
|
+
"ltv": 0,
|
|
308
|
+
"health_factor_wad": 0,
|
|
309
|
+
"health_factor": 0.0,
|
|
310
|
+
},
|
|
311
|
+
"base_currency_info": {
|
|
312
|
+
"marketReferenceCurrencyUnit": 100000000,
|
|
313
|
+
"marketReferenceCurrencyPriceInUsd": 100000000,
|
|
314
|
+
"networkBaseTokenPriceInUsd": 0,
|
|
315
|
+
"networkBaseTokenPriceDecimals": 8,
|
|
316
|
+
},
|
|
317
|
+
}
|
|
264
318
|
mock_hyperlend_client.get_assets_view = AsyncMock(return_value=mock_response)
|
|
265
319
|
|
|
266
320
|
success, data = await adapter.get_assets_view(
|
|
267
|
-
chain_id=999,
|
|
268
321
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
269
322
|
)
|
|
270
323
|
|
|
271
324
|
assert success is True
|
|
272
325
|
assert data == mock_response
|
|
273
326
|
assert len(data.get("assets", [])) == 0
|
|
274
|
-
|
|
327
|
+
# New API uses account_data; total_value may not be present
|
|
328
|
+
assert data.get("account_data", {}).get("total_collateral_base") == 0
|
|
@@ -44,30 +44,29 @@ else:
|
|
|
44
44
|
print(f"Error: {data}")
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
### Get
|
|
47
|
+
### Get pools (all or filtered)
|
|
48
|
+
|
|
49
|
+
Pass at least `chain_id` when fetching all pools (e.g. `chain_id=8453` for Base):
|
|
48
50
|
|
|
49
51
|
```python
|
|
50
|
-
success, data = await adapter.get_pools()
|
|
52
|
+
success, data = await adapter.get_pools(chain_id=8453)
|
|
51
53
|
if success:
|
|
52
54
|
matches = data.get("matches", [])
|
|
53
|
-
print(f"Found {len(matches)} Llama matches")
|
|
54
55
|
for match in matches:
|
|
55
56
|
if match.get("stablecoin"):
|
|
56
|
-
print(f"
|
|
57
|
+
print(f"Pool {match.get('id')} - APY: {match.get('apy')}%")
|
|
57
58
|
else:
|
|
58
59
|
print(f"Error: {data}")
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
Optional: `project="lido"` to filter by project.
|
|
62
63
|
|
|
63
64
|
## API Endpoints
|
|
64
65
|
|
|
65
|
-
The adapter uses the
|
|
66
|
+
The adapter uses the Wayfinder API:
|
|
66
67
|
|
|
67
|
-
- `GET /
|
|
68
|
-
- `
|
|
69
|
-
- `GET /api/v1/public/pools/llama/matches/` - Get Llama matches
|
|
70
|
-
- `GET /api/v1/public/pools/llama/reports/` - Get Llama reports
|
|
68
|
+
- `GET /v1/blockchain/pools/?chain_id=1&project=lido` - List pools (filter by chain_id, optional project)
|
|
69
|
+
- `POST /v1/blockchain/pools/` - Get pools by IDs (body: `{"pool_ids": ["id1", "id2"]}`)
|
|
71
70
|
|
|
72
71
|
## Error Handling
|
|
73
72
|
|
|
@@ -2,7 +2,7 @@ from typing import Any
|
|
|
2
2
|
|
|
3
3
|
from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
|
|
4
4
|
from wayfinder_paths.core.clients.PoolClient import (
|
|
5
|
-
|
|
5
|
+
LlamaMatchesResponse,
|
|
6
6
|
PoolClient,
|
|
7
7
|
PoolList,
|
|
8
8
|
)
|
|
@@ -48,16 +48,15 @@ class PoolAdapter(BaseAdapter):
|
|
|
48
48
|
self.logger.error(f"Error fetching pools by IDs: {e}")
|
|
49
49
|
return (False, str(e))
|
|
50
50
|
|
|
51
|
-
async def get_pools(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"""
|
|
51
|
+
async def get_pools(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
chain_id: int | None = None,
|
|
55
|
+
project: str | None = None,
|
|
56
|
+
) -> tuple[bool, LlamaMatchesResponse | str]:
|
|
58
57
|
try:
|
|
59
|
-
data = await self.pool_client.get_pools()
|
|
58
|
+
data = await self.pool_client.get_pools(chain_id=chain_id, project=project)
|
|
60
59
|
return (True, data)
|
|
61
60
|
except Exception as e:
|
|
62
|
-
self.logger.error(f"Error fetching
|
|
61
|
+
self.logger.error(f"Error fetching pools: {e}")
|
|
63
62
|
return (False, str(e))
|
|
@@ -63,23 +63,11 @@ else:
|
|
|
63
63
|
print(f"Error: {data}")
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
### Get Token (Flexible)
|
|
67
|
-
|
|
68
|
-
```python
|
|
69
|
-
# Try by address first, then by token_id
|
|
70
|
-
success, data = await adapter.get_token(address="0x1234...", token_id="token-123")
|
|
71
|
-
if success:
|
|
72
|
-
print(f"Token data: {data}")
|
|
73
|
-
else:
|
|
74
|
-
print(f"Error: {data}")
|
|
75
|
-
```
|
|
76
|
-
|
|
77
66
|
## API Endpoints
|
|
78
67
|
|
|
79
|
-
The adapter uses the following
|
|
68
|
+
The adapter uses the following Wayfinder API endpoint:
|
|
80
69
|
|
|
81
|
-
|
|
82
|
-
2. **Token by ID**: `GET /public/tokens/lookup/?token_id={token_id}`
|
|
70
|
+
- `GET /api/v1/blockchain/tokens/detail/?query=...&market_data=...&chain_id=...`
|
|
83
71
|
|
|
84
72
|
## Error Handling
|
|
85
73
|
|
|
@@ -24,7 +24,9 @@ class TokenAdapter(BaseAdapter):
|
|
|
24
24
|
super().__init__("token_adapter", config)
|
|
25
25
|
self.token_client = token_client or TokenClient()
|
|
26
26
|
|
|
27
|
-
async def get_token(
|
|
27
|
+
async def get_token(
|
|
28
|
+
self, query: str, *, chain_id: int | None = None
|
|
29
|
+
) -> tuple[bool, TokenDetails | str]:
|
|
28
30
|
"""
|
|
29
31
|
Get token data by address using the token-details endpoint.
|
|
30
32
|
|
|
@@ -35,7 +37,7 @@ class TokenAdapter(BaseAdapter):
|
|
|
35
37
|
Tuple of (success, data) where data is the token information or error message
|
|
36
38
|
"""
|
|
37
39
|
try:
|
|
38
|
-
data = await self.token_client.get_token_details(query)
|
|
40
|
+
data = await self.token_client.get_token_details(query, chain_id=chain_id)
|
|
39
41
|
if not data:
|
|
40
42
|
return (False, f"No token found for: {query}")
|
|
41
43
|
return (True, data)
|
|
@@ -43,7 +45,9 @@ class TokenAdapter(BaseAdapter):
|
|
|
43
45
|
self.logger.error(f"Error getting token by query {query}: {e}")
|
|
44
46
|
return (False, str(e))
|
|
45
47
|
|
|
46
|
-
async def get_token_price(
|
|
48
|
+
async def get_token_price(
|
|
49
|
+
self, token_id: str, *, chain_id: int | None = None
|
|
50
|
+
) -> tuple[bool, dict[str, Any] | str]:
|
|
47
51
|
"""
|
|
48
52
|
Get token price by token ID or address using the token-details endpoint.
|
|
49
53
|
|
|
@@ -54,19 +58,21 @@ class TokenAdapter(BaseAdapter):
|
|
|
54
58
|
Tuple of (success, data) where data is the price information or error message
|
|
55
59
|
"""
|
|
56
60
|
try:
|
|
57
|
-
data = await self.token_client.get_token_details(
|
|
61
|
+
data = await self.token_client.get_token_details(
|
|
62
|
+
token_id, market_data=True, chain_id=chain_id
|
|
63
|
+
)
|
|
58
64
|
if not data:
|
|
59
65
|
return (False, f"No token found for: {token_id}")
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
price_change_24h = data.get("price_change_24h", 0.0)
|
|
62
68
|
price_data = {
|
|
63
69
|
"current_price": data.get("current_price", 0.0),
|
|
64
|
-
"price_change_24h":
|
|
65
|
-
"price_change_percentage_24h": data.get(
|
|
66
|
-
|
|
67
|
-
),
|
|
70
|
+
"price_change_24h": price_change_24h,
|
|
71
|
+
"price_change_percentage_24h": data.get("price_change_percentage_24h")
|
|
72
|
+
if data.get("price_change_percentage_24h") is not None
|
|
73
|
+
else (float(price_change_24h) * 100.0 if price_change_24h else 0.0),
|
|
68
74
|
"market_cap": data.get("market_cap", 0),
|
|
69
|
-
"total_volume": data.get("
|
|
75
|
+
"total_volume": data.get("total_volume_usd_24h", 0),
|
|
70
76
|
"symbol": data.get("symbol", ""),
|
|
71
77
|
"name": data.get("name", ""),
|
|
72
78
|
"address": data.get("address", ""),
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"basic_usage": {
|
|
3
3
|
"description": "Basic usage of TokenAdapter to get token information",
|
|
4
|
-
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Get token by address\nsuccess, data = await adapter.
|
|
4
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Get token by address (provide chain_id when querying by address)\nsuccess, data = await adapter.get_token(\n \"0x1234567890abcdef1234567890abcdef12345678\",\n chain_id=8453,\n)\nif success:\n print(f\"Token symbol: {data.get('symbol')}\")\n print(f\"Token name: {data.get('name')}\")\n print(f\"Decimals: {data.get('decimals')}\")\nelse:\n print(f\"Error: {data}\")"
|
|
5
5
|
},
|
|
6
6
|
"get_by_token_id": {
|
|
7
7
|
"description": "Get token information using token ID",
|
|
8
|
-
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Get token by ID\nsuccess, data = await adapter.
|
|
9
|
-
},
|
|
10
|
-
"flexible_lookup": {
|
|
11
|
-
"description": "Use flexible get_token method that tries both address and token_id",
|
|
12
|
-
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Try both address and token_id\nsuccess, data = await adapter.get_token(\n address=\"0x1234567890abcdef1234567890abcdef12345678\",\n token_id=\"token-12345\"\n)\nif success:\n print(f\"Found token: {data}\")\nelse:\n print(f\"Token not found: {data}\")"
|
|
8
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Get token by ID\nsuccess, data = await adapter.get_token(\"token-12345\")\nif success:\n print(f\"Token data: {data}\")\nelse:\n print(f\"Error: {data}\")"
|
|
13
9
|
},
|
|
14
10
|
"error_handling": {
|
|
15
11
|
"description": "Proper error handling for various scenarios",
|
|
16
|
-
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Handle
|
|
12
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Handle API errors\nsuccess, data = await adapter.get_token(\"invalid-address\")\nif not success:\n print(f\"API error: {data}\")\nelse:\n print(f\"Token found: {data}\")"
|
|
17
13
|
},
|
|
18
14
|
"health_check": {
|
|
19
15
|
"description": "Check adapter health and connectivity",
|
|
@@ -21,6 +17,6 @@
|
|
|
21
17
|
},
|
|
22
18
|
"batch_operations": {
|
|
23
19
|
"description": "Perform multiple token lookups efficiently",
|
|
24
|
-
"code": "from adapters.token_adapter.adapter import TokenAdapter\
|
|
20
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# List of token addresses to lookup (Base chain)\ntoken_addresses = [\n \"0x1234567890abcdef1234567890abcdef12345678\",\n \"0xabcdef1234567890abcdef1234567890abcdef12\",\n \"0x9876543210fedcba9876543210fedcba98765432\"\n]\n\n# Batch lookup\ntoken_data = {}\nfor address in token_addresses:\n success, data = await adapter.get_token(address, chain_id=8453)\n if success:\n token_data[address] = data\n else:\n print(f\"Failed to get token for {address}: {data}\")\n\nprint(f\"Successfully retrieved {len(token_data)} tokens\")\nfor address, data in token_data.items():\n print(f\"{address}: {data.get('symbol', 'Unknown')} - {data.get('name', 'Unknown')}\")"
|
|
25
21
|
}
|
|
26
22
|
}
|
|
@@ -68,9 +68,8 @@ class TestTokenAdapter:
|
|
|
68
68
|
mock_token_data = {
|
|
69
69
|
"current_price": 1.50,
|
|
70
70
|
"price_change_24h": 0.05,
|
|
71
|
-
"price_change_percentage_24h": 3.45,
|
|
72
71
|
"market_cap": 1000000,
|
|
73
|
-
"
|
|
72
|
+
"total_volume_usd_24h": 50000,
|
|
74
73
|
"symbol": "TEST",
|
|
75
74
|
"name": "Test Token",
|
|
76
75
|
"address": "0x1234...",
|
|
@@ -85,6 +84,8 @@ class TestTokenAdapter:
|
|
|
85
84
|
assert data["current_price"] == 1.50
|
|
86
85
|
assert data["symbol"] == "TEST"
|
|
87
86
|
assert data["name"] == "Test Token"
|
|
87
|
+
assert data["total_volume"] == 50000
|
|
88
|
+
assert data["price_change_percentage_24h"] == 5.0 # 0.05 * 100
|
|
88
89
|
|
|
89
90
|
@pytest.mark.asyncio
|
|
90
91
|
async def test_get_token_price_not_found(self, adapter):
|
|
@@ -99,7 +100,8 @@ class TestTokenAdapter:
|
|
|
99
100
|
async def test_get_gas_token_success(self, adapter):
|
|
100
101
|
"""Test successful gas token retrieval"""
|
|
101
102
|
mock_gas_token_data = {
|
|
102
|
-
"id": "
|
|
103
|
+
"id": "ethereum_0x0000000000000000000000000000000000000000",
|
|
104
|
+
"token_id": "ethereum_0x0000000000000000000000000000000000000000",
|
|
103
105
|
"symbol": "ETH",
|
|
104
106
|
"name": "Ethereum",
|
|
105
107
|
"address": "0x0000000000000000000000000000000000000000",
|