wayfinder-paths 0.1.14__py3-none-any.whl → 0.1.15__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/adapter.py +40 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +3 -3
- wayfinder_paths/adapters/brap_adapter/adapter.py +66 -15
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +14 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +7 -7
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +332 -9
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +13 -13
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/token_adapter/test_adapter.py +4 -4
- wayfinder_paths/core/constants/erc20_abi.py +0 -11
- wayfinder_paths/core/engine/StrategyJob.py +3 -1
- wayfinder_paths/core/services/base.py +1 -0
- wayfinder_paths/core/services/local_evm_txn.py +19 -3
- wayfinder_paths/core/services/local_token_txn.py +1 -5
- wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
- wayfinder_paths/core/strategies/Strategy.py +16 -2
- wayfinder_paths/core/utils/evm_helpers.py +0 -7
- wayfinder_paths/policies/erc20.py +1 -1
- wayfinder_paths/run_strategy.py +5 -0
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +67 -0
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +6 -6
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +71 -2
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -5
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2249 -1282
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +282 -121
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +65 -0
- wayfinder_paths/templates/adapter/README.md +1 -1
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.15.dist-info}/METADATA +1 -1
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.15.dist-info}/RECORD +35 -35
- wayfinder_paths/abis/generic/erc20.json +0 -383
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.15.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.15.dist-info}/WHEEL +0 -0
|
@@ -116,6 +116,46 @@ class BalanceAdapter(BaseAdapter):
|
|
|
116
116
|
skip_ledger=skip_ledger,
|
|
117
117
|
)
|
|
118
118
|
|
|
119
|
+
async def send_to_address(
|
|
120
|
+
self,
|
|
121
|
+
token_id: str,
|
|
122
|
+
amount: float,
|
|
123
|
+
from_wallet: dict[str, Any] | None,
|
|
124
|
+
to_address: str,
|
|
125
|
+
skip_ledger: bool = True,
|
|
126
|
+
) -> tuple[bool, Any]:
|
|
127
|
+
"""Send tokens from a wallet to an arbitrary address (e.g., bridge contract)."""
|
|
128
|
+
if self.token_transactions is None:
|
|
129
|
+
return False, "Token transaction service not configured"
|
|
130
|
+
|
|
131
|
+
from_address = self._wallet_address(from_wallet)
|
|
132
|
+
if not from_address:
|
|
133
|
+
return False, "from_wallet missing or invalid"
|
|
134
|
+
|
|
135
|
+
if not to_address:
|
|
136
|
+
return False, "to_address is required"
|
|
137
|
+
|
|
138
|
+
token_info = await self.token_client.get_token_details(token_id)
|
|
139
|
+
if not token_info:
|
|
140
|
+
return False, f"Token not found: {token_id}"
|
|
141
|
+
|
|
142
|
+
build_success, tx_data = await self.token_transactions.build_send(
|
|
143
|
+
token_id=token_id,
|
|
144
|
+
amount=amount,
|
|
145
|
+
from_address=from_address,
|
|
146
|
+
to_address=to_address,
|
|
147
|
+
token_info=token_info,
|
|
148
|
+
)
|
|
149
|
+
if not build_success:
|
|
150
|
+
return False, tx_data
|
|
151
|
+
|
|
152
|
+
tx = tx_data
|
|
153
|
+
broadcast_result = await self.wallet_provider.broadcast_transaction(
|
|
154
|
+
tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return broadcast_result
|
|
158
|
+
|
|
119
159
|
async def _move_between_wallets(
|
|
120
160
|
self,
|
|
121
161
|
*,
|
|
@@ -79,7 +79,7 @@ class TestBalanceAdapter:
|
|
|
79
79
|
wallet_address="0xWallet",
|
|
80
80
|
)
|
|
81
81
|
|
|
82
|
-
assert success
|
|
82
|
+
assert success
|
|
83
83
|
assert balance == 1000000
|
|
84
84
|
mock_token_client.get_token_details.assert_called_once_with("usd-coin-base")
|
|
85
85
|
mock_wallet_client.get_token_balance_for_address.assert_called_once_with(
|
|
@@ -108,7 +108,7 @@ class TestBalanceAdapter:
|
|
|
108
108
|
query={"token_id": "wsteth-base"},
|
|
109
109
|
wallet_address="0x123",
|
|
110
110
|
)
|
|
111
|
-
assert success
|
|
111
|
+
assert success
|
|
112
112
|
assert balance == 3000000
|
|
113
113
|
mock_token_client.get_token_details.assert_called_once_with("wsteth-base")
|
|
114
114
|
mock_wallet_client.get_token_balance_for_address.assert_called_once_with(
|
|
@@ -140,7 +140,7 @@ class TestBalanceAdapter:
|
|
|
140
140
|
chain_id=8453,
|
|
141
141
|
)
|
|
142
142
|
|
|
143
|
-
assert success
|
|
143
|
+
assert success
|
|
144
144
|
assert balance == 5000000
|
|
145
145
|
mock_wallet_client.get_token_balance_for_address.assert_called_once_with(
|
|
146
146
|
wallet_address="0xWallet",
|
|
@@ -442,7 +442,31 @@ class BRAPAdapter(BaseAdapter):
|
|
|
442
442
|
# (Calldata may include either "from" or "from_address" depending on provider.)
|
|
443
443
|
transaction["from"] = to_checksum_address(from_address)
|
|
444
444
|
|
|
445
|
-
|
|
445
|
+
def _as_address(value: Any) -> str | None:
|
|
446
|
+
if not isinstance(value, str):
|
|
447
|
+
return None
|
|
448
|
+
v = value.strip()
|
|
449
|
+
if (
|
|
450
|
+
v.startswith("0x")
|
|
451
|
+
and len(v) == 42
|
|
452
|
+
and v.lower() != ZERO_ADDRESS.lower()
|
|
453
|
+
):
|
|
454
|
+
return v
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
spender = (
|
|
458
|
+
_as_address(transaction.get("allowanceTarget"))
|
|
459
|
+
or _as_address(transaction.get("allowance_target"))
|
|
460
|
+
or _as_address(transaction.get("approvalAddress"))
|
|
461
|
+
or _as_address(transaction.get("approval_address"))
|
|
462
|
+
or _as_address(transaction.get("spender"))
|
|
463
|
+
or _as_address(quote.get("allowanceTarget"))
|
|
464
|
+
or _as_address(quote.get("allowance_target"))
|
|
465
|
+
or _as_address(quote.get("approvalAddress"))
|
|
466
|
+
or _as_address(quote.get("approval_address"))
|
|
467
|
+
or _as_address(quote.get("spender"))
|
|
468
|
+
or _as_address(transaction.get("to"))
|
|
469
|
+
)
|
|
446
470
|
approve_amount = (
|
|
447
471
|
quote.get("input_amount")
|
|
448
472
|
or quote.get("inputAmount")
|
|
@@ -469,13 +493,35 @@ class BRAPAdapter(BaseAdapter):
|
|
|
469
493
|
broadcast_success, broadcast_response = await self._broadcast_transaction(
|
|
470
494
|
transaction
|
|
471
495
|
)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
496
|
+
# Log only key fields to avoid spamming raw HexBytes logs
|
|
497
|
+
if isinstance(broadcast_response, dict):
|
|
498
|
+
tx_hash_log = broadcast_response.get("tx_hash", "unknown")
|
|
499
|
+
block_log = broadcast_response.get("block_number", "unknown")
|
|
500
|
+
status_log = (
|
|
501
|
+
broadcast_response.get("receipt", {}).get("status", "unknown")
|
|
502
|
+
if isinstance(broadcast_response.get("receipt"), dict)
|
|
503
|
+
else "unknown"
|
|
504
|
+
)
|
|
505
|
+
self.logger.info(
|
|
506
|
+
f"Swap broadcast: success={broadcast_success}, tx={tx_hash_log}, block={block_log}, status={status_log}"
|
|
507
|
+
)
|
|
508
|
+
else:
|
|
509
|
+
self.logger.info(f"Swap broadcast: success={broadcast_success}")
|
|
476
510
|
if not broadcast_success:
|
|
477
511
|
return (False, broadcast_response)
|
|
478
512
|
|
|
513
|
+
tx_hash = None
|
|
514
|
+
block_number = None
|
|
515
|
+
confirmations = None
|
|
516
|
+
confirmed_block_number = None
|
|
517
|
+
if isinstance(broadcast_response, dict):
|
|
518
|
+
tx_hash = broadcast_response.get("tx_hash") or broadcast_response.get(
|
|
519
|
+
"transaction_hash"
|
|
520
|
+
)
|
|
521
|
+
block_number = broadcast_response.get("block_number")
|
|
522
|
+
confirmations = broadcast_response.get("confirmations")
|
|
523
|
+
confirmed_block_number = broadcast_response.get("confirmed_block_number")
|
|
524
|
+
|
|
479
525
|
# Record the swap operation in ledger - but don't let ledger errors fail the swap
|
|
480
526
|
# since the on-chain transaction already succeeded
|
|
481
527
|
try:
|
|
@@ -491,15 +537,20 @@ class BRAPAdapter(BaseAdapter):
|
|
|
491
537
|
self.logger.warning(
|
|
492
538
|
f"Ledger recording failed (swap succeeded on-chain): {e}"
|
|
493
539
|
)
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
540
|
+
ledger_record = {}
|
|
541
|
+
|
|
542
|
+
result_payload: dict[str, Any] = {
|
|
543
|
+
"from_amount": quote.get("input_amount"),
|
|
544
|
+
"to_amount": quote.get("output_amount"),
|
|
545
|
+
"tx_hash": tx_hash,
|
|
546
|
+
"block_number": block_number,
|
|
547
|
+
"confirmations": confirmations,
|
|
548
|
+
"confirmed_block_number": confirmed_block_number,
|
|
549
|
+
}
|
|
550
|
+
if isinstance(ledger_record, dict):
|
|
551
|
+
result_payload.update(ledger_record)
|
|
552
|
+
|
|
553
|
+
return (True, result_payload)
|
|
503
554
|
|
|
504
555
|
async def get_bridge_quote(
|
|
505
556
|
self,
|
|
@@ -710,7 +761,7 @@ class BRAPAdapter(BaseAdapter):
|
|
|
710
761
|
return await self._broadcast_transaction(approve_tx, confirmations=2)
|
|
711
762
|
|
|
712
763
|
async def _broadcast_transaction(
|
|
713
|
-
self, transaction: dict[str, Any], confirmations: int =
|
|
764
|
+
self, transaction: dict[str, Any], confirmations: int | None = None
|
|
714
765
|
) -> tuple[bool, Any]:
|
|
715
766
|
return await self.wallet_provider.broadcast_transaction(
|
|
716
767
|
transaction,
|
|
@@ -83,7 +83,7 @@ class TestBRAPAdapter:
|
|
|
83
83
|
slippage=0.01,
|
|
84
84
|
)
|
|
85
85
|
|
|
86
|
-
assert success
|
|
86
|
+
assert success
|
|
87
87
|
assert data == mock_response
|
|
88
88
|
mock_brap_client.get_quote.assert_called_once_with(
|
|
89
89
|
from_token="0x" + "a" * 40,
|
|
@@ -125,7 +125,7 @@ class TestBRAPAdapter:
|
|
|
125
125
|
amount="1000000000000000000",
|
|
126
126
|
)
|
|
127
127
|
|
|
128
|
-
assert success
|
|
128
|
+
assert success
|
|
129
129
|
assert data["input_amount"] == 1000000000000000000
|
|
130
130
|
assert data["output_amount"] == 995000000000000000
|
|
131
131
|
|
|
@@ -183,7 +183,7 @@ class TestBRAPAdapter:
|
|
|
183
183
|
slippage=0.01,
|
|
184
184
|
)
|
|
185
185
|
|
|
186
|
-
assert success
|
|
186
|
+
assert success
|
|
187
187
|
assert data["input_amount"] == 1000000000000000000
|
|
188
188
|
assert data["output_amount"] == 995000000000000000
|
|
189
189
|
assert data["gas_fee"] == 5000000000000000
|
|
@@ -242,7 +242,7 @@ class TestBRAPAdapter:
|
|
|
242
242
|
amount="1000000000000000000",
|
|
243
243
|
)
|
|
244
244
|
|
|
245
|
-
assert success
|
|
245
|
+
assert success
|
|
246
246
|
assert data["total_routes"] == 2
|
|
247
247
|
assert len(data["all_routes"]) == 2
|
|
248
248
|
assert data["best_route"]["output_amount"] == 995000000000000000
|
|
@@ -254,7 +254,7 @@ class TestBRAPAdapter:
|
|
|
254
254
|
from_chain_id=8453, to_chain_id=1, operation_type="swap"
|
|
255
255
|
)
|
|
256
256
|
|
|
257
|
-
assert success
|
|
257
|
+
assert success
|
|
258
258
|
assert data["from_chain"] == "base"
|
|
259
259
|
assert data["to_chain"] == "ethereum"
|
|
260
260
|
assert data["from_gas_estimate"] == 100000
|
|
@@ -288,7 +288,7 @@ class TestBRAPAdapter:
|
|
|
288
288
|
amount="1000000000000000000",
|
|
289
289
|
)
|
|
290
290
|
|
|
291
|
-
assert success
|
|
291
|
+
assert success
|
|
292
292
|
assert data["valid"] is True
|
|
293
293
|
assert data["quote_available"] is True
|
|
294
294
|
assert data["estimated_output"] == "995000000000000000"
|
|
@@ -77,6 +77,20 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
77
77
|
buffer_bps=buffer_bps,
|
|
78
78
|
min_buffer_tokens=min_buffer_tokens,
|
|
79
79
|
)
|
|
80
|
+
# Strategies expect a dict with "markets" and "notes"; normalize if API returns a list
|
|
81
|
+
if isinstance(data, list):
|
|
82
|
+
markets: dict[str, Any] = {}
|
|
83
|
+
for i, item in enumerate(data):
|
|
84
|
+
if isinstance(item, dict) and "address" in item:
|
|
85
|
+
markets[item["address"]] = item
|
|
86
|
+
elif isinstance(item, dict):
|
|
87
|
+
markets[str(i)] = item
|
|
88
|
+
data = {"markets": markets, "notes": []}
|
|
89
|
+
elif isinstance(data, dict) and ("markets" not in data or "notes" not in data):
|
|
90
|
+
data = {
|
|
91
|
+
"markets": data.get("markets", {}),
|
|
92
|
+
"notes": data.get("notes", []),
|
|
93
|
+
}
|
|
80
94
|
return True, data
|
|
81
95
|
except Exception as exc:
|
|
82
96
|
return False, str(exc)
|
|
@@ -64,7 +64,7 @@ class TestHyperlendAdapter:
|
|
|
64
64
|
min_buffer_tokens=100.0,
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
-
assert success
|
|
67
|
+
assert success
|
|
68
68
|
assert data == mock_response
|
|
69
69
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
70
70
|
required_underlying_tokens=1000.0,
|
|
@@ -95,7 +95,7 @@ class TestHyperlendAdapter:
|
|
|
95
95
|
|
|
96
96
|
success, data = await adapter.get_stable_markets()
|
|
97
97
|
|
|
98
|
-
assert success
|
|
98
|
+
assert success
|
|
99
99
|
assert data == mock_response
|
|
100
100
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
101
101
|
required_underlying_tokens=None,
|
|
@@ -115,7 +115,7 @@ class TestHyperlendAdapter:
|
|
|
115
115
|
required_underlying_tokens=500.0
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
-
assert success
|
|
118
|
+
assert success
|
|
119
119
|
assert data == mock_response
|
|
120
120
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
121
121
|
required_underlying_tokens=500.0,
|
|
@@ -157,7 +157,7 @@ class TestHyperlendAdapter:
|
|
|
157
157
|
|
|
158
158
|
success, data = await adapter.get_stable_markets()
|
|
159
159
|
|
|
160
|
-
assert success
|
|
160
|
+
assert success
|
|
161
161
|
assert data == mock_response
|
|
162
162
|
assert len(data.get("markets", {})) == 0
|
|
163
163
|
|
|
@@ -203,7 +203,7 @@ class TestHyperlendAdapter:
|
|
|
203
203
|
|
|
204
204
|
success, data = await adapter.get_stable_markets()
|
|
205
205
|
|
|
206
|
-
assert success
|
|
206
|
+
assert success
|
|
207
207
|
assert data == mock_response
|
|
208
208
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
209
209
|
required_underlying_tokens=None,
|
|
@@ -270,7 +270,7 @@ class TestHyperlendAdapter:
|
|
|
270
270
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
271
271
|
)
|
|
272
272
|
|
|
273
|
-
assert success
|
|
273
|
+
assert success
|
|
274
274
|
assert data == mock_response
|
|
275
275
|
mock_hyperlend_client.get_assets_view.assert_called_once_with(
|
|
276
276
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
@@ -321,7 +321,7 @@ class TestHyperlendAdapter:
|
|
|
321
321
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
322
322
|
)
|
|
323
323
|
|
|
324
|
-
assert success
|
|
324
|
+
assert success
|
|
325
325
|
assert data == mock_response
|
|
326
326
|
assert len(data.get("assets", [])) == 0
|
|
327
327
|
# New API uses account_data; total_value may not be present
|
|
@@ -72,41 +72,41 @@ class TestHyperliquidAdapter:
|
|
|
72
72
|
async def test_get_meta_and_asset_ctxs(self, adapter):
|
|
73
73
|
"""Test fetching market metadata."""
|
|
74
74
|
success, data = await adapter.get_meta_and_asset_ctxs()
|
|
75
|
-
assert success
|
|
75
|
+
assert success
|
|
76
76
|
assert "universe" in data[0]
|
|
77
77
|
|
|
78
78
|
@pytest.mark.asyncio
|
|
79
79
|
async def test_get_spot_meta(self, adapter):
|
|
80
80
|
"""Test fetching spot metadata."""
|
|
81
81
|
success, data = await adapter.get_spot_meta()
|
|
82
|
-
assert success
|
|
82
|
+
assert success
|
|
83
83
|
|
|
84
84
|
@pytest.mark.asyncio
|
|
85
85
|
async def test_get_funding_history(self, adapter):
|
|
86
86
|
"""Test fetching funding history."""
|
|
87
87
|
success, data = await adapter.get_funding_history("ETH", 1700000000000)
|
|
88
|
-
assert success
|
|
88
|
+
assert success
|
|
89
89
|
assert isinstance(data, list)
|
|
90
90
|
|
|
91
91
|
@pytest.mark.asyncio
|
|
92
92
|
async def test_get_candles(self, adapter):
|
|
93
93
|
"""Test fetching candle data."""
|
|
94
94
|
success, data = await adapter.get_candles("ETH", "1h", 1700000000000)
|
|
95
|
-
assert success
|
|
95
|
+
assert success
|
|
96
96
|
assert isinstance(data, list)
|
|
97
97
|
|
|
98
98
|
@pytest.mark.asyncio
|
|
99
99
|
async def test_get_l2_book(self, adapter):
|
|
100
100
|
"""Test fetching order book."""
|
|
101
101
|
success, data = await adapter.get_l2_book("ETH")
|
|
102
|
-
assert success
|
|
102
|
+
assert success
|
|
103
103
|
assert "levels" in data
|
|
104
104
|
|
|
105
105
|
@pytest.mark.asyncio
|
|
106
106
|
async def test_get_user_state(self, adapter):
|
|
107
107
|
"""Test fetching user state."""
|
|
108
108
|
success, data = await adapter.get_user_state("0x1234")
|
|
109
|
-
assert success
|
|
109
|
+
assert success
|
|
110
110
|
assert "assetPositions" in data
|
|
111
111
|
|
|
112
112
|
@pytest.mark.asyncio
|
|
@@ -28,7 +28,7 @@ class TestSpotAssetIDs:
|
|
|
28
28
|
"""Verify get_spot_assets returns a populated dict."""
|
|
29
29
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
30
30
|
|
|
31
|
-
assert success
|
|
31
|
+
assert success
|
|
32
32
|
assert isinstance(spot_assets, dict)
|
|
33
33
|
assert len(spot_assets) > 0
|
|
34
34
|
|
|
@@ -37,7 +37,7 @@ class TestSpotAssetIDs:
|
|
|
37
37
|
"""PURR/USDC should be the first spot pair (index 0 + 10000 = 10000)."""
|
|
38
38
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
39
39
|
|
|
40
|
-
assert success
|
|
40
|
+
assert success
|
|
41
41
|
assert "PURR/USDC" in spot_assets
|
|
42
42
|
assert spot_assets["PURR/USDC"] == 10000
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ class TestSpotAssetIDs:
|
|
|
46
46
|
"""HYPE/USDC should have asset ID 10107."""
|
|
47
47
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
48
48
|
|
|
49
|
-
assert success
|
|
49
|
+
assert success
|
|
50
50
|
assert "HYPE/USDC" in spot_assets
|
|
51
51
|
# HYPE is index 107, so asset_id = 10107
|
|
52
52
|
assert spot_assets["HYPE/USDC"] == 10107
|
|
@@ -56,7 +56,7 @@ class TestSpotAssetIDs:
|
|
|
56
56
|
"""ETH/USDC spot pair should exist."""
|
|
57
57
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
58
58
|
|
|
59
|
-
assert success
|
|
59
|
+
assert success
|
|
60
60
|
# ETH spot may have different naming, check common variants
|
|
61
61
|
eth_pairs = [k for k in spot_assets if "ETH" in k and "USDC" in k]
|
|
62
62
|
assert len(eth_pairs) > 0, (
|
|
@@ -68,7 +68,7 @@ class TestSpotAssetIDs:
|
|
|
68
68
|
"""BTC/USDC spot pair should exist."""
|
|
69
69
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
70
70
|
|
|
71
|
-
assert success
|
|
71
|
+
assert success
|
|
72
72
|
# BTC spot may have different naming
|
|
73
73
|
btc_pairs = [k for k in spot_assets if "BTC" in k and "USDC" in k]
|
|
74
74
|
assert len(btc_pairs) > 0, (
|
|
@@ -80,7 +80,7 @@ class TestSpotAssetIDs:
|
|
|
80
80
|
"""All spot asset IDs should be >= 10000."""
|
|
81
81
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
82
82
|
|
|
83
|
-
assert success
|
|
83
|
+
assert success
|
|
84
84
|
for name, asset_id in spot_assets.items():
|
|
85
85
|
assert asset_id >= 10000, f"{name} has invalid asset_id {asset_id}"
|
|
86
86
|
|
|
@@ -89,7 +89,7 @@ class TestSpotAssetIDs:
|
|
|
89
89
|
"""Test synchronous helper after cache is populated."""
|
|
90
90
|
# First populate cache
|
|
91
91
|
success, _ = await live_adapter.get_spot_assets()
|
|
92
|
-
assert success
|
|
92
|
+
assert success
|
|
93
93
|
|
|
94
94
|
# Now use sync helper
|
|
95
95
|
purr_id = live_adapter.get_spot_asset_id("PURR", "USDC")
|
|
@@ -147,7 +147,7 @@ class TestSpotMetaStructure:
|
|
|
147
147
|
"""Spot meta should have tokens array."""
|
|
148
148
|
success, spot_meta = await live_adapter.get_spot_meta()
|
|
149
149
|
|
|
150
|
-
assert success
|
|
150
|
+
assert success
|
|
151
151
|
assert "tokens" in spot_meta
|
|
152
152
|
assert isinstance(spot_meta["tokens"], list)
|
|
153
153
|
assert len(spot_meta["tokens"]) > 0
|
|
@@ -157,7 +157,7 @@ class TestSpotMetaStructure:
|
|
|
157
157
|
"""Spot meta should have universe array with pairs."""
|
|
158
158
|
success, spot_meta = await live_adapter.get_spot_meta()
|
|
159
159
|
|
|
160
|
-
assert success
|
|
160
|
+
assert success
|
|
161
161
|
assert "universe" in spot_meta
|
|
162
162
|
assert isinstance(spot_meta["universe"], list)
|
|
163
163
|
assert len(spot_meta["universe"]) > 0
|
|
@@ -167,7 +167,7 @@ class TestSpotMetaStructure:
|
|
|
167
167
|
"""Each spot universe entry should have tokens and index."""
|
|
168
168
|
success, spot_meta = await live_adapter.get_spot_meta()
|
|
169
169
|
|
|
170
|
-
assert success
|
|
170
|
+
assert success
|
|
171
171
|
for pair in spot_meta["universe"][:5]: # Check first 5
|
|
172
172
|
assert "tokens" in pair, f"Missing tokens in {pair}"
|
|
173
173
|
assert "index" in pair, f"Missing index in {pair}"
|
|
@@ -182,7 +182,7 @@ class TestL2BookResolution:
|
|
|
182
182
|
"""PURR/USDC (10000) should return valid L2 book."""
|
|
183
183
|
success, book = await live_adapter.get_spot_l2_book(10000)
|
|
184
184
|
|
|
185
|
-
assert success
|
|
185
|
+
assert success
|
|
186
186
|
assert "levels" in book
|
|
187
187
|
|
|
188
188
|
@pytest.mark.asyncio
|
|
@@ -190,7 +190,7 @@ class TestL2BookResolution:
|
|
|
190
190
|
"""HYPE/USDC (10107) should return valid L2 book."""
|
|
191
191
|
success, book = await live_adapter.get_spot_l2_book(10107)
|
|
192
192
|
|
|
193
|
-
assert success
|
|
193
|
+
assert success
|
|
194
194
|
assert "levels" in book
|
|
195
195
|
|
|
196
196
|
|
|
@@ -45,7 +45,7 @@ class TestLedgerAdapter:
|
|
|
45
45
|
offset=0,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
-
assert success
|
|
48
|
+
assert success
|
|
49
49
|
assert data == mock_response
|
|
50
50
|
mock_ledger_client.get_strategy_transactions.assert_called_once_with(
|
|
51
51
|
wallet_address="0x1234567890123456789012345678901234567890",
|
|
@@ -84,7 +84,7 @@ class TestLedgerAdapter:
|
|
|
84
84
|
wallet_address="0x1234567890123456789012345678901234567890"
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
-
assert success
|
|
87
|
+
assert success
|
|
88
88
|
assert data == mock_response
|
|
89
89
|
mock_ledger_client.get_strategy_net_deposit.assert_called_once_with(
|
|
90
90
|
wallet_address="0x1234567890123456789012345678901234567890"
|
|
@@ -110,7 +110,7 @@ class TestLedgerAdapter:
|
|
|
110
110
|
strategy_name="TestStrategy",
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
-
assert success
|
|
113
|
+
assert success
|
|
114
114
|
assert data == mock_response
|
|
115
115
|
mock_ledger_client.add_strategy_deposit.assert_called_once_with(
|
|
116
116
|
wallet_address="0x1234567890123456789012345678901234567890",
|
|
@@ -142,7 +142,7 @@ class TestLedgerAdapter:
|
|
|
142
142
|
strategy_name="TestStrategy",
|
|
143
143
|
)
|
|
144
144
|
|
|
145
|
-
assert success
|
|
145
|
+
assert success
|
|
146
146
|
assert data == mock_response
|
|
147
147
|
|
|
148
148
|
@pytest.mark.asyncio
|
|
@@ -177,7 +177,7 @@ class TestLedgerAdapter:
|
|
|
177
177
|
strategy_name="TestStrategy",
|
|
178
178
|
)
|
|
179
179
|
|
|
180
|
-
assert success
|
|
180
|
+
assert success
|
|
181
181
|
assert data == mock_response
|
|
182
182
|
|
|
183
183
|
@pytest.mark.asyncio
|
|
@@ -197,7 +197,7 @@ class TestLedgerAdapter:
|
|
|
197
197
|
wallet_address="0x1234567890123456789012345678901234567890", limit=10
|
|
198
198
|
)
|
|
199
199
|
|
|
200
|
-
assert success
|
|
200
|
+
assert success
|
|
201
201
|
assert data["total_transactions"] == 3
|
|
202
202
|
assert data["operations"]["deposits"] == 1
|
|
203
203
|
assert data["operations"]["withdrawals"] == 1
|