wayfinder-paths 0.1.7__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 (149) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +399 -0
  2. wayfinder_paths/__init__.py +22 -0
  3. wayfinder_paths/abis/generic/erc20.json +383 -0
  4. wayfinder_paths/adapters/__init__.py +0 -0
  5. wayfinder_paths/adapters/balance_adapter/README.md +94 -0
  6. wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
  7. wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
  8. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  9. wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
  10. wayfinder_paths/adapters/brap_adapter/README.md +249 -0
  11. wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
  13. wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
  15. wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
  19. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
  20. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  21. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  24. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  28. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  29. wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
  30. wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
  31. wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
  32. wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
  33. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
  34. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
  35. wayfinder_paths/adapters/pool_adapter/README.md +206 -0
  36. wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
  37. wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
  38. wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
  39. wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
  40. wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
  41. wayfinder_paths/adapters/token_adapter/README.md +101 -0
  42. wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
  43. wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
  46. wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
  47. wayfinder_paths/config.example.json +22 -0
  48. wayfinder_paths/conftest.py +31 -0
  49. wayfinder_paths/core/__init__.py +18 -0
  50. wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
  51. wayfinder_paths/core/adapters/__init__.py +5 -0
  52. wayfinder_paths/core/adapters/base.py +5 -0
  53. wayfinder_paths/core/adapters/models.py +46 -0
  54. wayfinder_paths/core/analytics/__init__.py +11 -0
  55. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  56. wayfinder_paths/core/analytics/stats.py +48 -0
  57. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  58. wayfinder_paths/core/clients/AuthClient.py +83 -0
  59. wayfinder_paths/core/clients/BRAPClient.py +109 -0
  60. wayfinder_paths/core/clients/ClientManager.py +210 -0
  61. wayfinder_paths/core/clients/HyperlendClient.py +192 -0
  62. wayfinder_paths/core/clients/LedgerClient.py +443 -0
  63. wayfinder_paths/core/clients/PoolClient.py +128 -0
  64. wayfinder_paths/core/clients/SimulationClient.py +192 -0
  65. wayfinder_paths/core/clients/TokenClient.py +89 -0
  66. wayfinder_paths/core/clients/TransactionClient.py +63 -0
  67. wayfinder_paths/core/clients/WalletClient.py +94 -0
  68. wayfinder_paths/core/clients/WayfinderClient.py +269 -0
  69. wayfinder_paths/core/clients/__init__.py +48 -0
  70. wayfinder_paths/core/clients/protocols.py +392 -0
  71. wayfinder_paths/core/clients/sdk_example.py +110 -0
  72. wayfinder_paths/core/config.py +458 -0
  73. wayfinder_paths/core/constants/__init__.py +26 -0
  74. wayfinder_paths/core/constants/base.py +42 -0
  75. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  76. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  77. wayfinder_paths/core/engine/StrategyJob.py +188 -0
  78. wayfinder_paths/core/engine/__init__.py +5 -0
  79. wayfinder_paths/core/engine/manifest.py +97 -0
  80. wayfinder_paths/core/services/__init__.py +0 -0
  81. wayfinder_paths/core/services/base.py +179 -0
  82. wayfinder_paths/core/services/local_evm_txn.py +430 -0
  83. wayfinder_paths/core/services/local_token_txn.py +231 -0
  84. wayfinder_paths/core/services/web3_service.py +45 -0
  85. wayfinder_paths/core/settings.py +61 -0
  86. wayfinder_paths/core/strategies/Strategy.py +280 -0
  87. wayfinder_paths/core/strategies/__init__.py +5 -0
  88. wayfinder_paths/core/strategies/base.py +7 -0
  89. wayfinder_paths/core/strategies/descriptors.py +81 -0
  90. wayfinder_paths/core/utils/__init__.py +1 -0
  91. wayfinder_paths/core/utils/evm_helpers.py +206 -0
  92. wayfinder_paths/core/utils/wallets.py +77 -0
  93. wayfinder_paths/core/wallets/README.md +91 -0
  94. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  95. wayfinder_paths/core/wallets/__init__.py +7 -0
  96. wayfinder_paths/policies/enso.py +17 -0
  97. wayfinder_paths/policies/erc20.py +34 -0
  98. wayfinder_paths/policies/evm.py +21 -0
  99. wayfinder_paths/policies/hyper_evm.py +19 -0
  100. wayfinder_paths/policies/hyperlend.py +12 -0
  101. wayfinder_paths/policies/hyperliquid.py +30 -0
  102. wayfinder_paths/policies/moonwell.py +54 -0
  103. wayfinder_paths/policies/prjx.py +30 -0
  104. wayfinder_paths/policies/util.py +27 -0
  105. wayfinder_paths/run_strategy.py +411 -0
  106. wayfinder_paths/scripts/__init__.py +0 -0
  107. wayfinder_paths/scripts/create_strategy.py +181 -0
  108. wayfinder_paths/scripts/make_wallets.py +169 -0
  109. wayfinder_paths/scripts/run_strategy.py +124 -0
  110. wayfinder_paths/scripts/validate_manifests.py +213 -0
  111. wayfinder_paths/strategies/__init__.py +0 -0
  112. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  113. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  114. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  115. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  116. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  117. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  118. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  119. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  120. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  121. wayfinder_paths/strategies/config.py +85 -0
  122. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
  123. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
  124. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  125. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
  126. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
  127. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
  128. wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
  129. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  130. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
  131. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
  132. wayfinder_paths/templates/adapter/README.md +105 -0
  133. wayfinder_paths/templates/adapter/adapter.py +26 -0
  134. wayfinder_paths/templates/adapter/examples.json +8 -0
  135. wayfinder_paths/templates/adapter/manifest.yaml +6 -0
  136. wayfinder_paths/templates/adapter/test_adapter.py +49 -0
  137. wayfinder_paths/templates/strategy/README.md +153 -0
  138. wayfinder_paths/templates/strategy/examples.json +11 -0
  139. wayfinder_paths/templates/strategy/manifest.yaml +8 -0
  140. wayfinder_paths/templates/strategy/strategy.py +57 -0
  141. wayfinder_paths/templates/strategy/test_strategy.py +197 -0
  142. wayfinder_paths/tests/__init__.py +0 -0
  143. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  144. wayfinder_paths/tests/test_test_coverage.py +212 -0
  145. wayfinder_paths/tests/test_utils.py +64 -0
  146. wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
  147. wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
  148. wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
  149. wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,143 @@
