@agentlayer.tech/wallet 0.1.0

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.
Files changed (96) hide show
  1. package/.openclaw/AGENTS.md +98 -0
  2. package/.openclaw/extensions/agent-wallet/README.md +127 -0
  3. package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
  5. package/.openclaw/extensions/agent-wallet/package.json +11 -0
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
  7. package/CHANGELOG.md +42 -0
  8. package/LICENSE +104 -0
  9. package/README.md +332 -0
  10. package/RELEASING.md +204 -0
  11. package/agent-wallet/.env.example +62 -0
  12. package/agent-wallet/AGENTS.md +129 -0
  13. package/agent-wallet/README.md +527 -0
  14. package/agent-wallet/agent_wallet/__init__.py +11 -0
  15. package/agent-wallet/agent_wallet/approval.py +161 -0
  16. package/agent-wallet/agent_wallet/bootstrap.py +178 -0
  17. package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
  18. package/agent-wallet/agent_wallet/config.py +382 -0
  19. package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
  20. package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
  21. package/agent-wallet/agent_wallet/exceptions.py +9 -0
  22. package/agent-wallet/agent_wallet/file_ops.py +34 -0
  23. package/agent-wallet/agent_wallet/http_client.py +25 -0
  24. package/agent-wallet/agent_wallet/models.py +66 -0
  25. package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
  26. package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
  27. package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
  28. package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
  29. package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
  30. package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
  31. package/agent-wallet/agent_wallet/providers/bags.py +259 -0
  32. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
  33. package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
  34. package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
  35. package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
  36. package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
  37. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
  38. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
  39. package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
  40. package/agent-wallet/agent_wallet/solana_stake.py +103 -0
  41. package/agent-wallet/agent_wallet/solana_tx.py +93 -0
  42. package/agent-wallet/agent_wallet/spending_limits.py +101 -0
  43. package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
  44. package/agent-wallet/agent_wallet/user_wallets.py +355 -0
  45. package/agent-wallet/agent_wallet/validation.py +31 -0
  46. package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
  47. package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
  48. package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
  49. package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
  50. package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
  51. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
  52. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
  53. package/agent-wallet/examples/bootstrap_wallet.py +21 -0
  54. package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
  55. package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
  56. package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
  57. package/agent-wallet/openclaw.plugin.json +138 -0
  58. package/agent-wallet/pyproject.toml +31 -0
  59. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
  60. package/agent-wallet/scripts/build_release_bundle.py +188 -0
  61. package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
  62. package/agent-wallet/scripts/install_agent_wallet.py +505 -0
  63. package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
  64. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
  65. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
  66. package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
  67. package/agent-wallet/scripts/security_utils.py +37 -0
  68. package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
  69. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
  70. package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
  71. package/bin/openclaw-agent-wallet.mjs +487 -0
  72. package/install-from-github.sh +134 -0
  73. package/package.json +61 -0
  74. package/setup.sh +40 -0
  75. package/wdk-btc-wallet/README.md +325 -0
  76. package/wdk-btc-wallet/bootstrap.sh +22 -0
  77. package/wdk-btc-wallet/package-lock.json +1839 -0
  78. package/wdk-btc-wallet/package.json +18 -0
  79. package/wdk-btc-wallet/run-local.sh +21 -0
  80. package/wdk-btc-wallet/src/config.js +160 -0
  81. package/wdk-btc-wallet/src/json.js +35 -0
  82. package/wdk-btc-wallet/src/local_vault.js +432 -0
  83. package/wdk-btc-wallet/src/network_state.js +84 -0
  84. package/wdk-btc-wallet/src/server.js +257 -0
  85. package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
  86. package/wdk-evm-wallet/README.md +183 -0
  87. package/wdk-evm-wallet/bootstrap.sh +8 -0
  88. package/wdk-evm-wallet/package-lock.json +2340 -0
  89. package/wdk-evm-wallet/package.json +23 -0
  90. package/wdk-evm-wallet/run-local.sh +12 -0
  91. package/wdk-evm-wallet/src/config.js +274 -0
  92. package/wdk-evm-wallet/src/json.js +35 -0
  93. package/wdk-evm-wallet/src/local_vault.js +430 -0
  94. package/wdk-evm-wallet/src/network_state.js +92 -0
  95. package/wdk-evm-wallet/src/server.js +575 -0
  96. package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
