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.

Files changed (47) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +33 -32
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
  4. wayfinder_paths/adapters/brap_adapter/README.md +11 -16
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +78 -63
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +16 -14
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
  10. wayfinder_paths/adapters/pool_adapter/README.md +9 -10
  11. wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
  12. wayfinder_paths/adapters/token_adapter/README.md +2 -14
  13. wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
  14. wayfinder_paths/adapters/token_adapter/examples.json +4 -8
  15. wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
  16. wayfinder_paths/core/clients/BRAPClient.py +102 -61
  17. wayfinder_paths/core/clients/ClientManager.py +1 -68
  18. wayfinder_paths/core/clients/HyperlendClient.py +125 -64
  19. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  20. wayfinder_paths/core/clients/PoolClient.py +122 -48
  21. wayfinder_paths/core/clients/TokenClient.py +91 -36
  22. wayfinder_paths/core/clients/WalletClient.py +26 -56
  23. wayfinder_paths/core/clients/WayfinderClient.py +28 -160
  24. wayfinder_paths/core/clients/__init__.py +0 -2
  25. wayfinder_paths/core/clients/protocols.py +35 -46
  26. wayfinder_paths/core/clients/sdk_example.py +37 -22
  27. wayfinder_paths/core/engine/StrategyJob.py +7 -55
  28. wayfinder_paths/core/services/local_evm_txn.py +6 -6
  29. wayfinder_paths/core/services/local_token_txn.py +1 -1
  30. wayfinder_paths/core/strategies/Strategy.py +0 -2
  31. wayfinder_paths/core/utils/evm_helpers.py +2 -2
  32. wayfinder_paths/run_strategy.py +8 -19
  33. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +10 -11
  34. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +40 -25
  35. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  36. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +3 -3
  37. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
  38. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
  39. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +88 -56
  40. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
  41. wayfinder_paths/templates/strategy/README.md +3 -3
  42. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  43. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +14 -49
  44. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +46 -47
  45. wayfinder_paths/core/clients/AuthClient.py +0 -83
  46. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
  47. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
@@ -1,76 +1,150 @@
1
- """
2
- Pool Client
3
- Provides read-only access to pool metadata and analytics via public endpoints.
4
- """
1
+ """Read-only pool metadata and analytics via public endpoints."""
5
2
 
6
3
  from __future__ import annotations
7
4
 
8
- from typing import Any, NotRequired, Required, TypedDict
5
+ from typing import Any, Required, TypedDict
9
6
 
10
- from wayfinder_paths.core.clients.AuthClient import AuthClient
11
7
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
8
  from wayfinder_paths.core.config import get_api_base_url
13
9
 
14
10
 
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
- apy: NotRequired[float | None]
27
- tvlUsd: NotRequired[float | None]
28
- stablecoin: NotRequired[bool | None]
29
- ilRisk: NotRequired[str | None]
30
- network: NotRequired[str | None]
11
+ class PoolPredictions(TypedDict, total=False):
12
+ predictedClass: str | None
13
+ predictedProbability: int | None
14
+ binnedConfidence: int | None
15
+
16
+
17
+ class PoolData(TypedDict, total=False):
18
+ pool: str
19
+ timestamp: str
20
+ project: str
21
+ chain: str
22
+ symbol: str
23
+ poolMeta: str | None
24
+ underlyingTokens: list[str]
25
+ rewardTokens: list[str] | None
26
+ tvlUsd: float
27
+ apy: float
28
+ apyBase: float
29
+ apyReward: float | None
30
+ il7d: float | None
31
+ apyBase7d: float | None
32
+ volumeUsd1d: float | None
33
+ volumeUsd7d: float | None
34
+ apyBaseInception: float | None
35
+ url: str
36
+ apyPct1D: float
37
+ apyPct7D: float
38
+ apyPct30D: float
39
+ apyMean30d: float
40
+ stablecoin: bool
41
+ ilRisk: str
42
+ exposure: str
43
+ count: int
44
+ apyMeanExpanding: float
45
+ apyStdExpanding: float
46
+ mu: float
47
+ sigma: float
48
+ outlier: bool
49
+ project_factorized: int
50
+ chain_factorized: int
51
+ predictions: PoolPredictions
52
+ pool_old: str
53
+ pool_old_addr: str
54
+ pool_old_chain: str
55
+ apy_pct: float
56
+ kind: str
57
+ source: str
58
+ underlying_apy_pct: float | None
59
+ combined_apy_pct: float
60
+ id: str
61
+ address: str
62
+ network: str
63
+ chain_code: str
31
64
 
