wayfinder-paths 0.1.11__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 +36 -39
- 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 +87 -75
- 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 +22 -23
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
- wayfinder_paths/adapters/pool_adapter/README.md +11 -27
- wayfinder_paths/adapters/pool_adapter/adapter.py +11 -37
- wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
- 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 +103 -62
- wayfinder_paths/core/clients/ClientManager.py +1 -68
- wayfinder_paths/core/clients/HyperlendClient.py +127 -66
- wayfinder_paths/core/clients/LedgerClient.py +1 -4
- wayfinder_paths/core/clients/PoolClient.py +126 -88
- wayfinder_paths/core/clients/TokenClient.py +92 -37
- wayfinder_paths/core/clients/WalletClient.py +28 -58
- wayfinder_paths/core/clients/WayfinderClient.py +33 -166
- wayfinder_paths/core/clients/__init__.py +0 -2
- wayfinder_paths/core/clients/protocols.py +35 -52
- wayfinder_paths/core/clients/sdk_example.py +37 -22
- wayfinder_paths/core/config.py +60 -224
- wayfinder_paths/core/engine/StrategyJob.py +7 -55
- wayfinder_paths/core/services/local_evm_txn.py +28 -10
- wayfinder_paths/core/services/local_token_txn.py +1 -1
- wayfinder_paths/core/strategies/Strategy.py +3 -5
- wayfinder_paths/core/strategies/descriptors.py +7 -0
- wayfinder_paths/core/utils/evm_helpers.py +7 -3
- wayfinder_paths/core/utils/wallets.py +12 -19
- wayfinder_paths/core/wallets/README.md +1 -1
- wayfinder_paths/run_strategy.py +8 -17
- wayfinder_paths/scripts/create_strategy.py +5 -5
- wayfinder_paths/scripts/make_wallets.py +5 -5
- wayfinder_paths/scripts/run_strategy.py +3 -3
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +206 -526
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +41 -25
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +10 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +3 -3
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +110 -78
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +44 -21
- wayfinder_paths/templates/adapter/README.md +1 -1
- wayfinder_paths/templates/strategy/README.md +3 -3
- wayfinder_paths/templates/strategy/test_strategy.py +3 -2
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +21 -59
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +64 -65
- wayfinder_paths/core/clients/AuthClient.py +0 -83
- wayfinder_paths/core/settings.py +0 -61
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
|
@@ -2,8 +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
|
-
|
|
6
|
-
LlamaReport,
|
|
5
|
+
LlamaMatchesResponse,
|
|
7
6
|
PoolClient,
|
|
8
7
|
PoolList,
|
|
9
8
|
)
|
|
@@ -31,58 +30,33 @@ class PoolAdapter(BaseAdapter):
|
|
|
31
30
|
self.pool_client = pool_client or PoolClient()
|
|
32
31
|
|
|
33
32
|
async def get_pools_by_ids(
|
|
34
|
-
self, pool_ids: list[str]
|
|
33
|
+
self, pool_ids: list[str]
|
|
35
34
|
) -> tuple[bool, PoolList | str]:
|
|
36
35
|
"""
|
|
37
36
|
Get pool information by pool IDs.
|
|
38
37
|
|
|
39
38
|
Args:
|
|
40
39
|
pool_ids: List of pool identifiers
|
|
41
|
-
merge_external: Whether to merge external data
|
|
42
40
|
|
|
43
41
|
Returns:
|
|
44
42
|
Tuple of (success, data) where data is pool information or error message
|
|
45
43
|
"""
|
|
46
44
|
try:
|
|
47
|
-
|
|
48
|
-
data = await self.pool_client.get_pools_by_ids(
|
|
49
|
-
pool_ids=pool_ids_str, merge_external=merge_external
|
|
50
|
-
)
|
|
45
|
+
data = await self.pool_client.get_pools_by_ids(pool_ids=pool_ids)
|
|
51
46
|
return (True, data)
|
|
52
47
|
except Exception as e:
|
|
53
48
|
self.logger.error(f"Error fetching pools by IDs: {e}")
|
|
54
49
|
return (False, str(e))
|
|
55
50
|
|
|
56
|
-
async def
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"""
|
|
63
|
-
try:
|
|
64
|
-
data = await self.pool_client.get_llama_matches()
|
|
65
|
-
return (True, data)
|
|
66
|
-
except Exception as e:
|
|
67
|
-
self.logger.error(f"Error fetching Llama matches: {e}")
|
|
68
|
-
return (False, str(e))
|
|
69
|
-
|
|
70
|
-
async def get_llama_reports(
|
|
71
|
-
self, identifiers: list[str]
|
|
72
|
-
) -> tuple[bool, dict[str, LlamaReport] | str]:
|
|
73
|
-
"""
|
|
74
|
-
Get Llama reports for specific identifiers.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
identifiers: List of identifiers (token IDs, addresses, pool IDs)
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
Tuple of (success, data) where data is Llama reports or error message
|
|
81
|
-
"""
|
|
51
|
+
async def get_pools(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
chain_id: int | None = None,
|
|
55
|
+
project: str | None = None,
|
|
56
|
+
) -> tuple[bool, LlamaMatchesResponse | str]:
|
|
82
57
|
try:
|
|
83
|
-
|
|
84
|
-
data = await self.pool_client.get_llama_reports(identifiers=identifiers_str)
|
|
58
|
+
data = await self.pool_client.get_pools(chain_id=chain_id, project=project)
|
|
85
59
|
return (True, data)
|
|
86
60
|
except Exception as e:
|
|
87
|
-
self.logger.error(f"Error fetching
|
|
61
|
+
self.logger.error(f"Error fetching pools: {e}")
|
|
88
62
|
return (False, str(e))
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
"get_pools_by_ids": {
|
|
3
3
|
"description": "Get pool information by pool IDs",
|
|
4
4
|
"input": {
|
|
5
|
-
"pool_ids": ["pool-123", "pool-456"]
|
|
6
|
-
"merge_external": true
|
|
5
|
+
"pool_ids": ["pool-123", "pool-456"]
|
|
7
6
|
},
|
|
8
7
|
"output": {
|
|
9
8
|
"success": true,
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
},
|
|
24
|
-
"
|
|
23
|
+
"get_pools": {
|
|
25
24
|
"description": "Get Llama protocol matches for pools",
|
|
26
25
|
"input": {},
|
|
27
26
|
"output": {
|
|
@@ -30,10 +29,10 @@
|
|
|
30
29
|
"matches": [
|
|
31
30
|
{
|
|
32
31
|
"id": "pool-123",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
32
|
+
"apy": 5.2,
|
|
33
|
+
"tvlUsd": 1000000,
|
|
34
|
+
"stablecoin": true,
|
|
35
|
+
"ilRisk": "no",
|
|
37
36
|
"network": "base"
|
|
38
37
|
}
|
|
39
38
|
]
|
|
@@ -38,33 +38,33 @@ class TestPoolAdapter:
|
|
|
38
38
|
mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_response)
|
|
39
39
|
|
|
40
40
|
success, data = await adapter.get_pools_by_ids(
|
|
41
|
-
pool_ids=["pool-123", "pool-456"]
|
|
41
|
+
pool_ids=["pool-123", "pool-456"]
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
assert success is True
|
|
45
45
|
assert data == mock_response
|
|
46
46
|
mock_pool_client.get_pools_by_ids.assert_called_once_with(
|
|
47
|
-
pool_ids="pool-123,pool-456"
|
|
47
|
+
pool_ids=["pool-123", "pool-456"]
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
@pytest.mark.asyncio
|
|
51
|
-
async def
|
|
51
|
+
async def test_get_pools_success(self, adapter, mock_pool_client):
|
|
52
52
|
"""Test successful Llama matches retrieval"""
|
|
53
53
|
# Mock response
|
|
54
54
|
mock_response = {
|
|
55
55
|
"matches": [
|
|
56
56
|
{
|
|
57
57
|
"id": "pool-123",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
58
|
+
"apy": 5.2,
|
|
59
|
+
"tvlUsd": 1000000,
|
|
60
|
+
"stablecoin": True,
|
|
61
61
|
"network": "base",
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
}
|
|
65
|
-
mock_pool_client.
|
|
65
|
+
mock_pool_client.get_pools = AsyncMock(return_value=mock_response)
|
|
66
66
|
|
|
67
|
-
success, data = await adapter.
|
|
67
|
+
success, data = await adapter.get_pools()
|
|
68
68
|
|
|
69
69
|
assert success is True
|
|
70
70
|
assert data == mock_response
|
|
@@ -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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BRAP (Bridge/Router/Adapter Protocol) Client
|
|
3
|
-
Provides access to quote operations via the
|
|
3
|
+
Provides access to quote operations via the blockchain quote endpoint.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -10,100 +10,141 @@ from typing import Any, NotRequired, Required, TypedDict
|
|
|
10
10
|
|
|
11
11
|
from loguru import logger
|
|
12
12
|
|
|
13
|
-
from wayfinder_paths.core.clients.AuthClient import AuthClient
|
|
14
13
|
from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
15
|
-
from wayfinder_paths.core.
|
|
14
|
+
from wayfinder_paths.core.config import get_api_base_url
|
|
16
15
|
|
|
17
16
|
|
|
18
|
-
class
|
|
17
|
+
class QuoteTx(TypedDict, total=False):
|
|
18
|
+
"""Quote transaction data structure"""
|
|
19
|
+
|
|
20
|
+
data: str
|
|
21
|
+
to: str
|
|
22
|
+
value: str
|
|
23
|
+
chainId: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class QuoteData(TypedDict):
|
|
27
|
+
"""Quote data structure"""
|
|
28
|
+
|
|
29
|
+
gas: Required[str]
|
|
30
|
+
amountOut: Required[str]
|
|
31
|
+
priceImpact: Required[int]
|
|
32
|
+
feeAmount: Required[list[str]]
|
|
33
|
+
minAmountOut: Required[str]
|
|
34
|
+
createdAt: Required[int]
|
|
35
|
+
tx: Required[QuoteTx]
|
|
36
|
+
route: Required[list[dict[str, Any]]]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FeeBreakdown(TypedDict):
|
|
40
|
+
"""Fee breakdown structure"""
|
|
41
|
+
|
|
42
|
+
name: Required[str]
|
|
43
|
+
amount: Required[int]
|
|
44
|
+
amount_usd: Required[float]
|
|
45
|
+
token: Required[str]
|
|
46
|
+
token_chain: Required[int]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class FeeEstimate(TypedDict):
|
|
50
|
+
"""Fee estimate structure"""
|
|
51
|
+
|
|
52
|
+
fee_total_usd: Required[float]
|
|
53
|
+
fee_breakdown: Required[list[FeeBreakdown]]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Calldata(TypedDict, total=False):
|
|
57
|
+
"""Calldata structure"""
|
|
58
|
+
|
|
59
|
+
data: str
|
|
60
|
+
to: str
|
|
61
|
+
value: str
|
|
62
|
+
chainId: int
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BRAPQuoteEntry(TypedDict):
|
|
66
|
+
"""BRAP quote entry structure"""
|
|
67
|
+
|
|
68
|
+
provider: Required[str]
|
|
69
|
+
quote: Required[QuoteData]
|
|
70
|
+
calldata: Required[Calldata]
|
|
71
|
+
output_amount: Required[int]
|
|
72
|
+
input_amount: Required[int]
|
|
73
|
+
gas_estimate: NotRequired[int | None]
|
|
74
|
+
error: NotRequired[str | None]
|
|
75
|
+
input_amount_usd: Required[float]
|
|
76
|
+
output_amount_usd: Required[float]
|
|
77
|
+
fee_estimate: Required[FeeEstimate]
|
|
78
|
+
wrap_transaction: NotRequired[dict[str, Any] | None]
|
|
79
|
+
unwrap_transaction: NotRequired[dict[str, Any] | None]
|
|
80
|
+
native_input: Required[bool]
|
|
81
|
+
native_output: Required[bool]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class BRAPQuoteResponse(TypedDict):
|
|
19
85
|
"""BRAP quote response structure"""
|
|
20
86
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from_chain_id: Required[int]
|
|
24
|
-
to_chain_id: Required[int]
|
|
25
|
-
from_address: Required[str]
|
|
26
|
-
to_address: Required[str]
|
|
27
|
-
amount1: Required[str]
|
|
28
|
-
amount2: NotRequired[str]
|
|
29
|
-
routes: NotRequired[list[dict[str, Any]]]
|
|
30
|
-
best_route: NotRequired[dict[str, Any]]
|
|
31
|
-
fees: NotRequired[dict[str, Any] | None]
|
|
32
|
-
slippage: NotRequired[float | None]
|
|
33
|
-
wayfinder_fee: NotRequired[float | None]
|
|
87
|
+
quotes: Required[list[BRAPQuoteEntry]]
|
|
88
|
+
best_quote: Required[BRAPQuoteEntry]
|
|
34
89
|
|
|
35
90
|
|
|
36
91
|
class BRAPClient(WayfinderClient):
|
|
37
92
|
"""Client for BRAP quote operations"""
|
|
38
93
|
|
|
39
|
-
def __init__(self
|
|
40
|
-
super().__init__(
|
|
41
|
-
self.api_base_url = f"{
|
|
42
|
-
self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
|
|
94
|
+
def __init__(self):
|
|
95
|
+
super().__init__()
|
|
96
|
+
self.api_base_url = f"{get_api_base_url()}/v1/blockchain/braps"
|
|
43
97
|
|
|
44
98
|
async def get_quote(
|
|
45
99
|
self,
|
|
46
100
|
*,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
slippage: float | None = None,
|
|
55
|
-
wayfinder_fee: float | None = None,
|
|
56
|
-
) -> BRAPQuote:
|
|
101
|
+
from_token: str,
|
|
102
|
+
to_token: str,
|
|
103
|
+
from_chain: int,
|
|
104
|
+
to_chain: int,
|
|
105
|
+
from_wallet: str,
|
|
106
|
+
from_amount: str,
|
|
107
|
+
) -> BRAPQuoteResponse: # type: ignore # noqa: E501
|
|
57
108
|
"""
|
|
58
109
|
Get a quote for a bridge/swap operation.
|
|
59
110
|
|
|
60
111
|
Args:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
amount1: Amount to swap (in smallest units)
|
|
68
|
-
slippage: Maximum slippage tolerance (optional)
|
|
69
|
-
wayfinder_fee: Wayfinder fee (optional)
|
|
112
|
+
from_token: Source token contract address
|
|
113
|
+
to_token: Destination token contract address
|
|
114
|
+
from_chain: Source chain ID
|
|
115
|
+
to_chain: Destination chain ID
|
|
116
|
+
from_wallet: Source wallet address
|
|
117
|
+
from_amount: Amount to swap (in smallest units)
|
|
70
118
|
|
|
71
119
|
Returns:
|
|
72
|
-
Quote
|
|
120
|
+
Quote response including quotes array and best_quote
|
|
73
121
|
"""
|
|
74
122
|
logger.info(
|
|
75
|
-
f"Getting BRAP quote: {
|
|
76
|
-
)
|
|
77
|
-
logger.debug(
|
|
78
|
-
f"Quote params: amount={amount1}, slippage={slippage}, wayfinder_fee={wayfinder_fee}"
|
|
123
|
+
f"Getting BRAP quote: {from_token} -> {to_token} (chain {from_chain} -> {to_chain})"
|
|
79
124
|
)
|
|
125
|
+
logger.debug(f"Quote params: amount={from_amount}")
|
|
80
126
|
start_time = time.time()
|
|
81
127
|
|
|
82
|
-
url = f"{self.api_base_url}/
|
|
128
|
+
url = f"{self.api_base_url}/quote/"
|
|
83
129
|
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"amount1": amount1,
|
|
130
|
+
params: dict[str, Any] = {
|
|
131
|
+
"from_token": from_token,
|
|
132
|
+
"to_token": to_token,
|
|
133
|
+
"from_chain": from_chain,
|
|
134
|
+
"to_chain": to_chain,
|
|
135
|
+
"from_wallet": from_wallet,
|
|
136
|
+
"from_amount": from_amount,
|
|
92
137
|
}
|
|
93
138
|
|
|
94
|
-
# Only add optional parameters if they're provided
|
|
95
|
-
if slippage is not None:
|
|
96
|
-
payload["slippage"] = slippage
|
|
97
|
-
if wayfinder_fee is not None:
|
|
98
|
-
payload["wayfinder_fee"] = wayfinder_fee
|
|
99
|
-
|
|
100
139
|
try:
|
|
101
|
-
response = await self.
|
|
140
|
+
response = await self._authed_request("GET", url, params=params, headers={})
|
|
102
141
|
response.raise_for_status()
|
|
103
142
|
data = response.json()
|
|
143
|
+
result = data.get("data", data)
|
|
144
|
+
|
|
104
145
|
elapsed = time.time() - start_time
|
|
105
146
|
logger.info(f"BRAP quote request completed successfully in {elapsed:.2f}s")
|
|
106
|
-
return
|
|
147
|
+
return result
|
|
107
148
|
except Exception as e:
|
|
108
149
|
elapsed = time.time() - start_time
|
|
109
150
|
logger.error(f"BRAP quote request failed after {elapsed:.2f}s: {e}")
|
|
@@ -5,7 +5,6 @@ Consolidated client management for all API interactions
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from wayfinder_paths.core.clients.AuthClient import AuthClient
|
|
9
8
|
from wayfinder_paths.core.clients.BRAPClient import BRAPClient
|
|
10
9
|
from wayfinder_paths.core.clients.HyperlendClient import HyperlendClient
|
|
11
10
|
from wayfinder_paths.core.clients.LedgerClient import LedgerClient
|
|
@@ -37,7 +36,6 @@ class ClientManager:
|
|
|
37
36
|
self,
|
|
38
37
|
clients: dict[str, Any] | None = None,
|
|
39
38
|
skip_auth: bool = False,
|
|
40
|
-
api_key: str | None = None,
|
|
41
39
|
):
|
|
42
40
|
"""
|
|
43
41
|
Initialize ClientManager.
|
|
@@ -45,14 +43,10 @@ class ClientManager:
|
|
|
45
43
|
Args:
|
|
46
44
|
clients: Optional dict of pre-instantiated clients to inject directly.
|
|
47
45
|
skip_auth: If True, skips authentication (for SDK usage).
|
|
48
|
-
api_key: Optional API key for service account authentication.
|
|
49
46
|
"""
|
|
50
47
|
self._injected_clients = clients or {}
|
|
51
48
|
self._skip_auth = skip_auth
|
|
52
|
-
self._api_key = api_key
|
|
53
|
-
self._access_token: str | None = None
|
|
54
49
|
|
|
55
|
-
self._auth_client: AuthClient | None = None
|
|
56
50
|
self._token_client: TokenClientProtocol | None = None
|
|
57
51
|
self._wallet_client: WalletClientProtocol | None = None
|
|
58
52
|
self._ledger_client: LedgerClientProtocol | None = None
|
|
@@ -79,25 +73,10 @@ class ClientManager:
|
|
|
79
73
|
"""
|
|
80
74
|
client = getattr(self, client_attr)
|
|
81
75
|
if not client:
|
|
82
|
-
client = self._injected_clients.get(injected_key) or client_class(
|
|
83
|
-
api_key=self._api_key
|
|
84
|
-
)
|
|
76
|
+
client = self._injected_clients.get(injected_key) or client_class()
|
|
85
77
|
setattr(self, client_attr, client)
|
|
86
|
-
if self._access_token and hasattr(client, "set_bearer_token"):
|
|
87
|
-
client.set_bearer_token(self._access_token)
|
|
88
78
|
return client
|
|
89
79
|
|
|
90
|
-
@property
|
|
91
|
-
def auth(self) -> AuthClient | None:
|
|
92
|
-
"""Get or create auth client. Returns None if skip_auth=True."""
|
|
93
|
-
if self._skip_auth:
|
|
94
|
-
return None
|
|
95
|
-
if not self._auth_client:
|
|
96
|
-
self._auth_client = AuthClient(api_key=self._api_key)
|
|
97
|
-
if self._access_token:
|
|
98
|
-
self._auth_client.set_bearer_token(self._access_token)
|
|
99
|
-
return self._auth_client
|
|
100
|
-
|
|
101
80
|
@property
|
|
102
81
|
def token(self) -> TokenClientProtocol:
|
|
103
82
|
"""Get or create token client"""
|
|
@@ -130,55 +109,9 @@ class ClientManager:
|
|
|
130
109
|
"""Get or create BRAP client"""
|
|
131
110
|
return self._get_or_create_client("_brap_client", "brap", BRAPClient)
|
|
132
111
|
|
|
133
|
-
async def authenticate(
|
|
134
|
-
self,
|
|
135
|
-
username: str | None = None,
|
|
136
|
-
password: str | None = None,
|
|
137
|
-
*,
|
|
138
|
-
refresh_token: str | None = None,
|
|
139
|
-
) -> dict[str, Any]:
|
|
140
|
-
"""Authenticate with the API. Raises ValueError if skip_auth=True."""
|
|
141
|
-
if self._skip_auth:
|
|
142
|
-
raise ValueError(
|
|
143
|
-
"Authentication is disabled in SDK mode. SDK users handle their own authentication."
|
|
144
|
-
)
|
|
145
|
-
auth_client = self.auth
|
|
146
|
-
if auth_client is None:
|
|
147
|
-
raise ValueError("Auth client is not available")
|
|
148
|
-
data = await auth_client.authenticate(
|
|
149
|
-
username, password, refresh_token=refresh_token
|
|
150
|
-
)
|
|
151
|
-
access = data.get("access") or data.get("access_token")
|
|
152
|
-
if access:
|
|
153
|
-
self.set_access_token(access)
|
|
154
|
-
return data
|
|
155
|
-
|
|
156
|
-
def set_access_token(self, access_token: str) -> None:
|
|
157
|
-
"""Set and propagate access token to all initialized clients."""
|
|
158
|
-
self._access_token = access_token
|
|
159
|
-
if self._auth_client:
|
|
160
|
-
self._auth_client.set_bearer_token(access_token)
|
|
161
|
-
if self._token_client and hasattr(self._token_client, "set_bearer_token"):
|
|
162
|
-
self._token_client.set_bearer_token(access_token)
|
|
163
|
-
if self._transaction_client and hasattr(
|
|
164
|
-
self._transaction_client, "set_bearer_token"
|
|
165
|
-
):
|
|
166
|
-
self._transaction_client.set_bearer_token(access_token)
|
|
167
|
-
if self._ledger_client and hasattr(self._ledger_client, "set_bearer_token"):
|
|
168
|
-
self._ledger_client.set_bearer_token(access_token)
|
|
169
|
-
if self._pool_client and hasattr(self._pool_client, "set_bearer_token"):
|
|
170
|
-
self._pool_client.set_bearer_token(access_token)
|
|
171
|
-
if self._hyperlend_client and hasattr(
|
|
172
|
-
self._hyperlend_client, "set_bearer_token"
|
|
173
|
-
):
|
|
174
|
-
self._hyperlend_client.set_bearer_token(access_token)
|
|
175
|
-
if self._wallet_client and hasattr(self._wallet_client, "set_bearer_token"):
|
|
176
|
-
self._wallet_client.set_bearer_token(access_token)
|
|
177
|
-
|
|
178
112
|
def get_all_clients(self) -> dict[str, Any]:
|
|
179
113
|
"""Get all initialized clients for direct access"""
|
|
180
114
|
return {
|
|
181
|
-
"auth": self._auth_client,
|
|
182
115
|
"token": self._token_client,
|
|
183
116
|
"transaction": self._transaction_client,
|
|
184
117
|
"ledger": self._ledger_client,
|