1
+ {
2
+ "get_pools_by_ids": {
3
+ "description": "Get pool information by pool IDs",
4
+ "input": {
5
+ "pool_ids": ["pool-123", "pool-456"],
6
+ "merge_external": true
7
+ },
8
+ "output": {
9
+ "success": true,
10
+ "data": {
11
+ "pools": [
12
+ {
13
+ "id": "pool-123",
14
+ "name": "USDC/USDT Pool",
15
+ "symbol": "USDC-USDT",
16
+ "apy": 0.05,
17
+ "tvl": 1000000,
18
+ "chain": "base"
19
+ }
20
+ ]
21
+ }
22
+ }
23
+ },
24
+ "get_all_pools": {
25
+ "description": "Get all available pools",
26
+ "input": {
27
+ "merge_external": false
28
+ },
29
+ "output": {
30
+ "success": true,
31
+ "data": {
32
+ "pools": [
33
+ {
34
+ "id": "pool-123",
35
+ "name": "USDC/USDT Pool",
36
+ "symbol": "USDC-USDT",
37
+ "apy": 0.05,
38
+ "tvl": 1000000
39
+ }
40
+ ],
41
+ "total": 1
42
+ }
43
+ }
44
+ },
45
+ "get_llama_matches": {
46
+ "description": "Get Llama protocol matches for pools",
47
+ "input": {},
48
+ "output": {
49
+ "success": true,
50
+ "data": {
51
+ "matches": [
52
+ {
53
+ "id": "pool-123",
54
+ "llama_apy_pct": 5.2,
55
+ "llama_tvl_usd": 1000000,
56
+ "llama_stablecoin": true,
57
+ "llama_il_risk": "no",
58
+ "network": "base"
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ },
64
+ "find_high_yield_pools": {
65
+ "description": "Find high-yield pools based on criteria",
66
+ "input": {
67
+ "min_apy": 0.03,
68
+ "min_tvl": 500000,
69
+ "stablecoin_only": true,
70
+ "network_codes": ["base", "ethereum"]
71
+ },
72
+ "output": {
73
+ "success": true,
74
+ "data": {
75
+ "pools": [
76
+ {
77
+ "id": "pool-123",
78
+ "llama_apy_pct": 5.2,
79
+ "llama_tvl_usd": 1000000,
80
+ "llama_stablecoin": true,
81
+ "network": "base"
82
+ }
83
+ ],
84
+ "total_found": 1,
85
+ "filters_applied": {
86
+ "min_apy": 0.03,
87
+ "min_tvl": 500000,
88
+ "stablecoin_only": true,
89
+ "network_codes": ["base", "ethereum"]
90
+ }
91
+ }
92
+ }
93
+ },
94
+ "get_pool_analytics": {
95
+ "description": "Get comprehensive analytics for specific pools",
96
+ "input": {
97
+ "pool_ids": ["pool-123"]
98
+ },
99
+ "output": {
100
+ "success": true,
101
+ "data": {
102
+ "analytics": [
103
+ {
104
+ "pool": {
105
+ "id": "pool-123",
106
+ "name": "USDC/USDT Pool",
107
+ "symbol": "USDC-USDT"
108
+ },
109
+ "llama_data": {
110
+ "llama_apy_pct": 5.2,
111
+ "llama_tvl_usd": 1000000
112
+ },
113
+ "combined_apy": 0.052,
114
+ "tvl_usd": 1000000
115
+ }
116
+ ],
117
+ "total_pools": 1
118
+ }
119
+ }
120
+ },
121
+ "search_pools": {
122
+ "description": "Search pools by name, symbol, or other criteria",
123
+ "input": {
124
+ "query": "USDC",
125
+ "limit": 5
126
+ },
127
+ "output": {
128
+ "success": true,
129
+ "data": {
130
+ "pools": [
131
+ {
132
+ "id": "pool-123",
133
+ "name": "USDC/USDT Pool",
134
+ "symbol": "USDC-USDT",
135
+ "description": "Stablecoin pool on Base"
136
+ }
137
+ ],
138
+ "total_found": 1,
139
+ "query": "USDC"
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,10 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.pool_adapter.adapter.PoolAdapter"
3
+ capabilities:
4
+ - "pool.read"
5
+ - "pool.analytics"
6
+ - "pool.discovery"
7
+ - "llama.data"
8
+ - "pool.reports"
9
+ dependencies:
10
+ - "PoolClient"
@@ -0,0 +1,220 @@
1
+ from unittest.mock import AsyncMock
2
+
3
+ import pytest
4
+
5
+ from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
6
+
7
+
8
+ class TestPoolAdapter:
9
+ """Test cases for PoolAdapter"""
10
+
11
+ @pytest.fixture
12
+ def mock_pool_client(self):
13
+ """Mock PoolClient for testing"""
14
+ mock_client = AsyncMock()
15
+ return mock_client
16
+
17
+ @pytest.fixture
18
+ def adapter(self, mock_pool_client):
19
+ """Create a PoolAdapter instance with mocked client for testing"""
20
+ adapter = PoolAdapter()
21
+ adapter.pool_client = mock_pool_client
22
+ return adapter
23
+
24
+ @pytest.mark.asyncio
25
+ async def test_get_pools_by_ids_success(self, adapter, mock_pool_client):
26
+ """Test successful pool retrieval by IDs"""
27
+ mock_response = {
28
+ "pools": [
29
+ {
30
+ "id": "pool-123",
31
+ "name": "USDC/USDT Pool",
32
+ "symbol": "USDC-USDT",
33
+ "apy": 0.05,
34
+ "tvl": 1000000,
35
+ }
36
+ ]
37
+ }
38
+ mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_response)
39
+
40
+ success, data = await adapter.get_pools_by_ids(
41
+ pool_ids=["pool-123", "pool-456"], merge_external=True
42
+ )
43
+
44
+ assert success is True
45
+ assert data == mock_response
46
+ mock_pool_client.get_pools_by_ids.assert_called_once_with(
47
+ pool_ids="pool-123,pool-456", merge_external=True
48
+ )
49
+
50
+ @pytest.mark.asyncio
51
+ async def test_get_all_pools_success(self, adapter, mock_pool_client):
52
+ """Test successful retrieval of all pools"""
53
+ # Mock response
54
+ mock_response = {
55
+ "pools": [
56
+ {"id": "pool-123", "name": "Pool 1"},
57
+ {"id": "pool-456", "name": "Pool 2"},
58
+ ],
59
+ "total": 2,
60
+ }
61
+ mock_pool_client.get_all_pools = AsyncMock(return_value=mock_response)
62
+
63
+ success, data = await adapter.get_all_pools(merge_external=False)
64
+
65
+ assert success is True
66
+ assert data == mock_response
67
+ mock_pool_client.get_all_pools.assert_called_once_with(merge_external=False)
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_get_llama_matches_success(self, adapter, mock_pool_client):
71
+ """Test successful Llama matches retrieval"""
72
+ # Mock response
73
+ mock_response = {
74
+ "matches": [
75
+ {
76
+ "id": "pool-123",
77
+ "llama_apy_pct": 5.2,
78
+ "llama_tvl_usd": 1000000,
79
+ "llama_stablecoin": True,
80
+ "network": "base",
81
+ }
82
+ ]
83
+ }
84
+ mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_response)
85
+
86
+ success, data = await adapter.get_llama_matches()
87
+
88
+ assert success is True
89
+ assert data == mock_response
90
+
91
+ @pytest.mark.asyncio
92
+ async def test_find_high_yield_pools_success(self, adapter, mock_pool_client):
93
+ """Test successful high yield pool discovery"""
94
+ mock_llama_response = {
95
+ "matches": [
96
+ {
97
+ "pool_id": "pool-123",
98
+ "llama_apy_pct": 5.2,
99
+ "llama_tvl_usd": 1000000,
100
+ "llama_stablecoin": True,
101
+ "network": "base",
102
+ },
103
+ {
104
+ "pool_id": "pool-456",
105
+ "llama_apy_pct": 2.0,
106
+ "llama_tvl_usd": 500000,
107
+ "llama_stablecoin": True,
108
+ "network": "ethereum",
109
+ },
110
+ {
111
+ "pool_id": "pool-789",
112
+ "llama_apy_pct": 6.0,
113
+ "llama_tvl_usd": 2000000,
114
+ "llama_stablecoin": False,
115
+ "network": "base",
116
+ },
117
+ ]
118
+ }
119
+ mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_llama_response)
120
+
121
+ success, data = await adapter.find_high_yield_pools(
122
+ min_apy=0.03, min_tvl=500000, stablecoin_only=True, network_codes=["base"]
123
+ )
124
+
125
+ assert success is True
126
+ assert len(data["pools"]) == 1 # Only pool-123 meets criteria
127
+ assert (
128
+ data["pools"][0].get("pool_id") == "pool-123"
129
+ or data["pools"][0].get("id") == "pool-123"
130
+ )
131
+ assert data["total_found"] == 1
132
+ assert data["filters_applied"]["min_apy"] == 0.03
133
+ assert data["filters_applied"]["stablecoin_only"] is True
134
+
135
+ @pytest.mark.asyncio
136
+ async def test_get_pool_analytics_success(self, adapter, mock_pool_client):
137
+ """Test successful pool analytics generation"""
138
+ mock_pool_data = {
139
+ "pools": [
140
+ {"id": "pool-123", "name": "USDC/USDT Pool", "symbol": "USDC-USDT"}
141
+ ]
142
+ }
143
+ mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_pool_data)
144
+
145
+ mock_llama_data = {
146
+ "pool-123": {
147
+ "llama_apy_pct": 5.2,
148
+ "llama_combined_apy_pct": 5.2,
149
+ "llama_tvl_usd": 1000000,
150
+ }
151
+ }
152
+ mock_pool_client.get_llama_reports = AsyncMock(return_value=mock_llama_data)
153
+
154
+ success, data = await adapter.get_pool_analytics(["pool-123"])
155
+
156
+ assert success is True
157
+ assert len(data["analytics"]) == 1
158
+ assert data["analytics"][0]["pool"]["id"] == "pool-123"
159
+ assert round(data["analytics"][0]["combined_apy"], 6) == round(0.052, 6)
160
+ assert data["analytics"][0]["tvl_usd"] == 1000000
161
+
162
+ @pytest.mark.asyncio
163
+ async def test_search_pools_success(self, adapter, mock_pool_client):
164
+ """Test successful pool search"""
165
+ mock_all_pools = {
166
+ "pools": [
167
+ {
168
+ "id": "pool-123",
169
+ "name": "USDC/USDT Pool",
170
+ "symbol": "USDC-USDT",
171
+ "description": "Stablecoin pool on Base",
172
+ },
173
+ {
174
+ "id": "pool-456",
175
+ "name": "ETH/WETH Pool",
176
+ "symbol": "ETH-WETH",
177
+ "description": "Ethereum pool",
178
+ },
179
+ ]
180
+ }
181
+ mock_pool_client.get_all_pools = AsyncMock(return_value=mock_all_pools)
182
+
183
+ success, data = await adapter.search_pools("USDC", limit=5)
184
+
185
+ assert success is True
186
+ assert len(data["pools"]) == 1
187
+ assert data["pools"][0]["id"] == "pool-123"
188
+ assert data["total_found"] == 1
189
+ assert data["query"] == "USDC"
190
+
191
+ @pytest.mark.asyncio
192
+ async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
193
+ """Test pool retrieval failure"""
194
+ mock_pool_client.get_pools_by_ids = AsyncMock(
195
+ side_effect=Exception("API Error")
196
+ )
197
+
198
+ success, data = await adapter.get_pools_by_ids(["pool-123"])
199
+
200
+ assert success is False
201
+ assert "API Error" in data
202
+
203
+ @pytest.mark.asyncio
204
+ async def test_find_high_yield_pools_no_matches(self, adapter, mock_pool_client):
205
+ """Test high yield pool discovery with no matches"""
206
+ mock_llama_response = {"matches": []}
207
+ mock_pool_client.get_llama_matches.return_value = mock_llama_response
208
+
209
+ success, data = await adapter.find_high_yield_pools(
210
+ min_apy=0.10,
211
+ min_tvl=10000000,
212
+ )
213
+
214
+ assert success is True
215
+ assert len(data["pools"]) == 0
216
+ assert data["total_found"] == 0
217
+
218
+ def test_adapter_type(self, adapter):
219
+ """Test adapter has adapter_type"""
220
+ assert adapter.adapter_type == "POOL"
@@ -0,0 +1,101 @@
1
+ # Token Adapter
2
+
3
+ A Wayfinder adapter that wraps the `_get_token_via_api` method for fetching token data via HeadlessAPIViewSet endpoints. This adapter supports both address and token_id lookups.
4
+
5
+ ## Capabilities
6
+
7
+ - `token.read`: Retrieve token information by address or token ID
8
+
9
+ ## Configuration
10
+
11
+ The adapter uses the TokenClient which automatically handles authentication and API configuration through the Wayfinder settings. No additional configuration is required.
12
+
13
+ The TokenClient will automatically:
14
+ - Use the WAYFINDER_API_URL from settings
15
+ - Handle authentication via environment variables or config.json
16
+ - Manage token refresh and retry logic
17
+
18
+ ## Usage
19
+
20
+ ### Initialize the Adapter
21
+
22
+ ```python
23
+ from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
24
+
25
+ # No configuration needed - uses TokenClient with automatic settings
26
+ adapter = TokenAdapter()
27
+ ```
28
+
29
+ ### Get Token Metadata
30
+
31
+ Both contract addresses and token ids are supported with the same method. Pass whichever identifier you have:
32
+
33
+ ```python
34
+ success, data = await adapter.get_token("0x1234...") # by address
35
+ # or
36
+ success, data = await adapter.get_token("usd-coin-base") # by token id
37
+
38
+ if success:
39
+ print(data)
40
+ else:
41
+ print(f"Error: {data}")
42
+ ```
43
+
44
+ ### Get Token Price
45
+
46
+ ```python
47
+ success, data = await adapter.get_token_price("token-123")
48
+ if success:
49
+ print(f"Price: ${data['current_price']}")
50
+ print(f"24h Change: {data['price_change_percentage_24h']}%")
51
+ else:
52
+ print(f"Error: {data}")
53
+ ```
54
+
55
+ ### Get Gas Token
56
+
57
+ ```python
58
+ success, data = await adapter.get_gas_token("base")
59
+ if success:
60
+ print(f"Gas token: {data['symbol']} - {data['name']}")
61
+ print(f"Address: {data['address']}")
62
+ else:
63
+ print(f"Error: {data}")
64
+ ```
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
+ ## API Endpoints
78
+
79
+ The adapter uses the following existing public API endpoints:
80
+
81
+ 1. **Token Details** (by address): `GET /public/tokens/detail/?query={address}&get_chart=false`
82
+ 2. **Token by ID**: `GET /public/tokens/lookup/?token_id={token_id}`
83
+
84
+ ## Error Handling
85
+
86
+ All methods return a tuple of `(success: bool, data: Any)` where:
87
+ - `success` indicates whether the operation was successful
88
+ - `data` contains either the token information (on success) or an error message (on failure)
89
+
90
+ ## Health Check
91
+
92
+ The adapter includes a health check that tests connectivity to the API:
93
+
94
+ ```python
95
+ health = await adapter.health_check()
96
+ print(f"Status: {health['status']}")
97
+ ```
98
+
99
+ ## Examples
100
+
101
+ See `examples.json` for more detailed usage examples.
@@ -0,0 +1,3 @@
1
+ from .adapter import TokenAdapter
2
+
3
+ __all__ = ["TokenAdapter"]
@@ -0,0 +1,96 @@
1
+ from typing import Any
2
+
3
+ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
4
+ from wayfinder_paths.core.clients.TokenClient import (
5
+ GasToken,
6
+ TokenClient,
7
+ TokenDetails,
8
+ )
9
+
10
+
11
+ class TokenAdapter(BaseAdapter):
12
+ """
13
+ Token adapter that wraps the _get_token_via_api method for fetching token data
14
+ via HeadlessAPIViewSet endpoints. Supports both address and token_id lookups.
15
+ """
16
+
17
+ adapter_type: str = "TOKEN"
18
+
19
+ def __init__(
20
+ self,
21
+ config: dict[str, Any] | None = None,
22
+ token_client: TokenClient | None = None,
23
+ ):
24
+ super().__init__("token_adapter", config)
25
+ self.token_client = token_client or TokenClient()
26
+
27
+ async def get_token(self, query: str) -> tuple[bool, TokenDetails | str]:
28
+ """
29
+ Get token data by address using the token-details endpoint.
30
+
31
+ Args:
32
+ address: Token contract address
33
+
34
+ Returns:
35
+ Tuple of (success, data) where data is the token information or error message
36
+ """
37
+ try:
38
+ data = await self.token_client.get_token_details(query)
39
+ if not data:
40
+ return (False, f"No token found for: {query}")
41
+ return (True, data)
42
+ except Exception as e:
43
+ self.logger.error(f"Error getting token by query {query}: {e}")
44
+ return (False, str(e))
45
+
46
+ async def get_token_price(self, token_id: str) -> tuple[bool, dict[str, Any] | str]:
47
+ """
48
+ Get token price by token ID or address using the token-details endpoint.
49
+
50
+ Args:
51
+ token_id: Token identifier or address
52
+
53
+ Returns:
54
+ Tuple of (success, data) where data is the price information or error message
55
+ """
56
+ try:
57
+ data = await self.token_client.get_token_details(token_id)
58
+ if not data:
59
+ return (False, f"No token found for: {token_id}")
60
+
61
+ # Extract just the price information
62
+ price_data = {
63
+ "current_price": data.get("current_price", 0.0),
64
+ "price_change_24h": data.get("price_change_24h", 0.0),
65
+ "price_change_percentage_24h": data.get(
66
+ "price_change_percentage_24h", 0.0
67
+ ),
68
+ "market_cap": data.get("market_cap", 0),
69
+ "total_volume": data.get("total_volume", 0),
70
+ "symbol": data.get("symbol", ""),
71
+ "name": data.get("name", ""),
72
+ "address": data.get("address", ""),
73
+ }
74
+ return (True, price_data)
75
+ except Exception as e:
76
+ self.logger.error(f"Error getting token price for {token_id}: {e}")
77
+ return (False, str(e))
78
+
79
+ async def get_gas_token(self, chain_code: str) -> tuple[bool, GasToken | str]:
80
+ """
81
+ Get gas token for a given chain code.
82
+
83
+ Args:
84
+ chain_code: Chain code (e.g., "base", "ethereum")
85
+
86
+ Returns:
87
+ Tuple of (success, data) where data is the gas token information or error message
88
+ """
89
+ try:
90
+ data = await self.token_client.get_gas_token(chain_code)
91
+ if not data:
92
+ return (False, f"No gas token found for chain: {chain_code}")
93
+ return (True, data)
94
+ except Exception as e:
95
+ self.logger.error(f"Error getting gas token for chain {chain_code}: {e}")
96
+ return (False, str(e))
@@ -0,0 +1,26 @@
1
+ {
2
+ "basic_usage": {
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.get_token_by_address(\"0x1234567890abcdef1234567890abcdef12345678\")\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
+ },
6
+ "get_by_token_id": {
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.get_token_by_id(\"token-12345\")\nif success:\n print(f\"Token data: {data}\")\nelse:\n print(f\"Error: {data}\")"
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}\")"
13
+ },
14
+ "error_handling": {
15
+ "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 missing parameters\ntry:\n success, data = await adapter.get_token()\n if not success:\n print(f\"Error: {data}\")\nexcept Exception as e:\n print(f\"Unexpected error: {e}\")\n\n# Handle API errors\nsuccess, data = await adapter.get_token_by_address(\"invalid-address\")\nif not success:\n print(f\"API error: {data}\")\nelse:\n print(f\"Token found: {data}\")"
17
+ },
18
+ "health_check": {
19
+ "description": "Check adapter health and connectivity",
20
+ "code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Check health\nhealth = await adapter.health_check()\nprint(f\"Adapter status: {health['status']}\")\nprint(f\"Connected: {health['connected']}\")\nprint(f\"Adapter type: {health['adapter']}\")\n\nif health['status'] == 'healthy':\n print(\"Adapter is ready to use\")\nelse:\n print(f\"Adapter has issues: {health.get('error', 'Unknown error')}\")"
21
+ },
22
+ "batch_operations": {
23
+ "description": "Perform multiple token lookups efficiently",
24
+ "code": "from adapters.token_adapter.adapter import TokenAdapter\nimport asyncio\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# List of token addresses to lookup\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_by_address(address)\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
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.token_adapter.adapter.TokenAdapter"
3
+ capabilities:
4
+ - "token.read"
5
+ dependencies:
6
+ - "TokenClient"