32
65
 
33
66
  class PoolList(TypedDict):
34
- """Pool list response structure"""
35
-
36
67
  pools: Required[list[PoolData]]
37
- total: NotRequired[int | None]
38
68
 
39
69
 
40
- class LlamaMatch(TypedDict):
41
- """Llama match data structure"""
42
-
43
- id: Required[str]
44
- apy: Required[float]
45
- tvlUsd: Required[float]
46
- stablecoin: Required[bool]
47
- ilRisk: Required[str]
48
- network: Required[str]
70
+ class LlamaMatchesResponse(TypedDict):
71
+ matches: Required[list[PoolData]]
72
+
73
+
74
+ def _normalize_pool(raw: dict[str, Any]) -> dict[str, Any]:
75
+ out = dict(raw)
76
+ pool_id_val = out.get("pool")
77
+ if pool_id_val is not None:
78
+ if "id" not in out:
79
+ out["id"] = pool_id_val
80
+ if "token_id" not in out:
81
+ out["token_id"] = pool_id_val
82
+ if "pool_id" not in out:
83
+ out["pool_id"] = pool_id_val
84
+ if "address" not in out and "pool_old_addr" in out:
85
+ out["address"] = out["pool_old_addr"]
86
+ chain_val = out.get("pool_old_chain") or out.get("chain")
87
+ if chain_val is not None:
88
+ chain_lower = (
89
+ chain_val.lower() if isinstance(chain_val, str) else str(chain_val)
90
+ )
91
+ if "network" not in out:
92
+ out["network"] = chain_lower
93
+ if "chain_code" not in out:
94
+ out["chain_code"] = chain_lower
95
+ return out
49
96
 
50
97
 
51
98
  class PoolClient(WayfinderClient):
52
- """Client for pool-related read operations"""
99
+ def __init__(self):
100
+ super().__init__()
101
+ self.api_base_url = get_api_base_url()
53
102
 
54
- def __init__(self, api_key: str | None = None):
55
- super().__init__(api_key=api_key)
56
- self.api_base_url = f"{get_api_base_url()}"
57
- self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
103
+ def _pools_url(self) -> str:
104
+ return f"{self.api_base_url}/v1/blockchain/pools/"
58
105
 
59
- async def get_pools(self) -> dict[str, LlamaMatch]:
60
- url = f"{self.api_base_url}/pools/"
61
- response = await self._request("GET", url, headers={})
106
+ async def get_pools(
107
+ self,
108
+ *,
109
+ chain_id: int | None = None,
110
+ project: str | None = None,
111
+ ) -> LlamaMatchesResponse:
112
+ url = self._pools_url()
113
+ params: dict[str, Any] = {}
114
+ if chain_id is not None:
115
+ params["chain_id"] = chain_id
116
+ if project is not None:
117
+ params["project"] = project
118
+ response = await self._request("GET", url, params=params, headers={})
62
119
  response.raise_for_status()
63
120
  data = response.json()
64
- return data.get("data", data)
121
+ if isinstance(data, list):
122
+ data = [_normalize_pool(p) for p in data]
123
+ return {"matches": data}
124
+ inner = data.get("data", data)
125
+ if isinstance(inner, list):
126
+ return {"matches": [_normalize_pool(p) for p in inner]}
127
+ return {"matches": inner.get("matches", [])}
65
128
 
