wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.20__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/__init__.py +0 -2
- wayfinder_paths/adapters/balance_adapter/README.md +59 -45
- wayfinder_paths/adapters/balance_adapter/adapter.py +0 -21
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
- wayfinder_paths/adapters/brap_adapter/README.md +61 -184
- wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/brap_adapter/adapter.py +0 -147
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -9
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
- wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
- wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
- wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
- wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
- wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +9 -121
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
- wayfinder_paths/adapters/pool_adapter/README.md +30 -51
- wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
- wayfinder_paths/adapters/token_adapter/README.md +41 -49
- wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
- wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
- wayfinder_paths/conftest.py +0 -8
- wayfinder_paths/core/__init__.py +0 -2
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
- wayfinder_paths/core/adapters/__init__.py +0 -5
- wayfinder_paths/core/adapters/models.py +0 -5
- wayfinder_paths/core/analytics/__init__.py +0 -2
- wayfinder_paths/core/analytics/bootstrap.py +0 -16
- wayfinder_paths/core/analytics/stats.py +0 -7
- wayfinder_paths/core/analytics/test_analytics.py +5 -34
- wayfinder_paths/core/clients/BRAPClient.py +0 -35
- wayfinder_paths/core/clients/ClientManager.py +0 -51
- wayfinder_paths/core/clients/HyperlendClient.py +0 -77
- wayfinder_paths/core/clients/LedgerClient.py +2 -122
- wayfinder_paths/core/clients/PoolClient.py +0 -2
- wayfinder_paths/core/clients/TokenClient.py +0 -39
- wayfinder_paths/core/clients/WalletClient.py +0 -15
- wayfinder_paths/core/clients/WayfinderClient.py +0 -24
- wayfinder_paths/core/clients/__init__.py +0 -4
- wayfinder_paths/core/clients/protocols.py +25 -98
- wayfinder_paths/core/config.py +0 -24
- wayfinder_paths/core/constants/__init__.py +0 -7
- wayfinder_paths/core/constants/base.py +2 -9
- wayfinder_paths/core/constants/erc20_abi.py +0 -5
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
- wayfinder_paths/core/constants/moonwell_abi.py +0 -35
- wayfinder_paths/core/engine/StrategyJob.py +0 -32
- wayfinder_paths/core/strategies/Strategy.py +0 -99
- wayfinder_paths/core/strategies/__init__.py +0 -2
- wayfinder_paths/core/utils/__init__.py +0 -1
- wayfinder_paths/core/utils/erc20_service.py +0 -1
- wayfinder_paths/core/utils/evm_helpers.py +0 -50
- wayfinder_paths/core/utils/transaction.py +0 -1
- wayfinder_paths/run_strategy.py +0 -46
- wayfinder_paths/scripts/create_strategy.py +0 -17
- wayfinder_paths/scripts/make_wallets.py +1 -4
- wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
- wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
- wayfinder_paths/templates/adapter/README.md +107 -46
- wayfinder_paths/templates/adapter/adapter.py +0 -9
- wayfinder_paths/templates/adapter/test_adapter.py +0 -19
- wayfinder_paths/templates/strategy/README.md +113 -59
- wayfinder_paths/templates/strategy/strategy.py +0 -22
- wayfinder_paths/templates/strategy/test_strategy.py +0 -28
- wayfinder_paths/tests/test_test_coverage.py +2 -12
- wayfinder_paths/tests/test_utils.py +1 -31
- wayfinder_paths-0.1.20.dist-info/METADATA +355 -0
- wayfinder_paths-0.1.20.dist-info/RECORD +129 -0
- wayfinder_paths/core/adapters/base.py +0 -5
- wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
- wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/WHEEL +0 -0
wayfinder_paths/__init__.py
CHANGED
|
@@ -1,91 +1,105 @@
|
|
|
1
1
|
# Balance Adapter
|
|
2
2
|
|
|
3
|
-
Adapter
|
|
3
|
+
Adapter for wallet and token balances with cross-wallet transfer capabilities.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
5
|
+
- **Type**: `BALANCE`
|
|
6
|
+
- **Module**: `wayfinder_paths.adapters.balance_adapter.adapter.BalanceAdapter`
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Overview
|
|
9
9
|
|
|
10
|
-
The
|
|
10
|
+
The BalanceAdapter provides:
|
|
11
|
+
- Token balance queries for any wallet
|
|
12
|
+
- Cross-wallet transfers between main and strategy wallets
|
|
13
|
+
- Automatic ledger recording for deposits/withdrawals
|
|
11
14
|
|
|
12
|
-
##
|
|
15
|
+
## Usage
|
|
13
16
|
|
|
14
17
|
```python
|
|
15
18
|
from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
|
|
16
19
|
|
|
20
|
+
adapter = BalanceAdapter(
|
|
21
|
+
config=config,
|
|
22
|
+
main_wallet_signing_callback=main_signing_cb,
|
|
23
|
+
strategy_wallet_signing_callback=strategy_signing_cb,
|
|
24
|
+
)
|
|
17
25
|
```
|
|
18
26
|
|
|
19
|
-
##
|
|
27
|
+
## Methods
|
|
20
28
|
|
|
21
|
-
###
|
|
29
|
+
### get_balance
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
Get token balance for a wallet.
|
|
24
32
|
|
|
25
33
|
```python
|
|
26
|
-
success, balance = await
|
|
34
|
+
success, balance = await adapter.get_balance(
|
|
27
35
|
token_id="usd-coin-base",
|
|
28
|
-
wallet_address=
|
|
36
|
+
wallet_address="0x...",
|
|
37
|
+
chain_id=8453, # optional, auto-resolved from token
|
|
29
38
|
)
|
|
30
39
|
```
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
**Returns**: `(bool, int)` - success flag and raw balance (in token units)
|
|
42
|
+
|
|
43
|
+
### move_from_main_wallet_to_strategy_wallet
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
Transfer tokens from main wallet to strategy wallet with ledger recording.
|
|
35
46
|
|
|
36
47
|
```python
|
|
37
|
-
success,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
success, tx_hash = await adapter.move_from_main_wallet_to_strategy_wallet(
|
|
49
|
+
token_id="usd-coin-base",
|
|
50
|
+
amount=100.0, # human-readable amount
|
|
51
|
+
strategy_name="my_strategy",
|
|
52
|
+
skip_ledger=False,
|
|
41
53
|
)
|
|
42
54
|
```
|
|
43
55
|
|
|
44
|
-
###
|
|
56
|
+
### move_from_strategy_wallet_to_main_wallet
|
|
45
57
|
|
|
46
|
-
|
|
58
|
+
Transfer tokens from strategy wallet back to main wallet.
|
|
47
59
|
|
|
48
60
|
```python
|
|
49
|
-
success,
|
|
61
|
+
success, tx_hash = await adapter.move_from_strategy_wallet_to_main_wallet(
|
|
50
62
|
token_id="usd-coin-base",
|
|
51
|
-
amount=
|
|
52
|
-
strategy_name="
|
|
63
|
+
amount=50.0,
|
|
64
|
+
strategy_name="my_strategy",
|
|
65
|
+
skip_ledger=False,
|
|
53
66
|
)
|
|
54
67
|
```
|
|
55
68
|
|
|
56
|
-
###
|
|
69
|
+
### send_to_address
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
Send tokens to an arbitrary address (e.g., bridge contract).
|
|
59
72
|
|
|
60
73
|
```python
|
|
61
|
-
await
|
|
74
|
+
success, tx_hash = await adapter.send_to_address(
|
|
62
75
|
token_id="usd-coin-base",
|
|
63
|
-
amount=
|
|
64
|
-
|
|
76
|
+
amount=1000000, # raw amount
|
|
77
|
+
from_wallet=config["strategy_wallet"],
|
|
78
|
+
to_address="0xBridgeContract...",
|
|
79
|
+
signing_callback=strategy_signing_cb,
|
|
65
80
|
)
|
|
66
81
|
```
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
## Configuration
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
The adapter requires wallet configuration in `config`:
|
|
71
86
|
|
|
72
87
|
```python
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
self.register_adapters([balance_adapter])
|
|
78
|
-
self.balance_adapter = balance_adapter
|
|
79
|
-
|
|
80
|
-
async def _status(self):
|
|
81
|
-
success, pool_balance = await self.balance_adapter.get_pool_balance(
|
|
82
|
-
pool_address=self.current_pool["address"],
|
|
83
|
-
chain_id=self.current_pool["chain"]["id"],
|
|
84
|
-
user_address=self.config["strategy_wallet"]["address"],
|
|
85
|
-
)
|
|
86
|
-
return {"portfolio_value": float(pool_balance or 0), ...}
|
|
88
|
+
config = {
|
|
89
|
+
"main_wallet": {"address": "0x..."},
|
|
90
|
+
"strategy_wallet": {"address": "0x..."},
|
|
91
|
+
}
|
|
87
92
|
```
|
|
88
93
|
|
|
89
|
-
##
|
|
94
|
+
## Dependencies
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
- `WalletClient` - For balance queries
|
|
97
|
+
- `TokenClient` - For token metadata
|
|
98
|
+
- `LedgerAdapter` - For transaction recording
|
|
99
|
+
- `TokenAdapter` - For price lookups
|
|
100
|
+
|
|
101
|
+
## Testing
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
poetry run pytest wayfinder_paths/adapters/balance_adapter/ -v
|
|
105
|
+
```
|
|
@@ -16,12 +16,10 @@ class BalanceAdapter(BaseAdapter):
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self,
|
|
18
18
|
config: dict[str, Any],
|
|
19
|
-
simulation: bool = False,
|
|
20
19
|
main_wallet_signing_callback=None,
|
|
21
20
|
strategy_wallet_signing_callback=None,
|
|
22
21
|
):
|
|
23
22
|
super().__init__("balance", config)
|
|
24
|
-
self.simulation = simulation
|
|
25
23
|
self.main_wallet_signing_callback = main_wallet_signing_callback
|
|
26
24
|
self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
|
|
27
25
|
self.wallet_client = WalletClient()
|
|
@@ -30,7 +28,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
30
28
|
self.ledger_adapter = LedgerAdapter()
|
|
31
29
|
|
|
32
30
|
def _parse_balance(self, raw: Any) -> int:
|
|
33
|
-
"""Parse balance value to integer, handling various formats."""
|
|
34
31
|
if raw is None:
|
|
35
32
|
return 0
|
|
36
33
|
try:
|
|
@@ -49,12 +46,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
49
46
|
wallet_address: str,
|
|
50
47
|
chain_id: int | None = None,
|
|
51
48
|
) -> tuple[bool, str | int]:
|
|
52
|
-
"""Get token or pool balance for a wallet.
|
|
53
|
-
|
|
54
|
-
query: token_id/address string or a dict with a "token_id" key.
|
|
55
|
-
token_id: alternative to query for convenience.
|
|
56
|
-
"""
|
|
57
|
-
# Support both query= and token_id= for caller convenience
|
|
58
49
|
effective_query = query if query is not None else token_id
|
|
59
50
|
resolved = (
|
|
60
51
|
effective_query
|
|
@@ -78,7 +69,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
78
69
|
query=resolved,
|
|
79
70
|
chain_id=int(chain_id),
|
|
80
71
|
)
|
|
81
|
-
# Use _parse_balance for consistent parsing (handles balance_raw or balance)
|
|
82
72
|
raw = (
|
|
83
73
|
data.get("balance_raw") or data.get("balance")
|
|
84
74
|
if isinstance(data, dict)
|
|
@@ -95,7 +85,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
95
85
|
strategy_name: str = "unknown",
|
|
96
86
|
skip_ledger: bool = False,
|
|
97
87
|
) -> tuple[bool, Any]:
|
|
98
|
-
"""Move funds from the configured main wallet into the strategy wallet."""
|
|
99
88
|
return await self._move_between_wallets(
|
|
100
89
|
token_id=token_id,
|
|
101
90
|
amount=amount,
|
|
@@ -114,7 +103,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
114
103
|
strategy_name: str = "unknown",
|
|
115
104
|
skip_ledger: bool = False,
|
|
116
105
|
) -> tuple[bool, Any]:
|
|
117
|
-
"""Move funds from the strategy wallet back into the main wallet."""
|
|
118
106
|
return await self._move_between_wallets(
|
|
119
107
|
token_id=token_id,
|
|
120
108
|
amount=amount,
|
|
@@ -135,7 +123,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
135
123
|
signing_callback=None,
|
|
136
124
|
skip_ledger: bool = True,
|
|
137
125
|
) -> tuple[bool, Any]:
|
|
138
|
-
"""Send tokens from a wallet to an arbitrary address (e.g., bridge contract)."""
|
|
139
126
|
from_address = self._wallet_address(from_wallet)
|
|
140
127
|
if not from_address:
|
|
141
128
|
return False, "from_wallet missing or invalid"
|
|
@@ -161,9 +148,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
161
148
|
amount=int(amount),
|
|
162
149
|
)
|
|
163
150
|
|
|
164
|
-
if self.simulation:
|
|
165
|
-
return True, {"simulation": tx}
|
|
166
|
-
|
|
167
151
|
if not signing_callback:
|
|
168
152
|
return False, "signing_callback is required"
|
|
169
153
|
|
|
@@ -220,11 +204,6 @@ class BalanceAdapter(BaseAdapter):
|
|
|
220
204
|
return broadcast_result
|
|
221
205
|
|
|
222
206
|
async def _send_tx(self, tx: dict[str, Any], from_address: str) -> tuple[bool, Any]:
|
|
223
|
-
"""Send transaction with simulation check, using appropriate signing callback."""
|
|
224
|
-
if self.simulation:
|
|
225
|
-
return True, {"simulation": tx}
|
|
226
|
-
|
|
227
|
-
# Choose callback based on which wallet is sending
|
|
228
207
|
main_wallet = self.config.get("main_wallet") or {}
|
|
229
208
|
main_addr = main_wallet.get("address", "").lower()
|
|
230
209
|
|
|
@@ -6,23 +6,18 @@ from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestBalanceAdapter:
|
|
9
|
-
"""Test cases for BalanceAdapter"""
|
|
10
|
-
|
|
11
9
|
@pytest.fixture
|
|
12
10
|
def mock_wallet_client(self):
|
|
13
|
-
"""Mock WalletClient for testing"""
|
|
14
11
|
mock_client = AsyncMock()
|
|
15
12
|
return mock_client
|
|
16
13
|
|
|
17
14
|
@pytest.fixture
|
|
18
15
|
def mock_token_client(self):
|
|
19
|
-
"""Mock TokenClient for testing"""
|
|
20
16
|
mock_client = AsyncMock()
|
|
21
17
|
return mock_client
|
|
22
18
|
|
|
23
19
|
@pytest.fixture
|
|
24
20
|
def adapter(self, mock_wallet_client, mock_token_client):
|
|
25
|
-
"""Create a BalanceAdapter instance with mocked clients for testing"""
|
|
26
21
|
with (
|
|
27
22
|
patch(
|
|
28
23
|
"wayfinder_paths.adapters.balance_adapter.adapter.WalletClient",
|
|
@@ -37,26 +32,22 @@ class TestBalanceAdapter:
|
|
|
37
32
|
|
|
38
33
|
@pytest.mark.asyncio
|
|
39
34
|
async def test_health_check(self, adapter):
|
|
40
|
-
"""Test adapter health check"""
|
|
41
35
|
health = await adapter.health_check()
|
|
42
36
|
assert isinstance(health, dict)
|
|
43
37
|
assert health.get("status") in {"healthy", "unhealthy", "error"}
|
|
44
38
|
|
|
45
39
|
@pytest.mark.asyncio
|
|
46
40
|
async def test_connect(self, adapter):
|
|
47
|
-
"""Test adapter connection"""
|
|
48
41
|
ok = await adapter.connect()
|
|
49
42
|
assert isinstance(ok, bool)
|
|
50
43
|
|
|
51
44
|
def test_adapter_type(self, adapter):
|
|
52
|
-
"""Test adapter has adapter_type"""
|
|
53
45
|
assert adapter.adapter_type == "BALANCE"
|
|
54
46
|
|
|
55
47
|
@pytest.mark.asyncio
|
|
56
48
|
async def test_get_balance_with_query_string(
|
|
57
49
|
self, adapter, mock_token_client, mock_wallet_client
|
|
58
50
|
):
|
|
59
|
-
"""Test get_balance with query as string (auto-resolves chain_id)."""
|
|
60
51
|
mock_token_client.get_token_details = AsyncMock(
|
|
61
52
|
return_value={
|
|
62
53
|
"token_id": "usd-coin-base",
|
|
@@ -86,7 +77,6 @@ class TestBalanceAdapter:
|
|
|
86
77
|
async def test_get_balance_with_query_dict(
|
|
87
78
|
self, adapter, mock_token_client, mock_wallet_client
|
|
88
79
|
):
|
|
89
|
-
"""get_balance accepts query= as dict with token_id key."""
|
|
90
80
|
mock_token_client.get_token_details = AsyncMock(
|
|
91
81
|
return_value={
|
|
92
82
|
"token_id": "wsteth-base",
|
|
@@ -113,7 +103,6 @@ class TestBalanceAdapter:
|
|
|
113
103
|
|
|
114
104
|
@pytest.mark.asyncio
|
|
115
105
|
async def test_get_balance_missing_query(self, adapter):
|
|
116
|
-
"""get_balance returns error when query is empty or missing token_id."""
|
|
117
106
|
success, result = await adapter.get_balance(query={}, wallet_address="0xabc")
|
|
118
107
|
assert success is False
|
|
119
108
|
assert "missing query" in str(result)
|
|
@@ -122,7 +111,6 @@ class TestBalanceAdapter:
|
|
|
122
111
|
async def test_get_balance_with_pool_address(
|
|
123
112
|
self, adapter, mock_token_client, mock_wallet_client
|
|
124
113
|
):
|
|
125
|
-
"""Test get_balance with pool address (explicit chain_id)"""
|
|
126
114
|
mock_wallet_client.get_token_balance_for_address = AsyncMock(
|
|
127
115
|
return_value={"balance": 5000000}
|
|
128
116
|
)
|
|
@@ -145,7 +133,6 @@ class TestBalanceAdapter:
|
|
|
145
133
|
|
|
146
134
|
@pytest.mark.asyncio
|
|
147
135
|
async def test_get_balance_token_not_found(self, adapter, mock_token_client):
|
|
148
|
-
"""Test get_balance when token is not found"""
|
|
149
136
|
mock_token_client.get_token_details = AsyncMock(return_value=None)
|
|
150
137
|
|
|
151
138
|
success, error = await adapter.get_balance(
|
|
@@ -158,7 +145,6 @@ class TestBalanceAdapter:
|
|
|
158
145
|
|
|
159
146
|
@pytest.mark.asyncio
|
|
160
147
|
async def test_get_balance_missing_chain_id(self, adapter, mock_token_client):
|
|
161
|
-
"""Test get_balance when chain_id cannot be resolved"""
|
|
162
148
|
mock_token_client.get_token_details = AsyncMock(
|
|
163
149
|
return_value={
|
|
164
150
|
"token_id": "token-without-chain",
|
|
@@ -1,247 +1,124 @@
|
|
|
1
1
|
# BRAP Adapter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Adapter for cross-chain swaps and bridges via the BRAP (Bridge/Router/Adapter Protocol).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **Type**: `BRAP`
|
|
6
|
+
- **Module**: `wayfinder_paths.adapters.brap_adapter.adapter.BRAPAdapter`
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- `swap.execute`: Execute cross-chain swaps
|
|
9
|
-
- `bridge.quote`: Get quotes for bridge operations
|
|
10
|
-
- `bridge.execute`: Execute bridge operations
|
|
11
|
-
- `route.optimize`: Compare and optimize routes
|
|
12
|
-
- `fee.calculate`: Calculate fees and costs
|
|
8
|
+
## Overview
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- Manage token refresh and retry logic
|
|
10
|
+
The BRAPAdapter provides:
|
|
11
|
+
- Cross-chain swap quotes
|
|
12
|
+
- Bridge operation quotes
|
|
13
|
+
- Route comparison and optimization
|
|
14
|
+
- Fee calculations
|
|
15
|
+
- Swap execution
|
|
21
16
|
|
|
22
17
|
## Usage
|
|
23
18
|
|
|
24
|
-
### Initialize the Adapter
|
|
25
|
-
|
|
26
19
|
```python
|
|
27
20
|
from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
|
|
28
21
|
|
|
29
22
|
adapter = BRAPAdapter()
|
|
30
23
|
```
|
|
31
24
|
|
|
32
|
-
|
|
25
|
+
## Methods
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
success, data = await adapter.get_swap_quote(
|
|
36
|
-
from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
|
|
37
|
-
to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
|
|
38
|
-
from_chain_id=8453, # Base
|
|
39
|
-
to_chain_id=1, # Ethereum
|
|
40
|
-
from_address="0x1234567890123456789012345678901234567890",
|
|
41
|
-
to_address="0x1234567890123456789012345678901234567890",
|
|
42
|
-
amount="1000000000000000000", # 1 token (18 decimals)
|
|
43
|
-
slippage=0.01 # 1% slippage
|
|
44
|
-
)
|
|
45
|
-
if success:
|
|
46
|
-
quotes = data.get("quotes", {})
|
|
47
|
-
best_quote = quotes.get("best_quote", {})
|
|
48
|
-
print(f"Output amount: {best_quote.get('output_amount')}")
|
|
49
|
-
print(f"Total fee: {best_quote.get('total_fee')}")
|
|
50
|
-
else:
|
|
51
|
-
print(f"Error: {data}")
|
|
52
|
-
```
|
|
27
|
+
### get_swap_quote
|
|
53
28
|
|
|
54
|
-
|
|
29
|
+
Get quotes for a cross-chain swap.
|
|
55
30
|
|
|
56
31
|
```python
|
|
57
|
-
success, data = await adapter.
|
|
58
|
-
from_token_address="
|
|
59
|
-
to_token_address="
|
|
32
|
+
success, data = await adapter.get_swap_quote(
|
|
33
|
+
from_token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
34
|
+
to_token_address="0x...",
|
|
60
35
|
from_chain_id=8453,
|
|
61
36
|
to_chain_id=1,
|
|
62
|
-
from_address="
|
|
63
|
-
to_address="
|
|
64
|
-
amount="
|
|
37
|
+
from_address="0x...",
|
|
38
|
+
to_address="0x...",
|
|
39
|
+
amount="1000000000", # Raw amount
|
|
40
|
+
slippage=0.01, # 1%
|
|
65
41
|
)
|
|
66
42
|
if success:
|
|
67
|
-
|
|
68
|
-
print(f"
|
|
69
|
-
print(f"Bridge fee: {data.get('bridge_fee')}")
|
|
70
|
-
else:
|
|
71
|
-
print(f"Error: {data}")
|
|
43
|
+
best_quote = data.get("quotes", {}).get("best_quote", {})
|
|
44
|
+
print(f"Output: {best_quote.get('output_amount')}")
|
|
72
45
|
```
|
|
73
46
|
|
|
74
|
-
###
|
|
47
|
+
### get_best_quote
|
|
75
48
|
|
|
76
|
-
|
|
77
|
-
success, data = await adapter.calculate_swap_fees(
|
|
78
|
-
from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
|
|
79
|
-
to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
|
|
80
|
-
from_chain_id=8453,
|
|
81
|
-
to_chain_id=1,
|
|
82
|
-
amount="1000000000000000000",
|
|
83
|
-
slippage=0.01
|
|
84
|
-
)
|
|
85
|
-
if success:
|
|
86
|
-
print(f"Input amount: {data.get('input_amount')}")
|
|
87
|
-
print(f"Output amount: {data.get('output_amount')}")
|
|
88
|
-
print(f"Gas fee: {data.get('gas_fee')}")
|
|
89
|
-
print(f"Bridge fee: {data.get('bridge_fee')}")
|
|
90
|
-
print(f"Protocol fee: {data.get('protocol_fee')}")
|
|
91
|
-
print(f"Total fee: {data.get('total_fee')}")
|
|
92
|
-
print(f"Price impact: {data.get('price_impact')}")
|
|
93
|
-
else:
|
|
94
|
-
print(f"Error: {data}")
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Compare Routes
|
|
49
|
+
Get the best quote for a swap.
|
|
98
50
|
|
|
99
51
|
```python
|
|
100
|
-
success, data = await adapter.
|
|
101
|
-
from_token_address="
|
|
102
|
-
to_token_address="
|
|
52
|
+
success, data = await adapter.get_best_quote(
|
|
53
|
+
from_token_address="0x...",
|
|
54
|
+
to_token_address="0x...",
|
|
103
55
|
from_chain_id=8453,
|
|
104
56
|
to_chain_id=1,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
print(f"Total routes available: {data.get('total_routes')}")
|
|
109
|
-
print(f"Best route output: {data.get('best_route', {}).get('output_amount')}")
|
|
110
|
-
|
|
111
|
-
for i, route in enumerate(data.get('all_routes', [])):
|
|
112
|
-
print(f"Route {i+1}: Output {route.get('output_amount')}, Fee {route.get('total_fee')}")
|
|
113
|
-
else:
|
|
114
|
-
print(f"Error: {data}")
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Estimate Gas Costs
|
|
118
|
-
|
|
119
|
-
```python
|
|
120
|
-
success, data = await adapter.estimate_gas_cost(
|
|
121
|
-
from_chain_id=8453, # Base
|
|
122
|
-
to_chain_id=1, # Ethereum
|
|
123
|
-
operation_type="swap"
|
|
57
|
+
from_address="0x...",
|
|
58
|
+
to_address="0x...",
|
|
59
|
+
amount="1000000000",
|
|
124
60
|
)
|
|
125
|
-
if success:
|
|
126
|
-
print(f"From chain: {data.get('from_chain')}")
|
|
127
|
-
print(f"To chain: {data.get('to_chain')}")
|
|
128
|
-
print(f"From gas estimate: {data.get('from_gas_estimate')}")
|
|
129
|
-
print(f"To gas estimate: {data.get('to_gas_estimate')}")
|
|
130
|
-
print(f"Total operations: {data.get('total_operations')}")
|
|
131
|
-
else:
|
|
132
|
-
print(f"Error: {data}")
|
|
133
61
|
```
|
|
134
62
|
|
|
135
|
-
###
|
|
136
|
-
|
|
137
|
-
```python
|
|
138
|
-
success, data = await adapter.validate_swap_parameters(
|
|
139
|
-
from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
|
|
140
|
-
to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
|
|
141
|
-
from_chain_id=8453,
|
|
142
|
-
to_chain_id=1,
|
|
143
|
-
amount="1000000000000000000"
|
|
144
|
-
)
|
|
145
|
-
if success:
|
|
146
|
-
if data.get("valid"):
|
|
147
|
-
print("Parameters are valid")
|
|
148
|
-
print(f"Estimated output: {data.get('estimated_output')}")
|
|
149
|
-
else:
|
|
150
|
-
print("Parameters are invalid:")
|
|
151
|
-
for error in data.get("errors", []):
|
|
152
|
-
print(f" - {error}")
|
|
153
|
-
else:
|
|
154
|
-
print(f"Error: {data}")
|
|
155
|
-
```
|
|
63
|
+
### compare_routes
|
|
156
64
|
|
|
157
|
-
|
|
65
|
+
Compare available routes for a swap.
|
|
158
66
|
|
|
159
67
|
```python
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
|
|
68
|
+
success, data = await adapter.compare_routes(
|
|
69
|
+
from_token_address="0x...",
|
|
70
|
+
to_token_address="0x...",
|
|
164
71
|
from_chain_id=8453,
|
|
165
72
|
to_chain_id=1,
|
|
166
|
-
amount="
|
|
167
|
-
slippage=0.01
|
|
73
|
+
amount="1000000000",
|
|
168
74
|
)
|
|
169
75
|
if success:
|
|
170
|
-
print(f"
|
|
171
|
-
|
|
172
|
-
|
|
76
|
+
print(f"Total routes: {data.get('total_routes')}")
|
|
77
|
+
for route in data.get("all_routes", []):
|
|
78
|
+
print(f"Output: {route.get('output_amount')}, Fee: {route.get('total_fee')}")
|
|
173
79
|
```
|
|
174
80
|
|
|
175
|
-
|
|
81
|
+
### calculate_swap_fees
|
|
176
82
|
|
|
177
|
-
|
|
83
|
+
Calculate fees for a swap operation.
|
|
178
84
|
|
|
179
85
|
```python
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
|
|
86
|
+
success, data = await adapter.calculate_swap_fees(
|
|
87
|
+
from_token_address="0x...",
|
|
88
|
+
to_token_address="0x...",
|
|
184
89
|
from_chain_id=8453,
|
|
185
90
|
to_chain_id=1,
|
|
186
|
-
amount="
|
|
91
|
+
amount="1000000000",
|
|
92
|
+
slippage=0.01,
|
|
187
93
|
)
|
|
188
|
-
|
|
189
94
|
if success:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
fastest = analysis.get("fastest")
|
|
194
|
-
|
|
195
|
-
print(f"Highest output route: {highest_output.get('output_amount')}")
|
|
196
|
-
print(f"Lowest fees route: {lowest_fees.get('total_fee')}")
|
|
197
|
-
print(f"Fastest route: {fastest.get('estimated_time')} seconds")
|
|
95
|
+
print(f"Gas fee: {data.get('gas_fee')}")
|
|
96
|
+
print(f"Bridge fee: {data.get('bridge_fee')}")
|
|
97
|
+
print(f"Total fee: {data.get('total_fee')}")
|
|
198
98
|
```
|
|
199
99
|
|
|
200
|
-
###
|
|
100
|
+
### validate_swap_parameters
|
|
101
|
+
|
|
102
|
+
Validate swap parameters before execution.
|
|
201
103
|
|
|
202
104
|
```python
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
|
|
105
|
+
success, data = await adapter.validate_swap_parameters(
|
|
106
|
+
from_token_address="0x...",
|
|
107
|
+
to_token_address="0x...",
|
|
207
108
|
from_chain_id=8453,
|
|
208
109
|
to_chain_id=1,
|
|
209
|
-
amount="
|
|
110
|
+
amount="1000000000",
|
|
210
111
|
)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
input_amount = int(data.get("input_amount", 0))
|
|
214
|
-
output_amount = int(data.get("output_amount", 0))
|
|
215
|
-
total_fee = int(data.get("total_fee", 0))
|
|
216
|
-
|
|
217
|
-
# Calculate effective rate
|
|
218
|
-
effective_rate = (input_amount - output_amount) / input_amount
|
|
219
|
-
print(f"Effective rate: {effective_rate:.4f} ({effective_rate * 100:.2f}%)")
|
|
220
|
-
print(f"Total fees: {total_fee / 1e18:.6f} tokens")
|
|
112
|
+
if success and data.get("valid"):
|
|
113
|
+
print("Parameters are valid")
|
|
221
114
|
```
|
|
222
115
|
|
|
223
|
-
##
|
|
224
|
-
|
|
225
|
-
The adapter uses the following Wayfinder API endpoints:
|
|
226
|
-
|
|
227
|
-
- `POST /api/v1/public/quotes/` - Get swap/bridge quotes
|
|
228
|
-
|
|
229
|
-
## Error Handling
|
|
230
|
-
|
|
231
|
-
All methods return a tuple of `(success: bool, data: Any)` where:
|
|
116
|
+
## Dependencies
|
|
232
117
|
|
|
233
|
-
- `
|
|
234
|
-
- `data` contains the response data on success or error message on failure
|
|
118
|
+
- `BRAPClient` - Low-level API client
|
|
235
119
|
|
|
236
120
|
## Testing
|
|
237
121
|
|
|
238
|
-
Run the adapter tests:
|
|
239
|
-
|
|
240
122
|
```bash
|
|
241
|
-
pytest wayfinder_paths/adapters/brap_adapter/
|
|
123
|
+
poetry run pytest wayfinder_paths/adapters/brap_adapter/ -v
|
|
242
124
|
```
|
|
243
|
-
|
|
244
|
-
## Dependencies
|
|
245
|
-
|
|
246
|
-
- `BRAPClient` - Low-level API client for BRAP operations
|
|
247
|
-
- `BaseAdapter` - Base adapter class with common functionality
|