@@ -0,0 +1,215 @@
1
+ """Kamino REST API provider for lending market data and unsigned transaction building."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agent_wallet.config import settings
8
+ from agent_wallet.exceptions import ProviderError
9
+ from agent_wallet.http_client import get_client
10
+
11
+
12
+ def _normalized_api_base() -> str:
13
+ return settings.kamino_api_base_url.rstrip("/")
14
+
15
+
16
+ def _normalize_named_list_response(
17
+ data: Any,
18
+ *,
19
+ key: str,
20
+ provider_name: str,
21
+ ) -> dict[str, Any]:
22
+ if isinstance(data, list):
23
+ return {key: data}
24
+ if isinstance(data, dict):
25
+ items = data.get(key)
26
+ if isinstance(items, list):
27
+ return data
28
+ fallback = data.get("data")
29
+ if isinstance(fallback, list):
30
+ normalized = dict(data)
31
+ normalized[key] = fallback
32
+ return normalized
33
+ raise ProviderError(provider_name, f"Unexpected {key} response from Kamino.")
34
+
35
+
36
+ def _normalized_tx_response(data: Any, *, provider_name: str) -> dict[str, Any]:
37
+ if not isinstance(data, dict) or not isinstance(data.get("transaction"), str):
38
+ raise ProviderError(provider_name, "Unexpected transaction build response from Kamino.")
39
+ return data
40
+
41
+
42
+ def _env_name(network: str) -> str:
43
+ normalized = str(network).strip().lower()
44
+ if normalized == "devnet":
45
+ return "devnet"
46
+ return "mainnet-beta"
47
+
48
+
49
+ async def fetch_lend_markets() -> dict[str, Any]:
50
+ """Fetch Kamino lending markets for the configured program id."""
51
+ client = get_client()
52
+ response = await client.get(
53
+ f"{_normalized_api_base()}/v2/kamino-market",
54
+ params={"programId": settings.kamino_program_id},
55
+ )
56
+ if response.status_code != 200:
57
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
58
+ return _normalize_named_list_response(
59
+ response.json(),
60
+ key="markets",
61
+ provider_name="kamino",
62
+ )
63
+
64
+
65
+ async def fetch_lend_market_reserves(
66
+ *,
67
+ market: str,
68
+ network: str,
69
+ ) -> dict[str, Any]:
70
+ """Fetch reserve metrics for one Kamino lending market."""
71
+ client = get_client()
72
+ response = await client.get(
73
+ f"{_normalized_api_base()}/kamino-market/{market}/reserves/metrics",
74
+ params={"env": _env_name(network)},
75
+ )
76
+ if response.status_code != 200:
77
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
78
+ return _normalize_named_list_response(
79
+ response.json(),
80
+ key="reserves",
81
+ provider_name="kamino",
82
+ )
83
+
84
+
85
+ async def fetch_lend_user_obligations(
86
+ *,
87
+ market: str,
88
+ user: str,
89
+ network: str,
90
+ ) -> dict[str, Any]:
91
+ """Fetch Kamino obligations for a wallet in a market."""
92
+ client = get_client()
93
+ response = await client.get(
94
+ f"{_normalized_api_base()}/kamino-market/{market}/users/{user}/obligations",
95
+ params={"env": _env_name(network)},
96
+ )
97
+ if response.status_code != 200:
98
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
99
+ return _normalize_named_list_response(
100
+ response.json(),
101
+ key="obligations",
102
+ provider_name="kamino",
103
+ )
104
+
105
+
106
+ async def fetch_lend_user_rewards(*, user: str) -> dict[str, Any]:
107
+ """Fetch Kamino rewards summary for a wallet."""
108
+ client = get_client()
109
+ response = await client.get(
110
+ f"{_normalized_api_base()}/klend/users/{user}/rewards",
111
+ )
112
+ if response.status_code != 200:
113
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
114
+ data = response.json()
115
+ if not isinstance(data, dict):
116
+ raise ProviderError("kamino", "Unexpected rewards response from Kamino.")
117
+ rewards = data.get("rewards")
118
+ if rewards is None:
119
+ data = dict(data)
120
+ data["rewards"] = []
121
+ elif not isinstance(rewards, list):
122
+ raise ProviderError("kamino", "Unexpected rewards response from Kamino.")
123
+ return data
124
+
125
+
126
+ async def build_lend_deposit_transaction(
127
+ *,
128
+ wallet: str,
129
+ market: str,
130
+ reserve: str,
131
+ amount_ui: str,
132
+ ) -> dict[str, Any]:
133
+ """Build an unsigned Kamino deposit transaction."""
134
+ client = get_client()
135
+ response = await client.post(
136
+ f"{_normalized_api_base()}/ktx/klend/deposit",
137
+ json={
138
+ "wallet": wallet,
139
+ "market": market,
140
+ "reserve": reserve,
141
+ "amount": amount_ui,
142
+ },
143
+ )
144
+ if response.status_code != 200:
145
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
146
+ return _normalized_tx_response(response.json(), provider_name="kamino")
147
+
148
+
149
+ async def build_lend_withdraw_transaction(
150
+ *,
151
+ wallet: str,
152
+ market: str,
153
+ reserve: str,
154
+ amount_ui: str,
155
+ ) -> dict[str, Any]:
156
+ """Build an unsigned Kamino withdraw transaction."""
157
+ client = get_client()
158
+ response = await client.post(
159
+ f"{_normalized_api_base()}/ktx/klend/withdraw",
160
+ json={
161
+ "wallet": wallet,
162
+ "market": market,
163
+ "reserve": reserve,
164
+ "amount": amount_ui,
165
+ },
166
+ )
167
+ if response.status_code != 200:
168
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
169
+ return _normalized_tx_response(response.json(), provider_name="kamino")
170
+
171
+
172
+ async def build_lend_borrow_transaction(
173
+ *,
174
+ wallet: str,
175
+ market: str,
176
+ reserve: str,
177
+ amount_ui: str,
178
+ ) -> dict[str, Any]:
179
+ """Build an unsigned Kamino borrow transaction."""
180
+ client = get_client()
181
+ response = await client.post(
182
+ f"{_normalized_api_base()}/ktx/klend/borrow",
183
+ json={
184
+ "wallet": wallet,
185
+ "market": market,
186
+ "reserve": reserve,
187
+ "amount": amount_ui,
188
+ },
189
+ )
190
+ if response.status_code != 200:
191
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
192
+ return _normalized_tx_response(response.json(), provider_name="kamino")
193
+
194
+
195
+ async def build_lend_repay_transaction(
196
+ *,
197
+ wallet: str,
198
+ market: str,
199
+ reserve: str,
200
+ amount_ui: str,
201
+ ) -> dict[str, Any]:
202
+ """Build an unsigned Kamino repay transaction."""
203
+ client = get_client()
204
+ response = await client.post(
205
+ f"{_normalized_api_base()}/ktx/klend/repay",
206
+ json={
207
+ "wallet": wallet,
208
+ "market": market,
209
+ "reserve": reserve,
210
+ "amount": amount_ui,
211
+ },
212
+ )
213
+ if response.status_code != 200:
214
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
215
+ return _normalized_tx_response(response.json(), provider_name="kamino")
@@ -0,0 +1,277 @@
1
+ """LI.FI cross-chain quote and status provider."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from agent_wallet.config import settings
8
+ from agent_wallet.exceptions import ProviderError
9
+ from agent_wallet.http_client import get_client
10
+
11
+ EVM_NATIVE_TOKEN = "0x0000000000000000000000000000000000000000"
12
+ SOLANA_NATIVE_TOKEN = "11111111111111111111111111111111"
13
+ ALWAYS_DENIED_BRIDGES = ("mayan",)
14
+
15
+ _CHAIN_ALIASES = {
16
+ "1": "1",
17
+ "eth": "1",
18
+ "ethereum": "1",
19
+ "mainnet": "1",
20
+ "eth-mainnet": "1",
21
+ "8453": "8453",
22
+ "base": "8453",
23
+ "base-mainnet": "8453",
24
+ "1151111081099710": "1151111081099710",
25
+ "sol": "1151111081099710",
26
+ "solana": "1151111081099710",
27
+ }
28
+
29
+ _CHAIN_NAMES_BY_ID = {
30
+ "1": "ethereum",
31
+ "8453": "base",
32
+ "1151111081099710": "solana",
33
+ }
34
+
35
+ OPENCLAW_SUPPORTED_CHAINS = [
36
+ {"chain": "ethereum", "chain_id": "1", "key": "eth", "name": "Ethereum", "coin": "ETH"},
37
+ {"chain": "base", "chain_id": "8453", "key": "bas", "name": "Base", "coin": "ETH"},
38
+ {"chain": "solana", "chain_id": "1151111081099710", "key": "sol", "name": "Solana", "coin": "SOL"},
39
+ ]
40
+ _KNOWN_EVM_TOKEN_ADDRESSES = {
41
+ "1": {
42
+ "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
43
+ "0xdac17f958d2ee523a2206206994597c13d831ec7": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
44
+ },
45
+ "8453": {
46
+ "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
47
+ },
48
+ }
49
+
50
+
51
+ def normalize_chain_id(value: str, *, field_name: str) -> str:
52
+ chain = str(value or "").strip().lower()
53
+ normalized = _CHAIN_ALIASES.get(chain, chain)
54
+ if normalized not in _CHAIN_NAMES_BY_ID:
55
+ raise ProviderError("lifi", f"{field_name} is not supported by OpenClaw LI.FI routing: {value}")
56
+ return normalized
57
+
58
+
59
+ def chain_name_for_id(chain_id: str) -> str:
60
+ return _CHAIN_NAMES_BY_ID.get(str(chain_id), str(chain_id))
61
+
62
+
63
+ def normalize_token_address(token: str, *, chain_id: str) -> str:
64
+ text = str(token or "").strip()
65
+ if not text:
66
+ raise ProviderError("lifi", "token address is required.")
67
+ alias = text.lower()
68
+ if chain_id == "1151111081099710" and alias in {"native", "sol", "solana"}:
69
+ return SOLANA_NATIVE_TOKEN
70
+ if chain_id in {"1", "8453"} and alias in {"native", "eth", "ethereum"}:
71
+ return EVM_NATIVE_TOKEN
72
+ if chain_id in _KNOWN_EVM_TOKEN_ADDRESSES:
73
+ known = _KNOWN_EVM_TOKEN_ADDRESSES[chain_id].get(alias)
74
+ if known:
75
+ return known.lower()
76
+ if chain_id in {"1", "8453"} and alias.startswith("0x") and len(alias) == 42:
77
+ return alias
78
+ return text
79
+
80
+
81
+ def format_openclaw_supported_chains(chains: list[dict[str, Any]]) -> list[dict[str, Any]]:
82
+ """Return OpenClaw's supported LI.FI subset, including Solana if omitted from discovery."""
83
+ items = [
84
+ {
85
+ "chain_id": str(item.get("id") or item.get("chainId") or "").strip(),
86
+ "key": str(item.get("key") or "").strip() or None,
87
+ "name": str(item.get("name") or "").strip() or None,
88
+ "coin": str(item.get("coin") or "").strip() or None,
89
+ "native_token": item.get("nativeToken"),
90
+ "raw": item,
91
+ }
92
+ for item in chains
93
+ ]
94
+ supported_ids = {chain["chain_id"] for chain in OPENCLAW_SUPPORTED_CHAINS}
95
+ supported_keys = {chain["key"] for chain in OPENCLAW_SUPPORTED_CHAINS}
96
+ supported_by_id = {
97
+ item["chain_id"]: item
98
+ for item in items
99
+ if item["chain_id"] in supported_ids or str(item.get("key") or "").lower() in supported_keys
100
+ }
101
+ for chain in OPENCLAW_SUPPORTED_CHAINS:
102
+ supported_by_id.setdefault(
103
+ chain["chain_id"],
104
+ {
105
+ "chain_id": chain["chain_id"],
106
+ "key": chain["key"],
107
+ "name": chain["name"],
108
+ "coin": chain["coin"],
109
+ "native_token": None,
110
+ "raw": None,
111
+ },
112
+ )
113
+ return [supported_by_id[chain["chain_id"]] for chain in OPENCLAW_SUPPORTED_CHAINS]
114
+
115
+
116
+ def _headers() -> dict[str, str]:
117
+ headers = {
118
+ "Accept": "application/json",
119
+ }
120
+ api_key = settings.lifi_api_key.strip()
121
+ if api_key:
122
+ headers["x-lifi-api-key"] = api_key
123
+ return headers
124
+
125
+
126
+ def _base_url() -> str:
127
+ return settings.lifi_api_base_url.rstrip("/")
128
+
129
+
130
+ def _normalize_error(payload: Any) -> str:
131
+ if isinstance(payload, dict):
132
+ message = (
133
+ payload.get("message")
134
+ or payload.get("error")
135
+ or payload.get("detail")
136
+ or payload.get("description")
137
+ )
138
+ if message:
139
+ return str(message)
140
+ errors = payload.get("errors")
141
+ if isinstance(errors, list) and errors:
142
+ return "; ".join(str(item) for item in errors[:3])
143
+ return "Route not found."
144
+
145
+
146
+ def _csv(value: str | list[str] | tuple[str, ...] | None) -> str | None:
147
+ if value is None:
148
+ return None
149
+ if isinstance(value, str):
150
+ text = value.strip()
151
+ return text or None
152
+ if isinstance(value, (list, tuple)):
153
+ items = [str(item).strip() for item in value if str(item).strip()]
154
+ return ",".join(items) if items else None
155
+ return str(value).strip() or None
156
+
157
+
158
+ def _merge_bridge_csv(*values: str | list[str] | tuple[str, ...] | None) -> str | None:
159
+ items: list[str] = []
160
+ for value in values:
161
+ text = _csv(value)
162
+ if not text:
163
+ continue
164
+ for item in text.split(","):
165
+ bridge = item.strip()
166
+ if bridge and bridge.lower() not in {existing.lower() for existing in items}:
167
+ items.append(bridge)
168
+ return ",".join(items) if items else None
169
+
170
+
171
+ def _clean_params(params: dict[str, Any]) -> dict[str, Any]:
172
+ cleaned: dict[str, Any] = {}
173
+ for key, value in params.items():
174
+ if value is None:
175
+ continue
176
+ if isinstance(value, str) and not value.strip():
177
+ continue
178
+ cleaned[key] = value
179
+ return cleaned
180
+
181
+
182
+ async def fetch_supported_chains() -> list[dict[str, Any]]:
183
+ client = get_client()
184
+ response = await client.get(
185
+ f"{_base_url()}/chains",
186
+ headers=_headers(),
187
+ )
188
+ payload = response.json()
189
+ if response.status_code != 200:
190
+ raise ProviderError("lifi", f"HTTP {response.status_code}: {_normalize_error(payload)}")
191
+ if isinstance(payload, dict) and isinstance(payload.get("chains"), list):
192
+ return [item for item in payload["chains"] if isinstance(item, dict)]
193
+ if isinstance(payload, list):
194
+ return [item for item in payload if isinstance(item, dict)]
195
+ raise ProviderError("lifi", "Unexpected chains response from LI.FI.")
196
+
197
+
198
+ async def fetch_quote(
199
+ *,
200
+ from_chain: str,
201
+ to_chain: str,
202
+ from_token: str,
203
+ to_token: str,
204
+ amount_in_raw: str,
205
+ from_address: str,
206
+ to_address: str,
207
+ slippage: float | int | None = None,
208
+ integrator: str | None = None,
209
+ allow_bridges: str | list[str] | None = None,
210
+ deny_bridges: str | list[str] | None = None,
211
+ prefer_bridges: str | list[str] | None = None,
212
+ ) -> dict[str, Any]:
213
+ from_chain_id = normalize_chain_id(from_chain, field_name="from_chain")
214
+ to_chain_id = normalize_chain_id(to_chain, field_name="to_chain")
215
+ default_deny_bridges = settings.lifi_default_deny_bridges.strip()
216
+ effective_deny_bridges = _merge_bridge_csv(
217
+ default_deny_bridges,
218
+ deny_bridges,
219
+ ALWAYS_DENIED_BRIDGES,
220
+ )
221
+ params = _clean_params(
222
+ {
223
+ "fromChain": from_chain_id,
224
+ "toChain": to_chain_id,
225
+ "fromToken": normalize_token_address(from_token, chain_id=from_chain_id),
226
+ "toToken": normalize_token_address(to_token, chain_id=to_chain_id),
227
+ "fromAmount": str(amount_in_raw).strip(),
228
+ "fromAddress": str(from_address).strip(),
229
+ "toAddress": str(to_address).strip(),
230
+ "slippage": slippage,
231
+ "integrator": (integrator or settings.lifi_integrator).strip(),
232
+ "allowBridges": _csv(allow_bridges),
233
+ "denyBridges": effective_deny_bridges,
234
+ "preferBridges": _csv(prefer_bridges),
235
+ }
236
+ )
237
+ client = get_client()
238
+ response = await client.get(
239
+ f"{_base_url()}/quote",
240
+ params=params,
241
+ headers=_headers(),
242
+ )
243
+ payload = response.json()
244
+ if response.status_code != 200:
245
+ raise ProviderError("lifi", f"HTTP {response.status_code}: {_normalize_error(payload)}")
246
+ if not isinstance(payload, dict):
247
+ raise ProviderError("lifi", "Unexpected quote response from LI.FI.")
248
+ return payload
249
+
250
+
251
+ async def fetch_transfer_status(
252
+ *,
253
+ tx_hash: str,
254
+ bridge: str | None = None,
255
+ from_chain: str | None = None,
256
+ to_chain: str | None = None,
257
+ ) -> dict[str, Any]:
258
+ params = _clean_params(
259
+ {
260
+ "txHash": str(tx_hash).strip(),
261
+ "bridge": str(bridge).strip() if isinstance(bridge, str) else None,
262
+ "fromChain": normalize_chain_id(from_chain, field_name="from_chain") if from_chain else None,
263
+ "toChain": normalize_chain_id(to_chain, field_name="to_chain") if to_chain else None,
264
+ }
265
+ )
266
+ client = get_client()
267
+ response = await client.get(
268
+ f"{_base_url()}/status",
269
+ params=params,
270
+ headers=_headers(),
271
+ )
272
+ payload = response.json()
273
+ if response.status_code != 200:
274
+ raise ProviderError("lifi", f"HTTP {response.status_code}: {_normalize_error(payload)}")
275
+ if not isinstance(payload, dict):
276
+ raise ProviderError("lifi", "Unexpected status response from LI.FI.")
277
+ return payload