66
129
  async def get_pools_by_ids(
67
130
  self,
68
131
  *,
69
- pool_ids: str,
132
+ pool_ids: list[str] | str,
70
133
  ) -> PoolList:
71
- url = f"{self.api_base_url}/pools/"
72
- params: dict[str, Any] = {"pool_ids": pool_ids}
73
- response = await self._request("GET", url, params=params, headers={})
134
+ url = self._pools_url()
135
+ ids = (
136
+ pool_ids
137
+ if isinstance(pool_ids, list)
138
+ else [s.strip() for s in str(pool_ids).split(",") if s.strip()]
139
+ )
140
+ body: dict[str, Any] = {"pool_ids": ids}
141
+ response = await self._request("POST", url, json=body, headers={})
74
142
  response.raise_for_status()
75
143
  data = response.json()
76
- return data.get("data", data)
144
+ if isinstance(data, list):
145
+ data = [_normalize_pool(p) for p in data]
146
+ return {"pools": data}
147
+ inner = data.get("data", data)
148
+ if isinstance(inner, list):
149
+ return {"pools": [_normalize_pool(p) for p in inner]}
150
+ return {"pools": inner.get("pools", [])}
@@ -7,83 +7,138 @@ from __future__ import annotations
7
7
 
8
8
  from typing import NotRequired, Required, TypedDict
9
9
 
10
- from wayfinder_paths.core.clients.AuthClient import AuthClient
11
10
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
11
  from wayfinder_paths.core.config import get_api_base_url
13
12
 
14
13
 
14
+ class TokenLinks(TypedDict):
15
+ """Token links structure"""
16
+
17
+ github: NotRequired[list[str]]
18
+ reddit: NotRequired[str]
19
+ discord: NotRequired[str]
20
+ twitter: NotRequired[str]
21
+ homepage: NotRequired[list[str]]
22
+ telegram: NotRequired[str]
23
+
24
+
25
+ class ChainAddress(TypedDict):
26
+ """Chain address structure"""
27
+
28
+ address: Required[str]
29
+ token_id: Required[str]
30
+ is_contract: NotRequired[bool]
31
+ chain_id: NotRequired[int]
32
+
33
+
34
+ class ChainInfo(TypedDict):
35
+ """Chain information structure"""
36
+
37
+ id: Required[int]
38
+ name: Required[str]
39
+ code: Required[str]
40
+
41
+
42
+ class TokenMetadata(TypedDict):
43
+ """Token metadata structure"""
44
+
45
+ query_processed: NotRequired[str]
46
+ query_type: NotRequired[str]
47
+ has_addresses: NotRequired[bool]
48
+ address_count: NotRequired[int]
49
+ has_price_data: NotRequired[bool]
50
+
51
+
15
52
  class TokenDetails(TypedDict):
16
53
  """Token details response structure"""
17
54
 
18
- id: Required[str]
19
- address: Required[str]
20
- symbol: Required[str]
55
+ asset_id: NotRequired[str]
56
+ token_ids: NotRequired[list[str]]
21
57
  name: Required[str]
58
+ symbol: Required[str]
22
59
  decimals: Required[int]
23
- chain_id: Required[int]
24
- chain_code: Required[str]
25
- price_usd: NotRequired[float]
26
- price: NotRequired[float]
60
+ description: NotRequired[str]
61
+ links: NotRequired[TokenLinks]
62
+ categories: NotRequired[list[str]]
63
+ current_price: NotRequired[float]
64
+ market_cap: NotRequired[float]
65
+ total_volume_usd_24h: NotRequired[float]
66
+ price_change_24h: NotRequired[float]
67
+ price_change_7d: NotRequired[float]
68
+ price_change_30d: NotRequired[float]
69
+ price_change_1y: NotRequired[float]
70
+ addresses: NotRequired[dict[str, str]]
71
+ chain_addresses: NotRequired[dict[str, ChainAddress]]
72
+ chain_ids: NotRequired[dict[str, int]]
73
+ id: NotRequired[int]
74
+ token_id: Required[str]
75
+ address: Required[str]
76
+ chain: NotRequired[ChainInfo]
77
+ query: NotRequired[str]
78
+ query_type: NotRequired[str]
79
+ metadata: NotRequired[TokenMetadata]
27
80
  image_url: NotRequired[str | None]
28
- coingecko_id: NotRequired[str | None]
29
81
 
30
82
 
31
83
  class GasToken(TypedDict):
32
84
  """Gas token response structure"""
33
85
 
34
86
  id: Required[str]
35
- address: Required[str]
36
- symbol: Required[str]
87
+ coingecko_id: NotRequired[str]
88
+ token_id: Required[str]
37
89
  name: Required[str]
90
+ symbol: Required[str]
91
+ address: Required[str]
38
92
  decimals: Required[int]
39
- chain_id: Required[int]
40
- chain_code: Required[str]
41
- price_usd: NotRequired[float]
93
+ chain: NotRequired[ChainInfo]
42
94
 
43
95
 
44
96
  class TokenClient(WayfinderClient):
45
97
  """Adapter for token-related operations"""
46
98
 
47
- def __init__(self, api_key: str | None = None):
48
- super().__init__(api_key=api_key)
49
- self.api_base_url = f"{self.api_base_url}/tokens"
50
- self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
51
-
52
- # ============== Public (No-Auth) Endpoints ==============
99
+ def __init__(self):
100
+ super().__init__()
101
+ self.api_base_url = f"{get_api_base_url()}/v1/blockchain/tokens"
53
102
 
54
103
  async def get_token_details(
55
- self, token_id: str, force_refresh: bool = False
104
+ self, query: str, market_data: bool = False, chain_id: int | None = None
56
105
  ) -> TokenDetails:
57
106
  """
58
- Get token data including price from the token-details endpoint
107
+ Get token data including price from the token-details endpoint.
59
108
 
60
109
  Args:
61
- token_id: Token identifier or address
110
+ query: Token identifier, address, or symbol to query
111
+ market_data: Whether to include market data (default: True)
112
+ chain_id: Optional chain ID
62
113
 
63
114
  Returns:
64
115
  Full token data including price information
65
116
  """
66
- url = f"{get_api_base_url()}/public/tokens/detail/"
117
+ url = f"{self.api_base_url}/detail/"
67
118
  params = {
68
- "query": token_id,
69
- "get_chart": "false",
70
- "force_refresh": str(force_refresh),
119
+ "query": query,
120
+ "market_data": market_data,
71
121
  }
72
- # Public endpoint: do not pass auth headers
73
- response = await self._request("GET", url, params=params, headers={})
122
+ if chain_id is not None:
123
+ params["chain_id"] = chain_id
124
+ response = await self._authed_request("GET", url, params=params)
74
125
  response.raise_for_status()
75
126
  data = response.json()
76
127
  return data.get("data", data)
77
128
 
78
- async def get_gas_token(self, chain_code: str) -> GasToken:
129
+ async def get_gas_token(self, query: str) -> GasToken:
79
130
  """
80
- Fetch the native gas token for a given chain code via public endpoint.
81
- Example: GET /api/v1/public/tokens/gas/?chain_code=base
131
+ Fetch the native gas token for a given chain code or query.
132
+
133
+ Args:
134
+ query: Chain code or query string
135
+
136
+ Returns:
137
+ Gas token information including chain details
82
138
  """
83
- url = f"{get_api_base_url()}/public/tokens/gas/"
84
- params = {"chain_code": chain_code}
85
- # Public endpoint: do not pass auth headers
86
- response = await self._request("GET", url, params=params, headers={})
139
+ url = f"{self.api_base_url}/gas/"
140
+ params = {"query": query}
141
+ response = await self._authed_request("GET", url, params=params)
87
142
  response.raise_for_status()
88
143
  data = response.json()
89
144
  return data.get("data", data)
@@ -7,80 +7,50 @@ from __future__ import annotations
7
7
 
8
8
  from typing import NotRequired, Required, TypedDict
9
9
 
10
- from wayfinder_paths.core.clients.AuthClient import AuthClient
11
10
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
11
  from wayfinder_paths.core.config import get_api_base_url
13
12
 
14
13
 
15
- class TokenBalance(TypedDict):
16
- """Token balance response structure"""
14
+ class AddressBalance(TypedDict):
15
+ """Balance response structure for address/query lookups."""
17
16
 
18
- token_id: Required[str]
19
- wallet_address: Required[str]
20
- balance: Required[str]
21
- balance_human: NotRequired[float | None]
22
- usd_value: NotRequired[float | None]
23
-
24
-
25
- class PoolBalance(TypedDict):
26
- """Pool balance response structure"""
27
-
28
- pool_address: Required[str]
29
- chain_id: Required[int]
30
- user_address: Required[str]
31
- balance: Required[str]
17
+ balance: Required[int]
32
18
  balance_human: NotRequired[float | None]
33
19
  usd_value: NotRequired[float | None]
20
+ address: NotRequired[str]
21
+ token_id: NotRequired[str | None]
22
+ wallet_address: NotRequired[str]
23
+ chain_id: NotRequired[int]
34
24
 
35
25
 
36
26
  class WalletClient(WayfinderClient):
37
- def __init__(self, api_key: str | None = None):
38
- super().__init__(api_key=api_key)
27
+ def __init__(self):
28
+ super().__init__()
39
29
  self.api_base_url = get_api_base_url()
40
- self._auth_client = AuthClient(api_key=api_key)
41
30
 
42
- async def get_token_balance_for_wallet(
31
+ async def get_token_balance_for_address(
43
32
  self,
44
33
  *,
45
- token_id: str,
46
34
  wallet_address: str,
47
- human_readable: bool = True,
48
- ) -> TokenBalance:
35
+ query: str,
36
+ chain_id: int | None = None,
37
+ ) -> AddressBalance:
49
38
  """
50
- Fetch a single token balance for an explicit wallet address.
39
+ Fetch a balance for a wallet address + chain + query.
51
40
 
52
- Mirrors POST /api/v1/public/balances/token/
41
+ Args:
42
+ wallet_address: Wallet address
43
+ query: Token address, pool address, or equivalent identifier
44
+ chain_id: Chain ID (required)
53
45
  """
54
- url = f"{self.api_base_url}/public/balances/token/"
55
- payload = {
56
- "token_id": token_id,
57
- "wallet_address": wallet_address,
58
- "human_readable": human_readable,
59
- }
60
- response = await self._authed_request("POST", url, json=payload)
61
- data = response.json()
62
- return data.get("data", data)
46
+ if chain_id is None:
47
+ raise ValueError("chain_id is required")
63
48
 
64
- async def get_pool_balance_for_wallet(
65
- self,
66
- *,
67
- pool_address: str,
68
- chain_id: int,
69
- user_address: str,
70
- human_readable: bool = True,
71
- ) -> PoolBalance:
72
- """
73
- Fetch a wallet's LP/share balance for a given pool address and chain.
74
-
75
- Mirrors POST /api/v1/public/balances/pool/
76
- """
77
- url = f"{self.api_base_url}/public/balances/pool/"
78
- payload = {
79
- "pool_address": pool_address,
49
+ url = f"{self.api_base_url}/v1/blockchain/balances/address/"
50
+ params = {
51
+ "wallet_address": wallet_address,
80
52
  "chain_id": chain_id,
81
- "user_address": user_address,
82
- "human_readable": human_readable,
53
+ "query": query,
83
54
  }
84
- response = await self._authed_request("POST", url, json=payload)
85
- data = response.json()
86
- return data.get("data", data)
55
+ response = await self._authed_request("GET", url, params=params)
56
+ return response.json()